C++ 引用变量(Reference variable)

C++ adds a new compound type to the language - the reference variable. A reference is a name that acts as an alias, or an alternative name, for a previously defined variable. For example, if you make tawin a reference to the celmens variable, you can use twain and clemens interchangeably to represent that variable. Of what use is such an alias? Is it to help people who are embarrassed by their choice of variable names? Maybe, but the main use for a reference variable is as a formal argument to a function. If you use a reference as an argument, the function works with the original data instead of with a copy. References provide a convenient alternative to pointers for processing large structures with a function, and they are essential for designing classes. 

[Creating a Reference Variable] The reference declaration allows you to use rats and rodents interchangebly; both refer to the same value and the same memory location.

int rats;
int & rodents = rats; // makes rodents an alias for rats 
cout<<&rats<<' '<<&rodents<<endl; // print addresses of both variables

Note that you should initialize a reference variable when you declare it.

[Reference as Function Parameters] Most often, references are used as function parameters, making a variable name in a function an alias for a varibale in the calling program. This method of passing arguments is called passing by reference. Passing by reference allows a called function to access variables in the calling function. C++'s addition of the feature is a break from C, which only passes by value. Passing by value, recall, results in the called function working with copies of values from the calling program. Of course, C lets you get around the passing by value limitation by using pointers.

[Temporary Variables, Reference Argumetns, and const]  

What's an lvalue? - An argument that's an lvalue is a data object that can be refernced by address. For example, a variable, an array element, a structure member, a reference, and a dereferenced pointer are lvalues. Non-lvalues include literal constants (aside from quoted strings, wihch are represented by their addresses) and expressions with multiple terms. The term lvalue in C originally meant entities that could appear on the left side of an assignment statement, but that was before the const keyword was introduced. Now both a regular variable and a const variable would be considered lvalues because both can be accessd by address. But the regular variable can be further characterized as being a modifiable lvalue and the const variable as a non-modified lvalue.  

int foo; //lvalue  &foo 有效
foo+5;   //rvalue  &(foo+5) 无效

[when c++ generates temporal variable?] C++ can generate a temporary variable if the actual argument doesn't match a reference argument. Currently, C++ permits this only if the argument is a cons reference, but this was not always the case. Let's look at the cases in which C++ does generate temporary variables and see why the restriction to a const reference makes sense. 

First, when is a temporary variable created? Provided that the reference parameter is a const, the compiler generates a temporary variable in two kinds of situations:

  • when the actual argument is the correct type but isn't an lvalue
  • when the actual argument is of the wrong type, but it's of a type that can be converted to the correct type

Now, return to examples as following

 1 double refcube(const double &ra) { return ra * ra * ra;}
 2 double side  = 3.0;
 3 double *pd  = &side;
 4 double &rd  = side;
 5 long edge = 5L;
 6 double lens[4] = {2.0,5.0,10.0,12.0};
 7 double c1 = refcube(side);      // ra is side
 8 double c2 = refcube(lens[2]);   // ra is lens[2]
 9 double c3 = refcube(rd);        // ra is rd/side
10 double c4 = refcube(*pd);       // ra is *pd/side/
11 double c5 = refcube(edge);      // ra is temporary variable, long->double
12 double c6 = refcube(7.0);       // ra is temporary variable, 7.0 is non-lvalue
13 double c7 = refcube(side+10.0); // ra is temporary variable, side+10.0 is non-lvalue 

In above code, the arguments side, lens[2], rd and *pd are type double data objects with names, so it is possible to generate a reference for them, for them, and no temporary variables are needed. But although edge is a variable, it is of the wrong type. A reference to a double can't refer to a long. The argments 7.0 and side + 10.0, on the other hand, are the right type, but they are not named data objects. In each of these cases, the compiler generates a temporary, anonymous variable and makes ra refer to it. These temporary variables last for the duration of the function call, but then the compiler is free to dump them.

[Use const When You Can] There are three strong reasons to declare reference arguments as references to constant data

  • Using const protects you against programming errors that inadvertently alter data
  • Using const allows a function to process both const and non-const actual arguments, whereas a function that omits const in the prototype only can accept non-const data
  • Using a const reference allows the function to generate and use a temporary variable appropriately

[rvalue reference] C++ introduces a second kind of reference, called an rvalue reference, that can refer to an rvalue. It is declared using &&. 

1 double &&rref = std::sqrt(36.00);
2 double j = 15.0;
3 double &&  jref = 2.0 * j + 18.5;
4 std::cout << rref << '\n';
5 std::cout << jref << '\n';

