天天看點

仔細差別指針和引用

先寫出兩者的3點差別,後面再具體分析:

  1. 當引用被建立時,它必須被初始化,而指針則可以在任何時候被初始化
  2. 一旦一個引用被初始化為指向一個對象,它就不能被改變為對另一個對象的引用,而指針可以在任何時候指向另一個對象
  3. 不可能有NULL引用,必須確定引用是和一塊合法的存儲單元關聯,而指針可以初始化為NULL

從概念上講,指針(pointers)從本質上講就是存放變量位址的一個變量,在邏輯上是獨立的,它可以被改變,包括其所指向的位址的改變和其指向的位址中所存放的資料的改變,這點後面再詳細叙述。

而引用(references)是一個别名,它在邏輯上不是獨立的,它的存在具有依附性,是以引用必須在一開始就被初始化,而且其引用的對象在其整個生命周期中是不能被改變的,自始至終隻能依附于同一個變量。

string s1("sunburn");
	string s2("saltwater");

	string &rs = s1;        //引用,rs代表s1
	string *ps = &s1;       //指針,ps指向s1

	rs = s2;                //rs仍然是代表s1,但是s1的值現在變成了"saltwater"
	                        //rs一直依附于s1,rs的值就是s1的值

	*ps = "owl city";       //修改ps指向的位址中所存放的資料,也就是s1變成了"owl city"
							//rs依附于s1,那麼這裡rs也會變成"owl city"

	ps = &s2;               //修改ps所指向的位址,ps現在指向s2,s1沒有變化
           

從上面可以得知,指針可以被重新指派,指向另一個對象,引用卻總是代表它最初獲得的那個對象。

一般而言,當你需要不指向任何對象時,或是需要在不同時間指向不同對象時,你就應該采用指針,前一種情況你可以将指針設為NULL,後一種情況你可以改變指針所指對象(位址)。而當你确定總是會代表某個對象,而且一旦代表了該對象就不再改變,就應該選用引用。

有一點最基本的,那就是沒有所謂的空引用(NULL references),一個引用必須總是代表一個對象,是以如果你有一個變量,其目的是用來指向(代表)另一個對象,但是也可能它不指向任何對象,那麼你就應該使用指針,如果這個變量必須總是代表一個對象,并且不允許這個變量為NULL,那麼就應該選用引用。說白了,就是C++允許空指針,但不允許空引用。

引用一定得代表某個對象,是以要求引用必須有初值,但是對指針則沒有這樣的要求

string s1("sunburn");
	string s2("saltwater");

	string &rs = s1;        //初始化
	string &rs;             //未初始化,error

	string *ps;             //未初始化的指針,有效,但是風險高,
	                        //實際上,在應用中是不允許的
           

沒有所謂的空引用,這就意味着當使用引用傳遞形參參數時,則不需要測試其參數的有效性,相反如果使用指針,通常就得測試它是否為NULL。

下面看看在C++中,指針和引用在函數參數傳遞中的差別:

說到指針傳遞,以前在C中經常同值傳遞進行差別,其實指針傳遞本質上也是值傳遞的方式,隻不過傳遞的是一個位址值,值傳遞過程中,被調函數的形參作為被調函數的局部變量處理,即在棧中開辟記憶體空間存放由主調函數放進來的實參的值,進而成為了實參的一個副本。值傳遞的特點是被調函數對形參的任何操作都是作為局部變量處理的,其值都存放在棧中,随着被調函數的結束而消亡,不會影響主調函數中實參變量的值,指針傳遞過程中的實參變量指的則是實參指針,指針傳遞的目的是通過被調函數去修改實參變量,那麼被調函數内部的操作肯定是解指針操作,也就是透過傳過來的指針的副本來操作其指向的變量(實參變量)。

而在引用傳遞過程中,被調函數的形式參數自然也作為局部變量在棧中開辟了記憶體空間,但是這時存放的是由主調函數放進來的實參變量的位址,然後被調函數對形參的任何操作都被處理成間接尋址,通過棧中存放的位址通路主調函數中的實參變量。這個和前面的說到的指針傳遞就相同了,都是通過棧中的位址通路主調函數的實參變量,隻不過引用傳遞則不需要指針的解引用操作,直接對形參的操作都會被内部解析為通過位址通路實參變量,進而去影響主調函數中的實參變量。但從這點來看,引用傳遞其實也是指針傳遞,隻不過這一切實作都隐藏在内部,由編譯器完成。

我們對比看下指針傳遞和引用傳遞的反彙編代碼:

仔細差別指針和引用

上面這兩個簡單的被調函數都是實作相同的功能,代碼的實作上存在本質差別,一個是指針傳遞一個是引用傳遞,但是内部的操作,從其反彙編代碼來看(紅色框框内),兩者的彙編代碼是一模一樣的。但是從代碼的實作上來看,引用傳遞顯得更為高效和簡潔,無需檢查傳入參數的有效性,并且不會帶入指針這個易錯量。

注意的是上面說的一模一樣是針對指針傳遞是以透過指針變量來操作實參變量的目的。當然,如果單一的操作指針變量,沒有解引用操作,也就是隻改變被調函數中的指針位址,那麼它将影響不到主調函數中的相關變量。

str1 = str2;    //這裡指針變量作為局部變量,不會影響到主調函數的實參變量
           

當然,如果實際上應用指針傳遞形參的話,是不會這樣去操作的。

其實二者都是位址的概念。指針指向一塊記憶體,它的内容是所指記憶體的位址,而引用則是某塊記憶體的别名,既然是别名,那麼在參數傳遞時,引用并不會産生對象的副本,對象無需複制,效率更高,以及在函數的傳回值上,傳回一個對象的引用有時也是必須的,尤其在某些運算符的重載上。

參考資料:《More Effective C++》

繼續閱讀