1. 程式人生 > >C++:引用的簡單理解

C++:引用的簡單理解

傳遞 技術 ren ring 知識 cout 進行 表達 並且

前言:引用是C++一個很重要的特性,最近看了很多有關引用的資料和博客,故在此對引用的相關知識進行總結

一、什麽是引用

引用,顧名思義是某一個變量或對象的別名,對引用的操作與對其所綁定的變量或對象的操作完全等價

語法:類型 &引用名=目標變量名;

特別註意:

1.&不是求地址運算符,而是起標誌作用

2.引用的類型必須和其所綁定的變量的類型相同

1 #include<iostream>
2 using namespace std;
3 int main(){
4     double a=10.3;
5     int &b=a; //錯誤,引用的類型必須和其所綁定的變量的類型相同
6 cout<<b<<endl; 7 }

技術分享

3.聲明引用的同時必須對其初始化,否則系統會報錯

1 #include<iostream>
2 using namespace std;
3 int main(){
4     int &a; //錯誤!聲明引用的同時必須對其初始化
5     return 0;
6 }

技術分享

4.引用相當於變量或對象的別名,因此不能再將已有的引用名作為其他變量或對象的名字或別名

5.引用不是定義一個新的變量或對象,因此內存不會為引用開辟新的空間存儲這個引用

1 #include<iostream>
2 using
namespace std; 3 int main(){ 4 int value=10; 5 int &new_value=value; 6 cout<<"value在內存中的地址為:"<<&value<<endl; 7 cout<<"new_value在內存中的地址為:"<<&new_value<<endl; 8 return 0; 9 }

技術分享

6.對數組的引用

語法:類型 (&引用名)[數組中元素數量]=數組名;
 1 #include<iostream>
 2
