您好,登錄后才能下訂單哦!
C++特性之引用 (Boolan)
本章內(nèi)容:
1 引用的不同用例
1.1 引用變量
1.2 引用數(shù)據(jù)成員
1.3 引用參數(shù)
1.4 引用作為返回值
1.5 使用引用還是指針
1.6 右值引用
1 引用
在C++中,引用是變量的別名。所有對引用的修改都會改變被引用的變量的值??蓪⒁卯?dāng)作隱式指針,這個指針沒有取變量地址和解除引用的麻煩。
也可以將引用當(dāng)作原始變量的另一種名稱??梢詣?chuàng)建單獨(dú)的引用變量,在類中使用引用數(shù)據(jù)成員,將引用作為函數(shù)和方法的參數(shù),也可以讓函數(shù)或者方法返回引用。
1.1 引用變量
引用變量在創(chuàng)建時必須初始化,如下:
int ival = 3; int &iRef = x;
賦值后,iRef就是ival的另一個名稱。使用iRef就是使用ival的當(dāng)前值。對iRef賦值會改變ival的值。
無法在類外面聲明一個引用而不初始化它:
int &emptyRef; //編譯出錯
不能創(chuàng)建對未命名值(例如一個整數(shù)字面值)的引用,除非這個引用是一個const值。在下面的示例中,unnamedRef1將無法編譯,因?yàn)檫@是一個針對常量的non-const引用。
這條語句意味著可以改變常量5的值,而這樣做沒有意義。由于unnamedRef2是一個const引用,因此可以運(yùn)行,不能寫成"unnamedRef2=7"。
int &unnamedRef1 = 5; //編譯出錯 const int &unnamedRef2 = 5; //正常運(yùn)行
(1) 修改引用
引用總是引用初始化的那個變量:引用一旦創(chuàng)建,就無法修改。這一規(guī)則導(dǎo)致了許多令人迷惑的語法。如果在聲明一個引用時用一個變量"賦值",那么這個引用就指向這個變量。
然而,如果在此后使用變量對引用賦值,被引用變量的值就變?yōu)橘x值變量的值。引用不會更新為指向這個變量。示例代碼如下:
int x = 3,y = 4; int &iRef = x; iRef = y;//Changes value of x to 4. Doesn't make iRef refer to y.
如果試圖在賦值時取y的地址,以繞過這一限制:
int x = 3,y = 4; int &iRef = x; iRef = &y; //編譯出錯
上面的代碼無法編譯。y的地址是一個指針,但iRef聲明為一個int的引用,而不是一個指針的引用。
如果將一個引用賦值給另一個引用時,只是修改了其指向的值,而不是修改所指向的引用變量。(在初始化引用之后無法改變引用所指的變量;而只能改變該變量的值。)
(2) 指針的引用和指向引用的指針
可以創(chuàng)建任何類型的引用,包括指針類型。下面給出一個指向int指針的引用例子:
int *pVal; int *&ptrRef = pVal; ptrRef = new int; *ptrRef = 5;
這一語法有一點(diǎn)奇怪:你可能不習(xí)慣看到*和&彼此相鄰。然而,該語義上很簡單:ptrRef是pVal的引用,pVal是一個指向int的指針。修改ptrRef會更改pVal。指針的引用很少見,但是在某些場合下很有用,在1.3節(jié)中會討論這一內(nèi)容。
注意:
(i)對引用取地址的結(jié)果與被引用變量取地址的結(jié)果是相同的。
(ii)無法聲明引用的引用,或者指向引用的指針。
1.2 引用數(shù)據(jù)成員
類的數(shù)據(jù)成員可以引用,但是如果不是指向其他變量,引用就無法存在。因此,必須在構(gòu)造函數(shù)初始化器(constructor initializer)中初始化引用數(shù)據(jù)成員,而不是在構(gòu)造函數(shù)體
內(nèi)。下面舉例說明:
class MyClass { public: MyClass(int &iRef):m_ref(iRef) {} private: int &m_ref; };
1.3 引用參數(shù)
C++程序員通常不會單獨(dú)使用引用變量或者引用數(shù)據(jù)成員。引用經(jīng)常用作函數(shù)或者方法的參數(shù)。默認(rèn)的參數(shù)傳遞機(jī)制是值傳遞:函數(shù)接收參數(shù)的副本。修改這些副本時,原始的參數(shù)
保持不變。引用允許指定另一種向函數(shù)傳遞參數(shù)的語義:按引用傳遞。當(dāng)使用引用參數(shù)時,函數(shù)將引用作為參數(shù)。如果引用被修改,最初的參數(shù)變量也會修改。下面給出交換兩個數(shù)的例子來說明:
void swap(int &first, int &second) { int temp = first; first = second; second = temp; }
可以采用下面的方式調(diào)用這個函數(shù):
int x = 5, y = 6; swap(x, y);
當(dāng)使用x和y做參數(shù)調(diào)用函數(shù)swap()時,first參數(shù)初始化為x的引用,second參數(shù)初始化為y的引用。當(dāng)swap()修改first和second時,x和y實(shí)際上也被修改了。
就像無法使用常量初始化普通引用變量一樣,不能將常量作為參數(shù)傳遞給按引用傳遞參數(shù)的函數(shù):
swap(3, 4); //編譯出錯
(1) 指針轉(zhuǎn)換為引用
某個函數(shù)或者方法需要一個引用做參數(shù),而你擁有一個指向被傳遞值的指針,這是一種常見的困境。在此情況下,可以對指針解除引用(dereferencing),將指針"轉(zhuǎn)換"為引用。
這一行為會給出指針?biāo)傅闹担S后編譯器用這個值初始化引用參數(shù)。例如,可以這樣調(diào)用swap():
int x = 5, y = 6; int *px = &x; int *py = &y; swap(*px, *py);
(2) 按引用傳遞與按值傳遞
如果要修改參數(shù),并修改傳遞給函數(shù)或者方法的變量,就需要使用按引用傳遞。然而,按引用傳遞的用途并不局限于此。按引用傳遞不需要將參數(shù)副本復(fù)制到函數(shù),在有些情況下
會帶來兩面的好處:
(i)效率:復(fù)制較大的對象或者結(jié)構(gòu)需要較長的時間。按引用傳遞只是把指向?qū)ο蠡蛘呓Y(jié)構(gòu)的指針傳遞給函數(shù)。
(ii)正確性:并非所有對象都允許按值傳遞,即使允許按值傳遞的對象,也可能不支持正確的深度復(fù)制(deep copying)。(如果需要深度復(fù)制,動態(tài)分配內(nèi)存的對象必須提供自定
義復(fù)制構(gòu)造函數(shù)。)
如果要利用好這些好處,但不想修改原始對象,可將參數(shù)標(biāo)記為const,從而實(shí)現(xiàn)按常理引用傳遞參數(shù)。按引用傳遞的這些優(yōu)點(diǎn)意味著,只有在參數(shù)是簡單的內(nèi)建類型(int或double
),且不需要修改參數(shù)的情況下才應(yīng)該使用按值傳遞。其他情況下都應(yīng)該按引用傳遞。
1.4 引用作為返回值
可以讓函數(shù)或者方法返回一個引用,這樣做的主要作用是提高效率。返回對象的引用不是返回整個對象可以避免不必要的復(fù)制。當(dāng)然,只有涉及的對象在函數(shù)終止之后仍然存在的
情況才能使用這一技巧。(如果變量的作用域局限于函數(shù)或者方法,例如:堆棧中分配的變量,在函數(shù)結(jié)束時會被銷毀。這個時候絕對不能返回這個變量的引用。)
返回引用的另一個原因是希望將返回值直接賦為左值(lvalue)(賦值語句在左邊)。一些重載的運(yùn)算符通常會返回引用。
1.5 使用引用還是指針
在C++中,引用有可能被認(rèn)為是多余的:幾乎所有使用引用可以完成的任務(wù)都可以用指針來代替完成。例如,可以這樣編寫swap()函數(shù):
void swap(int *first, int *second) { int temp = *first; *first = *second; *second = temp; }
然而,這些代碼不如使用引用版本那么清晰:引用可以使程序整潔并易于理解。此外,引用比指針安全:不可能存在無效的引用,也不需要顯式地解除引用,因此不會遇到像指針
那樣的解除引用問題。
大多數(shù)情況下,應(yīng)該使用引用而不是指針。對象的引用甚至可以像指向?qū)ο蟮闹羔樐菢又С侄鄳B(tài)性。只有在需要改變所指地址時,才需要使用指針,因?yàn)闊o法改變引用所致的對像
。例如,動態(tài)分配內(nèi)存時,應(yīng)該將結(jié)果存儲在指針而不是引用中。需要使用指針的第二種情況是可選參數(shù)。例如,指針參數(shù)可以定義為帶默認(rèn)值nullptr的可選參數(shù),而引用參數(shù)不能這樣定義。
還有一種方法可以判斷使用指針還是引用作為參數(shù)和返回類型:考慮誰擁有內(nèi)存。如果接受變量的代碼負(fù)責(zé)釋放相關(guān)對象的內(nèi)存,必須使用指向?qū)ο蟮闹羔?,最好是智能指針,這
是傳遞擁有權(quán)的推薦方式。如果接受變量的代碼不需要釋放內(nèi)存,那么應(yīng)該使用引用。
注意:如果不需要改變所指的地址,就應(yīng)該使用引用而不是指針。
1.6 右值引用
在C++中,左值(lvalue)是可以獲取其地址的一個量,例如一個有名稱的變量。由于經(jīng)常出現(xiàn)在賦值語句的左邊,因此稱其為左值。另一方面,所有不是左值的量都是右值(rvalue)
,例如常量值,臨時對象或者臨時值。通常右值位于賦值運(yùn)算符的右邊。
右值引用是一個對右值(rvalue)的引用。特別地,這是一個當(dāng)右值是臨時對象時使用的概念。右值引用的目的是提供在涉及臨時對象時可以選用的特定方法。由于知道臨時對象會
被銷毀,通過右值引用,某些涉及復(fù)制大量值的操作可以通過簡單的復(fù)制指向這些值的指針來實(shí)現(xiàn)。
函數(shù)可以將&&作為參數(shù)說明的一部分(例如 type&&name),來指定右值引用參數(shù)。通常,臨時對象被當(dāng)作const type&,但當(dāng)函數(shù)重載使用了右值引用時,可以解析臨時對象,
用于該重載。下面的示例說明了這一點(diǎn)。代碼首先定義了兩個incr()函數(shù),一個接受左值引用;另一個接受右值引用:
// Increment value using lvalue reference parameter. void incr(int &value) { cout << "increment with lvalue reference" << endl; ++value; } // Increment value using rvalue reference parameter. void incr(int &&value) { cout << "increment with rvalue reference" << endl; ++value; }
可以使具有名稱的變量作為參數(shù)調(diào)用incr()函數(shù)。于是a是一個具有名稱的變量,因此調(diào)用接受左值引用的incr()函數(shù)。調(diào)用完incr()后,a的值將是11。
int a = 10, b = 20; incr(a);//調(diào)用incr(int &value);
還可以用表達(dá)式作為參數(shù)來調(diào)用inrc()函數(shù)。此時無法使用接受左值引用作為參數(shù)的incr()函數(shù),因?yàn)楸磉_(dá)式a+b的結(jié)果是臨時的,這不是一個左值。在此情況下,會調(diào)用右值引用
版本。由于參數(shù)是一個臨時值,當(dāng)incr()函數(shù)調(diào)用結(jié)束后,會丟失這個增加的值。
incr(a + b); //將調(diào)用incr(int &&value);
字面量也可以作inrc()調(diào)用的參數(shù),此時同樣會調(diào)用右值引用版本,因?yàn)樽置媪坎荒茏鳛樽笾怠?/span>
incr(3); //將調(diào)用incr(int &&value);
如果刪除接受左值引用的incr()函數(shù),使用名稱的變量調(diào)用incr(),例如:incr(b),此時會導(dǎo)致編譯錯誤,因?yàn)橛抑狄脜?shù)(int &&value)永遠(yuǎn)不會與左值(b)綁定。如下所示可
以使用std::move()將左值轉(zhuǎn)換為右值,強(qiáng)迫編譯器調(diào)用incr()的右值版本。當(dāng)incr()調(diào)用結(jié)束后,b的值為21。
incr(std::move(b)); //將調(diào)用incr(int &&value);
右值引用并不局限于函數(shù)的參數(shù)??梢月暶饔抑狄妙愋偷淖兞?,并對其賦值,盡管這種用法并不常見。查下看如下代碼:
int &i = 2;//invalid:reference to a constant int a = 2, b = 3; int &j = a + b;//invalid:reference to a temporary
使用右值引用后,下面的代碼完全合法:
int &&i = 2; int a = 2, b = 3; int &&j = a + b;
前面示例中單獨(dú)使用右值引用的情況很少見。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。