c/c++中的左值右值詳解
左值 (Lvalue)
定義:
- 表達式結(jié)束后依然存在的持久對象。
- 有名字、有持久性的表達式,它是既能夠出現(xiàn)在等號左邊,也能出現(xiàn)在等號右邊的變量。
右值 (Rvalue)
定義:
- 表達式結(jié)束后就不再存在的臨時對象。
- 臨時的、將亡的值。一般是不可尋址的常量,或在表達式求值過程中創(chuàng)建的無名臨時對象,短暫性的。
左值和右值主要的區(qū)別之一是左值可以被修改,而右值不能。
int number; number = 1
在這段代碼中 number 為左值 ,1 為右值。
// getValue()返回的臨時值是右值 int value = getValue();
引用的本質(zhì)是別名,可以通過引用改變變量的值,傳參時傳引用可以避免拷貝。
左值引用 (Lvalue Reference)
定義: 引用一個對象;
基本用法:
int number = 10; int& count = number; count++; // number = count = 11;
- number 是左值,10 是右值
- count 是左值引用,count 引用 number
void addCount(int& count) { count++; } int number = 10; addCount(number);
如果給“左值”引用直接賦“右值”,則會報錯,例:
int& number = 10; 報錯: 錯誤 C2440 “初始化”: 無法從“int”轉(zhuǎn)換為“int &”
const 左值引用不會修改指向值,因此可以指向右值
例如:
std::vector 中的方法 push_back // 如果沒有 const 那么使用 v.push_back(10) 這樣 10 的類型是右值,就無法通過編譯了 void push_back(const value_type& _Val) // 可以直接傳遞字面值 v.push_back(10) // 可以傳遞表達式結(jié)果 v.push_back(x+y)
右值引用 (Rvalue Reference)
定義:就是必須綁定到右值的引用,它可以指向右值,不能指向左值。C++11中右值引用可以實現(xiàn)“移動語義”,通過 && 獲得右值引用。
右值引用綁定右值
int&& number = 1;
錯誤:右值引用不能直接綁定左值
int count = 2; int&& number = count; 報錯: error C2440: “初始化”: 無法從“int”轉(zhuǎn)換為“int &&” message : 無法將左值綁定到右值引用
嘗試讓右值引用指向左值
int count = 2; // 使用 std::move 把左值強制轉(zhuǎn)換為右值 ,number = 2 int&& number1 = std::move(count); // 等同與 std::move 類型轉(zhuǎn)換 ,number = 2 int&& number2 = static_cast<int&&>(count); // count = number1 = number2 = 10 number1 = 10;
簡單練習(xí):
void test(int & o) {std::cout << "左值。" << std::endl;} void test(int && temp) {std::cout << "右值。" << std::endl;} int main(){ int a; int && b = 10; test(a); test(std::move(a)); test(b); } 結(jié)果: a 是一個具名變量,有固定的內(nèi)存地址是典型的左值。輸出:"左值。" std::move(a) 將左值 a 轉(zhuǎn)換為右值引用 返回類型是 int&&。輸出:"右值" 雖然 b 的類型是右值引用(int&&)但 b 本身是一個具名變量,可以取地址。 輸出:"左值"
結(jié)論:右值引用類型只是用于匹配右值,而并非表示一個右值。因此,盡量不要聲明右值引用類型的變量,而只在函數(shù)形參使用它以匹配右值。
淺拷貝(Shallow Copy)
淺拷貝只復(fù)制指向某個對象的指針,而不復(fù)制對象本身,新舊對象還是共享同一塊內(nèi)存(分支)。
- 淺拷貝是按位拷貝對象,它會創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。
- 如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型),拷貝的就是內(nèi)存地址 ,因此如果其中一個對象改變了這個地址,就會影響到另一個對象。
深拷貝(deep copy)
深拷貝會另外創(chuàng)造一個一模一樣的對象,新對象跟原對象不共享內(nèi)存,修改新對象不會改到原對象,是“值”而不是“引用”(不是分支)
- 拷貝第一層級的對象屬性或數(shù)組元素
- 遞歸拷貝所有層級的對象屬性和數(shù)組元素
- 深拷貝會拷貝所有的屬性,并拷貝屬性指向的動態(tài)分配的內(nèi)存。當(dāng)對象和它所引用的對象一起拷貝時即發(fā)生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大。
編寫代碼:
#include <iostream> class Vector { int num; int* a; public: // 構(gòu)造函數(shù) Vector(int n = 0) : num(n) { a = new int[num]; for (int i = 0; i < num; ++i) { a[i] = i + 1; // 初始化為1,2,3... } } // 析構(gòu)函數(shù) ~Vector() { delete[] a; } void ShallowCopy(Vector& v); void DeepCopy(Vector& v); // 打印數(shù)組內(nèi)容的輔助函數(shù) void print() const { std::cout << "num = " << num << ", array content: "; for (int i = 0; i < num; ++i) { std::cout << a[i] << " "; } std::cout << std::endl; } // 修改數(shù)組內(nèi)容的輔助函數(shù) void modify(int index, int value) { if (index >= 0 && index < num) { a[index] = value; } } }; // 淺拷貝實現(xiàn) void Vector::ShallowCopy(Vector& v) { this->num = v.num; this->a = v.a; } // 深拷貝實現(xiàn) void Vector::DeepCopy(Vector& v) { delete[] this->a; // 先釋放原有內(nèi)存 this->num = v.num; this->a = new int[num]; for (int i = 0; i < num; ++i) { a[i] = v.a[i]; } }
淺拷貝測試
int main() { // 測試淺拷貝 std::cout << "=== 測試淺拷貝 ===" << std::endl; { Vector v1(5); // 創(chuàng)建一個包含5個元素的向量 std::cout << "Original v1: "; v1.print(); Vector v2(3); // 創(chuàng)建另一個向量 std::cout << "Original v2: "; v2.print(); v2.ShallowCopy(v1); // 淺拷貝 std::cout << "After shallow copy, v2: "; v2.print(); // 修改v1,觀察v2是否變化 v1.modify(0, 100); std::cout << "After modifying v1, v1: "; v1.print(); std::cout << "After modifying v1, v2: "; v2.print(); // 這里會崩潰,因為v1和v2都試圖刪除同一塊內(nèi)存 std::cout << "Program will crash here due to double delete\n"; } } 程序輸出: === 測試淺拷貝 === Original v1: num = 5, array content: 1 2 3 4 5 Original v2: num = 3, array content: 1 2 3 After shallow copy, v2: num = 5, array content: 1 2 3 4 5 After modifying v1, v1: num = 5, array content: 100 2 3 4 5 After modifying v1, v2: num = 5, array content: 100 2 3 4 5 Program will crash here due to double delete 崩潰位置: // 析構(gòu)函數(shù) ~Vector() { delete[] a; }
深拷貝測試
int main() { // 測試深拷貝 std::cout << "\n=== 測試深拷貝 ===" << std::endl; { Vector v1(5); std::cout << "Original v1: "; v1.print(); Vector v2(3); std::cout << "Original v2: "; v2.print(); v2.DeepCopy(v1); // 深拷貝 std::cout << "After deep copy, v2: "; v2.print(); // 修改v1,觀察v2是否變化 v1.modify(0, 100); std::cout << "After modifying v1, v1: "; v1.print(); std::cout << "After modifying v1, v2: "; v2.print(); // 這里不會崩潰,因為每個對象管理自己的內(nèi)存 std::cout << "Program will exit normally\n"; } return 0; } === 測試深拷貝 === Original v1: num = 5, array content: 1 2 3 4 5 Original v2: num = 3, array content: 1 2 3 After deep copy, v2: num = 5, array content: 1 2 3 4 5 After modifying v1, v1: num = 5, array content: 100 2 3 4 5 After modifying v1, v2: num = 5, array content: 1 2 3 4 5 Program will exit normally
再探右值引用
使用 C++ 11 的右值引用,重載拷貝函數(shù),代碼修改為:???????
class Vector { int num; int* a; public: // 構(gòu)造函數(shù) Vector(int n = 0) : num(n) { a = new int[num]; for (int i = 0; i < num; ++i) { a[i] = i + 1; // 初始化為1,2,3... } } // 析構(gòu)函數(shù) ~Vector() { delete[] a; } //左值引用形參=>匹配左值 void Copy(Vector& tmp) { if (nullptr != this->a){ delete[] this->a; } this->num = tmp.num; this->a = tmp.a; // 防止tmp析構(gòu)時刪除內(nèi)存 tmp.a = nullptr; } //右值引用形參=>匹配右值 void Copy(Vector&& tmp) { this->num = tmp.num; this->a = tmp.a; } };
調(diào)用測試
int main() { // 測試左值引用版本 Vector v1(5); // v1: {1,2,3,4,5} Vector v2; v2.Copy(v1); // 深拷貝:v2獲得自己的內(nèi)存副本 // 測試右值引用版本 Vector v3; v3.Copy(Vector(3)); // 移動:直接竊取臨時對象的資源 return 0; }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C語言數(shù)據(jù)結(jié)構(gòu)之串插入操作
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之串插入操作的相關(guān)資料,希望通過本文能幫助到大家,讓大家實現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10C語言數(shù)據(jù)結(jié)構(gòu)之迷宮問題
這篇文章主要為大家詳細介紹了C語言數(shù)據(jù)結(jié)構(gòu)之迷宮問題,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03