using namespace std; 3 int main(){ 4 int a[3]={1,2,3}; 5 int (&b)[3]=a;//對數組的引用 6 cout<<&a[0]<<" "<<&b[0]<<endl; 7 cout<<&a[1]<<" "<<&b[1]<<endl; 8 cout<<&a[2]<<" "<<&b[2]<<endl; 9 return 0; 10 }

技術分享

7.對指針的引用

語法:類型 *&引用名=指針名;//可以理解為:(類型*) &引用名=指針名,即將指針的類型當成類型*
1 #include<iostream>
2 using namespace std;
3 int main(){
4     int a=10;
5     int *ptr=&a;
6     int *&new_ptr=ptr;
7     cout<<&ptr<<" "<<&new_ptr<<endl;
8     return 0; 
9 }

技術分享

二、引用的應用

A.引用作為函數的參數

 1 #include<iostream>
 2 using namespace std;
 3 void swap(int &a,int &b){//引用作為函數的參數
 4     int temp=a;
 5     a=b;
 6     b=temp; 
 7 }
 8 int main(){
 9     int value1=10,value2=20;
10     cout<<"----------------------交換前----------------------------"<<endl;
11     cout<<"value1的值為:"<<value1<<endl; 
12     cout<<"value2的值為:"<<value2<<endl;
13     swap(value1,value2); 
14     cout<<"----------------------交換後----------------------------"<<endl;
15     cout<<"value1的值為:"<<value1<<endl; 
16     cout<<"value2的值為:"<<value2<<endl;
17     return 0;
18 }

技術分享

特別註意:

1.當用引用作為函數的參數時,其效果和用指針作為函數參數的效果相當。當調用函數時,函數中的形參就會被當成實參變量或對象的一個別名來使用,也就是說此時函數中對形參的各種操作實際上是對實參本身進行操作,而非簡單的將實參變量或對象的值拷貝給形參

2.通常函數調用時,系統采用值傳遞的方式將實參變量的值傳遞給函數的形參變量。此時,系統會在內存中開辟空間用來存儲形參變量,並將實參變量的值拷貝給形參變量,也就是說形參變量只是實參變量的副本而已;並且如果函數傳遞的是類的對象,系統還會調用類中的拷貝構造函數來構造形參對象。而使用引用作為函數的形參時,由於此時形參只是要傳遞給函數的實參變量或對象的別名而非副本,故系統不會耗費時間來在內存中開辟空間來存儲形參。因此如果參數傳遞的數據較大時,建議使用引用作為函數的形參,這樣會提高函數的時間效率,並節省內存空間

3.使用指針作為函數的形參雖然達到的效果和使用引用一樣,但當調用函數時仍需要為形參指針變量在內存中分配空間,而引用則不需要這樣,故在C++中推薦使用引用而非指針作為函數的參數

4.如果在編程過程中既希望通過讓引用作為函數的參數來提高函數的編程效率,又希望保護傳遞的參數使其在函數中不被改變,則此時應當使用對常量的引用作為函數的參數。

5.數組的引用作為函數的參數:C++的數組類型是帶有長度信息的,引用傳遞時如果指明的是數組則必須指定數組的長度

 1 #include<iostream>
 2 using namespace std;
 3 void func(int(&a)[5]){//數組引用作為函數的參數,必須指明數組的長度 
 4 //函數體 
 5 }
 6 int main(){
 7     int number[5]={0,1,2,3,4};
 8     func(number); 
 9     return 0; 
10  }

B.常引用

語法:const 類型 &引用名=目標變量名;

常引用不允許通過該引用對其所綁定的變量或對象進行修改

1 #include<iostream>
2 using namespace std;
3 int main(){
4     int a=10;
5     const int &new_a=a;
6     new_a=11;//錯誤!不允許通過常引用對其所綁定的變量或對象進行修改 
7     return 0;
8 }

技術分享

特別註意:

先看下面的例子

 1 #include<iostream>
 2 #include<string> 
 3 using namespace std;
 4 string func1(){
 5     string temp="This is func1";
 6     return temp;
 7 }
 8 void func2(string &str){
 9     cout<<str<<endl;
10 }
11 int main(){
12     func2(func1());
13     func2("Tomwenxing");
14     return 0;
15 }

運行上面的程序編譯器會報錯

技術分享

這是由於func1()和“Tomwenxing”都會在系統中產生一個臨時對象(string對象)來存儲它們,而在C++中所有的臨時對象都是const類型的,而上面的程序試圖將const對象賦值給非const對象,這必然會使程序報錯。如果在函數func2的參數前添加const,則程序便可正常運行了

 1 #include<iostream>
 2 #include<string> 
 3 using namespace std;
 4 string func1(){
 5     string temp="This is func1";
 6     return temp;
 7 }
 8 void func2(const string &str){
 9     cout<<str<<endl;
10 }
11 int main(){
12     func2(func1());
13     func2("Tomwenxing");
14     return 0;
15 }

技術分享

C.引用作為函數的返回值

語法:類型 &函數名(形參列表){ 函數體 }

特別註意:

1.引用作為函數的返回值時,必須在定義函數時在函數名前將&

2.用引用作函數的返回值的最大的好處是在內存中不產生返回值的副本

 1 //代碼來源:RUNOOB
 2 #include<iostream>
 3 using namespace std;
 4 float temp;
 5 float fn1(float r){
 6     temp=r*r*3.14;
 7     return temp;
 8 } 
 9 float &fn2(float r){ //&說明返回的是temp的引用,換句話說就是返回temp本身
10     temp=r*r*3.14;
11     return temp;
12 }
13 int main(){
14     float a=fn1(5.0); //case 1:返回值
15     //float &b=fn1(5.0); //case 2:用函數的返回值作為引用的初始化值 [Error] invalid initialization of non-const reference of type ‘float&‘ from an rvalue of type ‘float‘
//(有些編譯器可以成功編譯該語句,但會給出一個warning)
16 float c=fn2(5.0);//case 3:返回引用 17 float &d=fn2(5.0);//case 4:用函數返回的引用作為新引用的初始化值
18 cout<<a<<endl;//78.5 19 //cout<<b<<endl;//78.5 20 cout<<c<<endl;//78.5 21 cout<<d<<endl;//78.5 22 return 0; 23 }

上例中4個case的說明解釋:

case 1:用返回值方式調用函數(如下圖,圖片來源:伯樂在線):

技術分享

返回全局變量temp的值時,C++會在內存中創建臨時變量並將temp的值拷貝給該臨時變量。當返回到主函數main後,賦值語句a=fn1(5.0)會把臨時變量的值再拷貝給變量a

case 2:用函數的返回值初始化引用的方式調用函數(如下圖,圖片來源:伯樂在線)

技術分享

這種情況下,函數fn1()是以值方式返回到,返回時,首先拷貝temp的值給臨時變量。返回到主函數後,用臨時變量來初始化引用變量b,使得b成為該臨時變量到的別名。由於臨時變量的作用域短暫(在C++標準中,臨時變量或對象的生命周期在一個完整的語句表達式結束後便宣告結束,也就是在語句float &b=fn1(5.0);之後) ,所以b面臨無效的危險,很有可能以後的值是個無法確定的值。

如果真的希望用函數的返回值來初始化一個引用,應當先創建一個變量,將函數的返回值賦給這個變量,然後再用該變量來初始化引用:

1 int x=fn1(5.0);
2 int &b=x;

case 3:用返回引用的方式調用函數(如下圖,圖片來源:伯樂在線)

技術分享

這種情況下,函數fn2()的返回值不產生副本,而是直接將變量temp返回給主函數,即主函數的賦值語句中的左值是直接從變量temp中拷貝而來(也就是說c只是變量temp的一個拷貝而非別名) ,這樣就避免了臨時變量的產生。尤其當變量temp是一個用戶自定義的類的對象時,這樣還避免了調用類中的拷貝構造函數在內存中創建臨時對象的過程,提高了程序的時間和空間的使用效率。

case 4:用函數返回的引用作為新引用的初始化值的方式來調用函數(如下圖,圖片來源:伯樂在線)

技術分享

這種情況下,函數fn2()的返回值不產生副本,而是直接將變量temp返回給主函數。在主函數中,一個引用聲明d用該返回值初始化,也就是說此時d成為變量temp的別名。由於temp是全局變量,所以在d的有效期內temp始終保持有效,故這種做法是安全的。

3.不能返回局部變量的引用。如上面的例子,如果temp是局部變量,那麽它會在函數返回後被銷毀,此時對temp的引用就會成為“無所指”的引用,程序會進入未知狀態。

4.不能返回函數內部通過new分配的內存的引用。雖然不存在局部變量的被動銷毀問題,但如果被返回的函數的引用只是作為一個臨時變量出現,而沒有將其賦值給一個實際的變量,那麽就可能造成這個引用所指向的空間(有new分配)無法釋放的情況(由於沒有具體的變量名,故無法用delete手動釋放該內存),從而造成內存泄漏。因此應當避免這種情況的發生

5當返回類成員的引用時,最好是const引用。這樣可以避免在無意的情況下破壞該類的成員。

6.可以用函數返回的引用作為賦值表達式中的左值

 1 #include<iostream>
 2 using namespace std;
 3 int value[10];
 4 int error=-1;
 5 int &func(int n){
 6     if(n>=0&&n<=9)
 7         return value[n];//返回的引用所綁定的變量一定是全局變量,不能是函數中定義的局部變量 
 8     else
 9         return error;
10 }
11 
12 int main(){
13     func(0)=10;
14     func(4)=12;
15     cout<<value[0]<<endl;
16     cout<<value[4]<<endl;
17     return 0; 
18 }

技術分享

D.用引用實現多態

在C++中,引用是除了指針外另一個可以產生多態效果的手段。也就是說一個基類的引用可以用來綁定其派生類的實例

class Father;//基類(父類)
class Son:public Father{.....}//Son是Father的派生類
Son son;//son是類Son的一個實例
Father &ptr=son;//用派生類的對象初始化基類對象的使用

特別註意:

ptr只能用來訪問派生類對象中從基類繼承下來的成員如果基類(類Father)中定義的有虛函數,那麽就可以通過在派生類(類Son)中重寫這個虛函數來實現類的多態。

三、總結

1.在引用的使用中,單純給某個變量去別名是毫無意義的,引用的目的主要用於在函數參數的傳遞中,解決大塊數據或對象的傳遞效率和空間不如意的問題

2.用引用傳遞函數的參數,能保證參數在傳遞的過程中不產生副本,從而提高傳遞效率,同時通過const的使用,還可以保證參數在傳遞過程中的安全性

3.引用本身是目標變量或對象的別名,對引用的操作本質上就是對目標變量或對象的操作。因此能使用引用時盡量使用引用而非指針

C++:引用的簡單理解