左值引用VS右值引用
左值引用對於一般的C++程序員再熟悉不過,但對於右值引用(C++0X新特性),就稍微有點不知所雲
左值VS右值
在定義變量的時候,經常會用到左值和右值,比如:
int a = 1;
int b = a + 1;
上面這段代碼,a先作為左值,在作為右值。在作為右值的時候,是相當於(原理上等同,但不一定對)
int tmp(a + 1);
int b(tmp);
中間會先建立一個臨時遍歷,然后在把臨時遍歷賦值給b。對於數,只能作為左值,而變量名,即可以作為左值又可以作為右值。(作為左值的時候相當於用該變量的地址,作為右值的時候則相當於使用該變量的內容,這里對於類對象也成立)。
左值引用
用法:Type & 左值引用名 = 左值表達式;
注意點:聲明時必須初始化,初始化之后無法在改變;對別名的一切操作都等價於對原來變量的操作。
左值引用在傳遞參數的時候,和指針特別類似,如以下代碼:
int val = 10;
void fun(int * ptr){ cout<<*ptr<<endl; *ptr += 1; }
void fun(int & value){ cout<<value<<endl; a += 1; }
fun(val);
fun(&val);
上面的兩種fun()的調用方法,在函數體內的修改都可以引起val的改變。(簡單的理解,不一定完全正確:左值引用和指針都相當於是通過地址來訪問具體的值,因此可以修改)
const修飾左值引用
int & r = val + 1;
//此句不合法,因為右值無法賦值給左值引用
const int& r = val + 1;
//合法
解釋:資料說c++中臨時變量默認const屬性,所以只能傳給const的引用。規定右值不能綁定到非 const 限定的左值引用。
異常對象另說;如果是右值引用或const左值引用綁定的,那生存期延長為引用;否則到完全表達式結束銷毀。還有默認初始化數組元素時延長到數組初始化結束。(摘抄自其網頁)
右值引用
在上面的代碼中,我們無法建立 int &rb = a + 1;
這樣的語法,因為a + 1 此時是作為一個右值來使用的,我們無法把一個右值賦值給一個左值引用。(也就是左值引用相當於把一個變量的地址付給另一個變量,這兩個變量可以訪問同一個內存,右值僅僅是一個數,而非內存中的某塊地址,因此無法把右值復制給左值引用)。
聲明方法:Type && 右值引用名 = 右值表達式;
std::move()的用法
可以直接把左值或者右值轉換成右值引用,使用方法:
int && rrval = std::move(val);
但是這里需要注意:在調用完std::move之后,不能再使用val,只能使用 rrval,這一點用於基本類型可能沒什么直接影響,當應用到類函數的時候,用好std::move 可以減少構造函數數的次數,具體的使用參考下面的std::move比較好的理解
下面說一下自己的理解:
首先是由於STL里面默認的庫已經支持右值引用,也有所謂的移動構造函數如下面的形式A (A&& a){}
這里不能使用const A&& a,因為需要改變a。移動構造函數主要的用途是,當你不需要在使用一個變量的時候,可以直接通過該構造函數來實現把該變量的數據轉換到另一個變量中,省去調用默認的賦值構造或者拷貝構造函數帶來額外的開銷,如string類在賦值或者拷貝構造函數中會聲明char數組來存放數據,然后把原string中的 char 數組被析構函數釋放,如果a是一個臨時變量,則上面的拷貝,析構就是多余的,完全可以把臨時變量a中的數據直接 “轉移” 到新的變量下面即可。 如下面的程序(摘抄自網頁):
#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
std::string str = "Hello";
std::vector<std::string> v;
//調用常規的拷貝構造函數,新建字符數組,拷貝數據
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n";
//調用移動構造函數,掏空str,掏空后,最好不要使用str
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n";
std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n";
}
總結:
其實這個右值引用主要的用處就是在於配合std::move來實現 “轉移語句” `A();`//默認構造函數 `A(const A& a);`//拷貝構造函數 `oprator=(const A& a);`//賦值構造函數 `A(A&& a);`//移動構造函數 可以在移動構造函數中實現 把a的數據直接轉移到 新的變量b下面,而省去 申請聲明一個變量b, 把 copy a->b, 然后釋放 a 的空間。 比如,string str = “hello"; 現在str不在使用了,但需要聲明一個新的變量表示str, 此時就可以用 string newstr(str::move(str)); 調用移動構造函數,把str下面的內容全部轉移到 newstr下面,“掏空”str,這里需要注意掏空str之后,最好就不要在使用str了。(有點啰嗦了)
注釋: 賦值構造函數:A & operator = (const A& a); //這里返回A & 是為了進行連等 a1 = a2 = a3,當一個變量已經被定義之后,改變值的時候調用該函數 拷貝構造函數:A(const A&a); 直接在定義聲明一個對象的時候,依據另一個對象來構造。
參考鏈接:
左值右值定義:http://www.cnblogs.com/catch/p/3500678.html
右值表達式VS左值表達式:http://blog.sina.com.cn/s/blog_7fe1e77b01016okx.html
左值引用和右值引用:http://www.linuxidc.com/Linux/2015-02/114056.htm
右值引用:http://blog.csdn.net/zwvista/article/details/12306283
臨時對象不能綁定到非const左值引用上:http://blog.csdn.net/liuxialong/article/details/6539717
http://blog.csdn.net/feeltouch/article/details/9789731
std::move用法理解
std::move比較好的解釋:http://www.cnblogs.com/chezxiaoqiang/archive/2012/10/24/2736630.html
另外三篇關於std::move 的用法:
上:http://blog.csdn.net/yapian8/article/details/42341307
中:http://blog.csdn.net/yapian8/article/details/42341321
下:http://blog.csdn.net/yapian8/article/details/42341351