The rvalue reference was introduced mainly to help library designers provide more efficient implementations of certain operations. The original reference type (the one declared using a single &) is now called an lvalue reference.

[Using References with a Structure]

References work wonderfully with structures and classes, C++'s user-defined types. Indeed, references were introduced primarily for use with these types, not for use with the basic build-in types.  The method for using a reference to a structure as a function parameter is the same as the method for using a reference to a basic variable. For example, 

1 struct free_throws{
2     std::string name;
3     int made;
4     int attempts;
5     float percent;
6 };
7 void set_pc(free_throws &rt);        // use a reference to a structure
8 void set_pc(const free_throws &rt);  //don't allow changes to structure

【Why Return a Reference】

Let's look a bit further at how returning a reference is different from the traditional return mechanism. The latter works much like passing by value does with function parameters. The expression following the return is evaluated, and that value is passed back to the calling function. Conceptually, this value is copied to a temporary location and the calling program uses the value. Consider the following 

1 double m = sqrt(16.0);
2 cout<<sqrt(25.0);

In the first statement, the value 4.0 is copied to a temporary location and then  the value in that is copied to m. 

In the second statement, the value 5.0 is copied to a temporary location, then the contents of that location are passed on to count (This is the conceptual description. In practice, an optimizing compiler might consolidate some of the steps). Now consider this statement.

1 dup = accumulate(team, five);

If accumulate() returned a structure instead of a reference to a structure, this could involve copying the entire structure to a temporary location and then coping that copy to dup. But with a reference return value, returning value is copied directly to dup, a more efficient approach. Note: A function that returns a reference is actually an alias for the referred-to variable. 

【Being Careful About What a Return Reference Refers To】

The single most important point to remember when returning a reference is to avoid returning reference to a memory location that ceases to exist when the function terminates. What you want to void is code along these lines.

1 const free_throws & clone2(free_throws &ft){
2    free_throws newguy;
3    newguy = ft;
4    return newguy;      
5 }

This has the unfortunate effect of returning a reference to a temporary variable (newguy) that passes from existance as soon as the function terminates. For avoiding this problem, there are two methods. 

  • First is to return a reference that was passed as an argument to the function. A reference parameter will refer to data used by the calling function; hence, the returned reference will refer to that same data.
  • Second is to use pointer to return reference parameter, as follows
1 const free_throws & clone(free_throws &ft){
2      free_throws *pt;
3      *pt = ft;         // pointer to &ft
4      return *pt;       // return pointer's content &ft
5 } 

 

【题目3-1】一般变量的引用

 1 #include <iostream>
 2 #include <string>
 3 using namespace std;
 4 int main(){
 5     int a = 10;
 6     int b = 20;
 7     int &rn = a;
 8     int equal;
 9     
10     rn = b;
11     cout<<"a="<<a<<endl;  //20
12     cout<<"b="<<b<<endl;  //20
13     
14     rn = 100;
15     cout<<"a="<<a<<endl;  //100
16     cout<<"b="<<b<<endl;  //20
17     
18     equal = (&a==&rn)?1:0; //1
19     cout<<"equal="<<equal<<endl;
20     return 0;
21 }

运行结果如下

a = 20
b = 20
a = 100
b = 20
equal = 1

【题目3-2】指针变量引用

 1 #include <iostream>
 2 using namespace std;
 3 int main(){
 4     int a = 1;
 5     int b = 10;
 6     int* p = &a;
 7     int* &pa = p;
 8     
 9     (*pa)++;
10     cout<<"a="<<a<<endl;   //2
11     cout<<"b="<<b<<endl;   //10
12     cout<<"*p="<<*p<<endl; //2
13     
14     pa = &b;
15     (*pa)++;
16     cout<<"a="<<a<<endl;   //2
17     cout<<"b="<<b<<endl;   //11
18     cout<<"*p="<<*p<<endl; //11
19     return 0;
20 }

【题目3-3】代码找错 - 变量引用

 1 #include <iostream>
 2 using namespace std;
 3 int main(){
 4     int a = 1,b = 2;
 5     int &c;      // 引用在声明时要初始化
 6     int &d = a;
 7     &d = b;      // 引用只能在声明的时候被赋值,以后都不能再把该应用作为其他变量名的别名
 8     int *p;
 9     *p = 5;      //p没有被初始化,为野指针,对野指针赋值会导致程序运行时崩溃
10     return 0;
11 }

