C++?左值、右值、左值引用、右值引用的用途及區(qū)別
1、左值與右值
左值和右值是表達式的屬性,核心區(qū)別在于:能否取地址、是否有持久的存儲。
1.1 左值:有名字、能取地址、可被修改(通常)
左值是 “可以放在賦值號左邊” 的表達式(但并非絕對,如 const 左值不能被修改),它有明確的內存地址,生命周期較長(如變量)。
int a = 10; // a 是左值(有名字、能取地址) int* p = &a; // 合法:左值可以取地址 const int b = 20; // b 是 const 左值(有名字、能取地址,但不能修改) // b = 30; // 錯誤:const 左值不可修改 int arr[5]; arr[0] = 1; // arr[0] 是左值(數組元素有地址)
1.2 右值:無名字、不能取地址、臨時存在
右值是 “只能放在賦值號右邊” 的表達式,通常是臨時結果(如字面量、表達式計算結果),沒有持久的內存地址,生命周期短暫(表達式結束后銷毀)。
int a = 10; int b = 20; // 以下都是右值 100; // 字面量(無名字,不能取地址) a + b; // 表達式結果(臨時值,無名字) func(); // 函數返回值(非引用類型時,是臨時值)
關鍵特征:
- 右值不能被取地址:
&(a + b)會編譯報錯(無法對臨時值取地址)。 - 右值是 “消耗品”:使用后就會銷毀(除非被保存到左值中)。
右值的細分
C++11 后,右值又分為兩種,但日常使用中無需嚴格區(qū)分,知道它們都是右值即可:
- 純右值(Prvalue):字面量(如
10)、表達式結果(如a + b)、非引用返回的函數結果。 - 將亡值(Xvalue):通過
std::move轉換后的左值(本質是 “即將被銷毀的左值”,可被移動)。
2、左值引用:綁定左值的 “別名”
左值引用是最常用的引用類型,用 & 表示,只能綁定左值,本質是給左值起一個 “別名”,操作引用等價于操作原對象。
2.1 基本用法
int a = 10; int& ref_a = a; // 正確:左值引用綁定左值(ref_a 是 a 的別名) ref_a = 20; // 等價于 a = 20,a 現(xiàn)在是 20
2.2 左值引用的限制
不能綁定右值
// int& ref = 10; // 錯誤:左值引用不能綁定右值(10 是右值)
const 左值引用是特例:可以綁定右值(臨時延長右值的生命周期):
const int& ref = 10; // 正確:const 左值引用可綁定右值 // 原理:編譯器會生成臨時變量存儲 10,ref 綁定這個臨時變量(臨時變量生命周期被延長至 ref 相同)
2.3 左值引用的用途
避免函數傳參時的拷貝(如傳遞大對象 vector):
void func(const vector<int>& v) { ... } // 傳引用,無拷貝允許函數修改外部變量(非 const 引用):
void increment(int& x) { x++; }
int a = 5;
increment(a); // a 變?yōu)?63、右值引用:綁定右值的 “專屬引用”
右值引用是 C++11 新增的引用類型,用 && 表示,專門綁定右值,目的是 “利用右值的臨時特性” 實現(xiàn)移動語義(避免不必要的拷貝)。
3.1 基本用法
int&& ref1 = 10; // 正確:右值引用綁定純右值(10 是右值) int a = 10, b = 20; int&& ref2 = a + b; // 正確:右值引用綁定表達式結果(右值)
3.2 右值引用的限制
不能直接綁定左值:
int a = 10; // int&& ref = a; // 錯誤:右值引用不能直接綁定左值
但可以通過 std::move 將左值 “強制轉換” 為右值引用(本質是告訴編譯器:“這個左值可以被當作右值處理,資源可以被轉移”)
int a = 10; int&& ref = std::move(a); // 正確:std::move 將 a 轉為右值引用
注意:std::move 不會移動任何數據,只是 “標記” 左值為 “可被移動” 的右值,本身是編譯期操作,無運行時開銷。
3.3 右值引用的核心用途:移動語義
右值引用的最大價值是實現(xiàn)移動語義—— 對于臨時對象(右值),不再進行昂貴的拷貝,而是直接 “竊取” 其資源(如內存),大幅提升性能。
移動構造函數
class MyString {
private:
char* data; // 存儲字符串的動態(tài)內存
public:
// 普通構造函數
MyString(const char* str) {
size_t len = strlen(str);
data = new char[len + 1];
strcpy(data, str);
cout << "構造函數:分配內存" << endl;
}
// 拷貝構造函數(左值引用參數,深拷貝)
MyString(const MyString& other) {
size_t len = strlen(other.data);
data = new char[len + 1];
strcpy(data, other.data);
cout << "拷貝構造:深拷貝(性能差)" << endl;
}
// 移動構造函數(右值引用參數,直接竊取資源)
MyString(MyString&& other) noexcept {
data = other.data; // 直接接管 other 的內存
other.data = nullptr; // other 放棄資源(避免析構時重復釋放)
cout << "移動構造:竊取資源(性能好)" << endl;
}
~MyString() {
if (data) delete[] data;
cout << "析構函數:釋放內存" << endl;
}
};
int main() {
MyString s1("hello"); // 調用普通構造
MyString s2 = s1; // 調用拷貝構造(s1 是左值,必須深拷貝)
MyString s3 = MyString("world"); // 調用移動構造(臨時對象是右值,直接竊取資源)
MyString s4 = std::move(s1); // 調用移動構造(s1 被轉為右值,資源被 s4 竊?。?
return 0;
}輸出:
構造函數:分配內存
拷貝構造:深拷貝(性能差)
構造函數:分配內存
移動構造:竊取資源(性能好)
移動構造:竊取資源(性能好)
析構函數:釋放內存
析構函數:釋放內存
析構函數:釋放內存
關鍵:移動構造函數通過右值引用參數,識別出臨時對象(或被 std::move 標記的左值),直接接管其資源,避免了拷貝開銷(對于大對象,性能提升顯著)。
4、萬能引用與完美轉發(fā)
在模板中,還有一種特殊的 “萬能引用”,它能同時接受左值和右值,并通過 “完美轉發(fā)” 保持原表達式的屬性(左值還是右值)。
4.1 萬能引用:能接受左值和右值的引用
萬能引用僅出現(xiàn)在模板參數中,形式為 T&&,其類型會根據傳入的參數自動推導:
- 若傳入左值,
T&&會被推導為 “左值引用”(T&); - 若傳入右值,
T&&會被推導為 “右值引用”(T&&)。
template <typename T>
void func(T&& t) { // 萬能引用(僅模板中 T&& 才是萬能引用)
// 根據傳入的參數,t 可能是左值引用或右值引用
}
int main() {
int a = 10;
func(a); // 傳入左值,T 推導為 int&,func 實際為 void func(int& t)
func(20); // 傳入右值,T 推導為 int,func 實際為 void func(int&& t)
return 0;
}注意:萬能引用≠右值引用。只有模板中 T&& 且 T 是模板參數時,才是萬能引用;其他場景的 && 都是右值引用(如 void func(int&& t) 是右值引用)。
4.2 完美轉發(fā):保持原參數的左值 / 右值屬性
完美轉發(fā)是指在函數模板中,將參數通過萬能引用接收后,原封不動地轉發(fā)給其他函數(保持其左值 / 右值屬性)。需要配合 std::forward 實現(xiàn)。
為什么需要完美轉發(fā)?如果直接傳遞萬能引用參數,會丟失原屬性(因為引用本身是左值):
void target(int& x) { cout << "左值版本" << endl; }
void target(int&& x) { cout << "右值版本" << endl; }
template <typename T>
void func(T&& t) {
target(t); // 錯誤:t 是引用(有名字),被當作左值處理,始終調用 target(int&)
}
int main() {
int a = 10;
func(a); // 傳入左值,期望調用 target(int&) → 實際正確
func(20); // 傳入右值,期望調用 target(int&&) → 實際錯誤(調用了左值版本)
return 0;
}用 std::forward 實現(xiàn)完美轉發(fā):
template <typename T>
void func(T&& t) {
target(std::forward<T>(t)); // 完美轉發(fā):保持 t 的原始屬性
}
int main() {
int a = 10;
func(a); // 傳入左值 → 轉發(fā)為左值 → 調用 target(int&)
func(20); // 傳入右值 → 轉發(fā)為右值 → 調用 target(int&&)
return 0;
}原理:std::forward<T>(t) 會根據 T 的類型(左值引用或右值引用),將 t 還原為原始的左值或右值屬性。
5、常見問題
- 左值和右值有什么區(qū)別?
- 左值:有標識符,可以取地址,生命周期較長,可以出現(xiàn)在賦值左邊
- 右值:匿名臨時對象,不能取地址,生命周期短,只能出現(xiàn)在賦值右邊
- 左值引用和右值引用有什么區(qū)別?
- 左值引用 (
T&):只能綁定到左值,用于創(chuàng)建別名 - 右值引用 (
T&&):只能綁定到右值,用于實現(xiàn)移動語義和完美轉發(fā)
- 左值引用 (
std::move做了什么?它真的移動數據嗎?std::move只是進行類型轉換,將左值轉換為右值引用- 它本身不移動任何數據,只是標記對象為"可移動的"
- 實際的移動操作在移動構造函數或移動賦值運算符中完成
- 使用
std::move后,原始對象會怎樣? - 對象處于"有效但未指定狀態(tài)"
- 不應該再使用該對象,除非重新賦值
- 析構函數仍然需要正常工作
- 移動語義有什么優(yōu)勢?
- 性能提升:避免不必要的深拷貝,特別是對于管理資源的類
- 資源轉移:允許高效地轉移資源所有權
- 支持不可拷貝對象:可以移動但不能拷貝的對象
std::forward和std::move有什么區(qū)別?std::move:無條件將左值轉為右值引用std::forward:有條件地轉換,保持參數的原始值類別
- 如何實現(xiàn)一個支持移動語義的字符串類?
class MyString {
private:
char* data;
size_t size;
public:
// 移動構造函數
MyString(MyString&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移動賦值運算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
// 析構函數
~MyString() {
delete[] data;
}
// 禁用拷貝(可選)
MyString(const MyString&) = delete;
MyString& operator=(const MyString&) = delete;
};什么情況下應該使用完美轉發(fā)?
應該在包裝函數、工廠函數、構造函數轉發(fā)等場景中使用完美轉發(fā),以保持參數的原始值類別。
// 包裝器函數
template<typename Func, typename... Args>
auto wrapper(Func&& func, Args&&... args) {
return std::forward<Func>(func)(std::forward<Args>(args)...);
}
// 工廠函數
template<typename T, typename... Args>
T create(Args&&... args) {
return T(std::forward<Args>(args)...);
}右值引用本身是左值還是右值?
右值引用本身是左值。因為右值引用有名字、可以取地址(符合左值的特征)。
int&& ref = 10; // ref 是右值引用,但自身是左值 int& ref2 = ref; // 正確:ref 是左值,可綁定到左值引用
這也是為什么在轉發(fā)右值引用時,需要用 std::forward 才能還原其右值屬性。
const 左值引用為什么能綁定右值?
為了靈活性。const 左值引用設計的初衷之一是 “安全地引用臨時對象”,編譯器會為右值創(chuàng)建一個臨時變量,const 左值引用綁定這個臨時變量,并延長其生命周期(與引用同生命周期)。
用途:允許函數接受右值作為參數(如 void print(const string& s) 可接收字符串字面量 print("hello")),同時保證不修改原對象(const 約束)。
什么時候該用移動語義?
函數返回局部對象時
傳遞臨時對象給函數時
容器重新分配內存時
資源管理類(如智能指針)傳遞所有權時
什么是返回值優(yōu)化(RVO)?與移動語義的關系?
返回值優(yōu)化(RVO)是編譯器優(yōu)化技術,允許直接在調用者內存中構造返回對象,避免拷貝。與移動語義的關系:
RVO優(yōu)先級高于移動語義
當RVO不可用時,編譯器使用移動語義
C++17強制要求部分場景的RVO(稱為"guaranteed copy elision")
6、總結
左值/右值
├── 左值 (lvalue):有名字、可尋址
├── 右值 (rvalue):臨時對象、字面量
│ ├── 純右值 (prvalue)
│ └── 將亡值 (xvalue)
│
引用類型
├── 左值引用 (&):綁定左值
├── 右值引用 (&&):綁定右值
└── 通用引用 (T&&):模板推導
└── 完美轉發(fā) (std::forward)到此這篇關于C++ 左值、右值、左值引用、右值引用的文章就介紹到這了,更多相關C++ 左值、右值、左值引用、右值引用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

