1、引用的使用
- 引用必須在定義時候初始化,後期綁定不能更改 ,可以相當於 * const
int rats=101;
int & rodents=rats; //等價於 int * const rodents =&rats;
int rats2 =102;
rodents =rats2; //此時只是將 rodents的值設置爲 rats2 並非更改引用綁定。
- 函數參數的引用 ,在聲明和原型中指明引用, 但是從調用上看不出來
void swap1(int &a, int &b); //傳遞引用
void swap2(int a , int b ); //傳遞拷貝
void swap3(int *a, int *b); //傳遞指針
...
int x=1,y=2;
swap1(x, y); //不能顯示看出是引用還是拷貝型
swap2(x, y);
swap3(&x, &y); //明顯可見是指針調用類型
- 臨時匿名變量生成的情況。在實參並不是實際存在內存空間中的一個變量的時候,編譯器在編譯期間將會生成一個匿名臨時變量,並讓引用n引用指向這個變量,函數結束後便刪除臨時變量。
double caln(const double &n)
{
return n*n;
}
double a =3.0;
double out1 =double(5.0);
double out2 =double(a+3);
若形參和實參不配,則考慮兩種情況:
- 普通引用調用類型,顯示實參類型不匹配
double recal2( double & ra)
{
ra = ra * 3;
return ra;
}
double recal3(double & ra)
{
return ra * 3;
}
...
int a = 9;
cout << "result :" << recal2(a) << endl;
cout << "result :" << recal3(a) << endl;
- const 類型的引用調用,由於只是傳參數調用,並不會修改原值 ,故編譯器生成一個拷貝使用
double recal(const double & ra)
{
return ra * 2;
}
...
int a = 9;
cout << "result :" << recal(a) << endl;
所以如果只是簡單的傳值調用,可以考慮用上const 引用以防止出現這個錯誤
若不修改變量的值,應該儘可能使用const
- const 可以避免無意中修改數據的編程錯誤
- const 形參可以處理const 和非const 實參 ,否則只能接受非const 的數據
- 使用const 引用使函數能夠正確生成並使用臨時變量
函數中返回引用,返回變量 ,接受返回變量,接受引用。以下是測試和測試結果:
- 在引用中修改引用值,變量改變,從out1,out3 可以看出,不用多說。
- 外部定義變量接受函數返回值,返回的時候臨時變量還在,外部變量af,bf,cf,df正確賦值。從地址上看,雖然1 2函數返回類型爲變量,3 4函數返回類型爲引用,但是,af -df 確實都是新的變量和原始的 a b c d 沒有關係。
- out1 out2 返回類型並非引用類型,但是用引用類型接收,出現如下錯誤:
必須爲左值也就是這個要是可以被賦值的,比如 a變量,因爲左值有固定內存地址,可以制定引用的地址。一會兒可以測試一下。
- 函數內return 爲原外部變量引用的,out3 可以明顯看出和c 是一個地址 ,正確引用。
- 函數內 return 爲內部臨時變量的,從地址上看確實還是臨時使用變量的地址,但是由於在函數調用結束後,變量空間被釋放。期間其他程序可以使用這個空間,導致內存值變化。所以不應該用臨時變量作爲引用的返回值。
看如下的測試程序:
int out1(int & a){
a += 10;
return a;
}
int out2(int & b){
int m1 = b + 20;
return m1;
}
int & out3(int & c){
c += 30;
return c;
}
int &out4(int & d){
int m2 = d+ 40;
cout << " address m2 :" << &m2 << endl;
return m2;
}
int main()
{
int a = 0, b = 0, c = 0, d = 0;
cout << " address: a:" << &a << " a :" << a <<endl;
cout << " address: b:" << &b << " b :" << b << endl;
cout << " address: c:" << &c << " c :" << c << endl;
cout << " address: d:" << &d << " d :" << d << endl <<endl;
int af = out1(a);
int bf = out2(b);
int cf = out3(c);
int df = out4(d);
//int &ay = out1(a);
//int &by = out2(b);
int &cy = out3(c);
int &dy = out4(d);
cout << " 測試函數調用對原變量的影響: " << endl;
cout << " address: a:" << &a << " a :" << a << endl;
cout << " address: b:" << &b << " b :" << b << endl;
cout << " address: c:" << &c << " c :" << c << endl;
cout << " address: d:" << &d << " d :" << d << endl<<endl;
cout << " 測試使用普通變量接受返回值: " << endl;
cout << " address: af:" << &af << " af :" << af << endl;
cout << " address: bf:" << &bf << " bf :" << bf << endl;
cout << " address: cf:" << &cf << " cf :" << cf << endl;
cout << " address: df:" << &df << " df :" << df << endl<<endl;
cout << " 測試使用引用接受返回值: " << endl;
//cout << " address: ay:" << &af << " ay :" << ay << endl;
//cout << " address: by:" << &bf << " by :" << by << endl;
cout << " address: cy:" << &cy << " cy :" << cy<< endl;
cout << " address: dy:" << &dy << " dy :" << dy << endl;
cin.get();
return 0;
}
- 以下是對函數返回的測試
//cout << "address :out1 " << &(out1(a)) << &(1)<<endl;
//cout << "address :out2 " << &out2(b) << endl;
cout << "address :out3 " << &out3(c) << endl;
cout << "address :out4 " << &out4(d) << endl;
- 前面兩個報錯,註釋掉。錯誤的原因從兩個對比可以看出,得到是一個左值。而取地址只能取一個左值的地址,也就是一個變量的地址,因爲變量在內存空間中有固定的地址。而單獨的數字,如圖中的數字1,沒有指定內存空間,不能取地址。普通的函數返回值在臨時內存塊內,運行到下一句可能就不存在。具體解釋看後面介紹。
- 從運行結果來看,out3 out4返回的都是引用類型,編譯器開闢空間存儲引用,故是存在具體的內存空間的,可以取地址。
返回引用使用堆存儲數據
之所以函數內返回臨時變量的引用會出問題的原因在於,臨時變量存儲在棧上,生命週期只是當前的 塊內,就是花括號內 {}。只要能夠存在生命週期更長的地方就沒問題了。不妨試一下存在堆內會怎麼樣。
int &out5(int &e)
{
int *p = new int[1];
p=&e;
*p += 50;
return *p;
}
...
int e=0;
cout << " address e: " << &e << " result : " << e << endl;
cout << " address out5: " << &out5(e) << " result : " << out5(e) << endl;
- 結果符合預期估計,因爲申請的堆空間沒有被釋放,存儲的內容就不會丟失。
若是如下釋放對應的空間,再次去訪問空間,則會卡死奔潰
int e=0;
int *fp1 = &out5(e);
int *fp2 = fp1;
cout << " fp1 : " << fp1 << " fp2 : " << fp2 << endl;
delete fp1;
cout << " address e: " << &e << " result : " << e << endl;
cout << " address out5: " << fp2<< " result : " << *fp2 << endl;
- 關鍵性的一點 C++ Primer Plus P268頁
函數的引用返回值是左值,可以用於取地址
函數的常規返回類型是右值,不能用於取地址
以下測試一下這一點
可以後面兩個是返回引用函數,看到確實沒有報錯
關於什麼時候適合使用引用
- 程序員能夠修改調用函數中的數據對象。
- 通過傳遞引用而不是整個數據對象,提高程序的運行速度。
傳值 、指針、引用的選擇
- 如果數據很小,內置數據類型或者小型結構 ,傳值操作
- 如果對象是數組,那麼指針是唯一的方式
- 如果對象是較大的結構,那麼使用引用或者指針能夠節省比較大的時間和空間開銷。
- 如果數據對象是類的話,使用引用的操作方式則是標準操作方式。
- 如果將在操作中修改傳的數據值,使用指針會更加直觀。swap(&a,&b);能夠明顯看出意圖。