左值與右值
左值與右值的概念
可以被取地址的值為左值(left value,簡稱lvalue),否則為右值(right value,簡稱rvalue)。
常見的左值、右值例子:
// >>>>>>> 左值
// a, p, str, str[0]等均為左值
int a = 10;
int* p = &a;
string str("hello");
str[0]// >>>>>>> 右值
// 匿名對象,臨時表達式,常量10、“world”,均為右值
string("world")
x + y
10
初步認識左值和右值時,可以理解記憶為左值存儲在內存中,具有“身份”,生命周期長。右值存儲在寄存器中,大多為臨時變量,生命周期短。
但實際上,不完全是這樣:可能存在被編譯器優化為存儲于寄存器的左值(臨時變量),也有占用空間較大,需要存儲于內存的臨時副本(如復雜匿名對象)。初步認識時可以幫助理解move()
將左值轉換為右值的行為,類似匯編中的mov命令。
右值引用
語法為使用&&
,例如:
int&& a;
char&& b;
特性:
- 左值引用不能直接引用右值,除非加
const
限定;右值引用也不能直接引用左值,需要使用move()處理左值。 - 右值引用本身是一個左值。
移動構造
語法規則
class class_name {
public:class_name(class_name&& t) {::swap(t, t.array); // 交換資源}private:int* array;
}
意義
右值引用的概念與移動構造等特性密切相關,C++11提供了一種提高程序效率的方式,通過交換臨時資源的指針代替拷貝。
對于下面這一段程序:
string func() {string tmp("1111111111111");return tmp;
}int main() {string str = func();...
}
取決于編譯器的不同優化方式,C++98標準下,實際的程序會有三種行為:
- 在func內調用構造函數得到tmp,傳值返回時調用構造,得到臨時對象,然后拷貝賦值給str;
- 省略臨時對象的構造,直接將tmp拷貝賦值給str;
- 省略tmp的構造,直接用"1111111111111"構造str;
移動構造出現后就能夠替代拷貝構造,降低拷貝時的資源消耗。因為對于整個程序來說,tmp的存在只是暫時的【實際會被視為將亡值】,其資源被轉移給str也無所謂,所以不用進行拷貝,直接交換資源,就能達成相同的效果。
引用折疊
基本規則
類似“&”與運算,要右值引用的右值引用最終才是右值引用。
typedef int& lref
typedef int&& rref
lref& a -> a的類型為int&
lref&& b -> b的類型為int&
rref& c -> c的類型為int&
rref&& d -> d的類型為int&&
TIP: 不能直接寫int& &&
或int& &
,即不能直接對引用再做引用,必須通過typedef
自定義類型才可以,否則會語法報錯。
完美引用
借用“引用折疊”的規則,可以統一模版的寫法,使得最終的類型完全由實例化時的類型決定。萬能模版寫法:
tempate<class T>
void func(T&& a) {...
}func<int&> f1(num1) // 最終a要求為一個左值,因此num1需要是左值
func<int&&> f2(num2) // 最終a要求為一個右值,因此num2需要是右值
完美轉發
forward<typename>(arg);
為避免右值被右值引用后變為左值,調用該函數,可以保持參數arg
的原始屬性。
例子:
void overload(int&& rv) { std::cout << "rv " std::endl; }
void overload(int& lv) { std::cout << "lv" std::endl; }void func(int&& v) {overload(v);overload(std::forwad<int>(v));
}
- 當調用
func
時輸入左值,將會得到輸出:lv lv - 當調用func時輸入右值,將會輸出:lv rv
意義
完美引用結合完美轉發,可以運用在模版的嵌套場景,可以有效避免代碼冗余,同時提高代碼效率。
Lambda表達式
本質
lambda表達式的本質是一個匿名函數對象,可以直接定義在函數內部。
語法
// [capture list] (parameters) -> retrun type (function body)int main() {auto test1 = [](int a) -> void {cout << a << endl;return 0;}test1(1);return 0;
}
- 捕捉列表(capture list)不能省略
- 如果參數為空,則可以省略
- 即使有返回值,返回值類型也可以省略,將會由返回對象自動推導
- 函數體不能省略(必然的)