C++特性之引用 (Boolan)程序員
本章內容:安全
1 引用的不一樣用例ide
1.1 引用變量函數
1.2 引用數據成員spa
1.3 引用參數指針
1.4 引用做爲返回值對象
1.5 使用引用仍是指針內存
1.6 右值引用ci
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;
前面示例中單獨使用右值引用的狀況不多見。