C++ 左值与右值全面讲解
一、什么是左值(Lvalue)与右值(Rvalue)
项目 | 左值(Lvalue) | 右值(Rvalue) |
---|---|---|
是否有名字 | ✅ 有,具名变量 | ❌ 无名,通常是表达式/字面值 |
是否可取地址 | ✅ 可以 &a |
❌ 不能 &(a + b) |
生命周期 | 通常在作用域内持续 | 临时值,表达式计算后可能立即销毁 |
是否能赋值 | ✅ 可以放在赋值号左边 | ❌ 不可以出现在赋值号左边 |
举例 | int a; , a = 5; , int& r = a; |
10 , a + b , getTemp() (非引用返回) |
二、左值与右值基础示例
int a = 5; // a 是左值
int b = a + 3; // a + 3 是右值(表达式结果)
理解:
a
是具名变量,有内存地址,可被赋值;a + 3
是右值,只存在于表达式计算期间。
三、引用绑定规则示例
int a = 10;
int& ref = a; // ✅ 左值引用,绑定 a
ref = 15; // ✅ 修改了 a 的值
int& ref2 = 10; // ❌ 错误!右值不能用普通引用绑定
const int& cref = 10; // ✅ 合法!const 引用可绑定右值
结论:
- 右值可以用
const T&
绑定; - 不能用普通
T&
来绑定右值; const T&
会延长右值生命周期。
四、右值引用(C++11)
右值引用的语法:
int&& rref = 5; // 右值引用
rref = 10; // ✅ 修改右值绑定的变量
应用:用于移动语义,替代深拷贝,提高性能。
五、函数参数中的左值引用与右值引用重载
void print(string& s) { cout << "左值引用: " << s << endl; }
void print(string&& s) { cout << "右值引用: " << s << endl; }
int main() {
string a = "Hello";
print(a); // 左值引用
print(string("World")); // 右值引用
print(a + "!"); // 右值引用(临时表达式)
}
输出:
左值引用: Hello
右值引用: World
右值引用: Hello!
六、函数返回值与左值右值
int& getA() {
static int a = 10;
return a;
}
int getB() {
return 20;
}
int main() {
getA() = 30; // ✅ 合法,getA() 是左值引用
getB() = 40; // ❌ 错误,getB() 是右值(返回值拷贝)
}
原则:
- 返回 引用(左值) 可以继续赋值;
- 返回 值(右值) 不能被赋值。
七、引用折叠(C++11)
用于模板中区分左值右值:
template<typename T>
void func(T&& arg);
func(a); // T = int&,T&& = int&(折叠为左值引用)
func(10); // T = int,T&& = int&&(右值引用)
折叠规则口诀:
& &
→&
& &&
→&
&& &
→&
&& &&
→&&
八、const 修饰的指针/引用绑定
1. 右值绑定常量引用
const int& r = 100; // OK,右值可以用 const 左值引用绑定
2. 左值引用右值
int& r = 100; // ❌ 错误
九、右值引用与移动构造函数
class MyString {
string data;
public:
MyString(string&& s) : data(std::move(s)) {}
};
std::move(s)
将右值传递到 data
,避免了复制构造。
十、配套总结表格
声明 | 含义 |
---|---|
int& a = b; |
左值引用,b 必须是左值 |
const int& a = 10; |
常量左值引用,可绑定右值 |
int&& a = 10; |
右值引用,绑定临时值或移动资源 |
int x = get(); x = 10; |
正常赋值 |
get() = 10; |
❌ 若 get() 返回右值,非法赋值 |
getRef() = 10; |
✅ 若返回引用,是左值,可赋值 |
十一、小测试题与解析
题 1:哪些是合法声明?
int a = 5;
int& r1 = a; // ✅
int& r2 = 5; // ❌,5 是右值
const int& r3 = 5; // ✅,右值 + const
int&& r4 = 5; // ✅,右值引用
题 2:以下调用是否合法?
int& get(); // 返回左值引用
int set(); // 返回右值
get() = 10; // ✅ 合法
set() = 20; // ❌ 错误,右值不能赋值
总结口诀回顾
“能取地址是左值,临时结果是右值;加 const 能绑定,双&&能转移。”