【知识点】引用在声明时要被初始化,且之后被不能被作为其他变量名的别名

【题目3-4】实现交换两个字符串的功能

 1 #include <iostream>
 2 using namespace std;
 3 void swap(char* &x, char* &y){
 4     char *temp;
 5     temp = x;
 6     x = y;
 7     y = temp;
 8 }
 9 int main(){
10     char *ap = "hello";
11     char *bp = "how are you?";
12     
13     cout<<"*ap":<<ap<<endl;
14     cout<<"*bp:"<<bp<<endl;
15     swap(ap,bp);
16     cout<<"*ap:"<<ap<<endl;
17     cout<<"*bp:">>bp<<endl;
18     return 0;
19 }

运行结果如下

*ap: hello 
*bp: how are you?
*ap: how are you?
*bp: hello

【题目3-5】程序查错 - 参数引用

 1 #include <iostream>
 2 using namespace std;
 3 
 4 const float pi = 3.14f;
 5 float f;
 6 
 7 float f1(float r){
 8     f = r * r * pi;
 9     return f;
10 }
11 
12 float& f2(float r){
13     f = r * r * pi;
14     return f;
15 }
16 
17 int main(){
18     float f1(float=5);  // 声明 f1() 函数默认参数为5
19     float& f2(float=5); // 声明 f2() 函数默认参数为5
20     float a = f1();     // 78.5
21     float& b = f1();    // wrong
22     float c= f2();      // 78.5
23     float& d = f2();    // 78.5
24     d += 1.0f;          // 79.5
25     cout<<"a="<<a<<endl;  // 78.5
26     cout<<"b="<<b<<endl;  // 注释掉
27     cout<<"c="<<c<<endl;  // 78.5
28     cout<<"d="<<d<<endl;  // 79.5
29     cout<<"f="<<f<<endl;  // 79.5
30     return 0;
31 }

Line 18,19 对 f1,f2 函数进行函数默认参数确定

Line 21 发生错误,f1() 返回为临时变量,不能对临时变量建立引用

Line 23 对全局变量 f 进行引用。d 变量的声明周期 小于 f 全局变量的 声明周期,没有问题。但是,此时将一个局部变量的 引用返回,会出现错误

【知识点】:不能对临时变量建立引用。若错误操作,会产生如下的报错信息

Non-const lvalue reference to type 'float' cannot bind to a temporary of type 'float'

【题目3-6】参数引用常见的错误

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Test{
 5 public:
 6     void f(const int& arg);
 7 private:
 8     int value;
 9 };
10 
11 void Test::f(const int& arg){
12     arg = 10;                   // wrong! const 变量不能被修改
13     cout<<"arg = "<<arg<<endl;
14     value = 20;
15 }
16 
17 int main(){
18     int a = 7;
19     const int b = 10;
20     int &c = b;       // wrong! 应该改成 const int &c = b;
21     const int &d = a;
22     a++;
23     d++;              // wrong! d 是 const int 类型不能修改
24     Test test;
25     test.f(a);
26     cout<<"a = "<<a<<endl;
27     return 0;
28 }

Line 20: 对于常量类型的变量,其引用也必须是常量类型的

Line 21: 对于非常来那个类型的变量,其引用可以是非常量的,也可以是常量的。但是要注意,无论什么情况下,都不能使用常量引用修改起引用的变量的值

【题目3-7】指针和引用有什么区别?

(1)初始化要求不同:引用在创建的同时必须初始化,即引用到一个有效的对象,而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值

(2)可修改性不同:引用一旦被初始化指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象。

(3)不存在NULL引用,引用不能使用指向空值的引用,它必须总指向某个对象;而指针在任何时候都可以是NULL,不需要总是指向某些对象,可以把指针指向任意的对象,所以指针更加灵活,也容易出错

(4)测试需要的区别:由于引用不会指向空值,这以为这使用引用之前不需要测试它的合法性;而指针需要经常进行测试。因此使用引用的代码效率比使用指针的要高

(5)应用的区别:如果是指一旦指向一个对象后就不会改变指向,那么应该使用引用。如果有存在指向NULL或在不同的时刻指向不同对象这些可能性,应该使用指针。

【题目3-8】为什么传引用比传指针安全?

由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全,const指针依然存在空指针,并且有可能产生野指针。

posted on 2020-03-11 21:17  猪伯  阅读(1580)  评论(0编辑  收藏  举报

导航