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