C++:引用的簡單理解

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

1、什麼是引用

引用,顧名思義是某一個變量或對象的別名,對引用的操做與對其所綁定的變量或對象的操做徹底等價編程

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

特別注意:數組

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.聲明引用的同時必須對其初始化,不然系統會報錯spa

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

4.引用至關於變量或對象的別名,所以不能再將已有的引用名做爲其餘變量或對象的名字或別名3d

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.對數組的引用code

語法:類型 (&引用名)[數組中元素數量]=數組名;
 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 }

 

2、引用的應用

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)中重寫這個虛函數來實現類的多態。

 

3、總結

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

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

3.引用自己是目標變量或對象的別名,對引用的操做本質上就是對目標變量或對象的操做。所以能使用引用時儘可能使用引用而非指針

相關文章
相關標籤/搜索