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指針依然存在空指針,並且有可能產生野指針。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM