什么是 "lvalue" 和 "rvalue"?
要理解这个错误,首先必须搞清楚 C 语言中的两个核心概念:左值 和 右值。

左值
"lvalue" 中的 "l" 源于 "location"(位置),左值是一个表达式,它标识了一个内存位置,因此你可以对它进行赋值(在 C 中,除了 const 修饰的情况)。
左值就是“可以出现在赋值号 左边的表达式”。
左值的例子:
-
变量名:这是最常见的左值。
(图片来源网络,侵删)int x = 10; x = 20; // x 是左值,因为它代表一个内存地址
-
数组元素:
arr[i]是一个左值。int arr[10]; arr[0] = 100; // arr[0] 是左值
-
结构体/联合体的成员:
s.member是一个左值。struct Point { int x; int y; }; struct Point p; p.x = 5; // p.x 是左值 -
解引用指针:
*ptr是一个左值。int y = 30; int *ptr = &y; *ptr = 40; // *ptr 是左值,它代表 y 所在的内存位置
-
带下标的指针:
ptr[i]也是一个左值。
(图片来源网络,侵删)int *ptr = &y; ptr[0] = 50; // 等同于 *ptr = 50,是左值
右值
"rvalue" 中的 "r" 源于 "read"(读取),右值是一个临时值,它不持有一个持久的内存位置,你只能读取它的值,但不能对它进行赋值。
右值就是“只能出现在赋值号 右边的表达式”。
右值的例子:
-
字面量:
int a = 10; // 10 是右值 int b = 3.14; // 3.14 是右值 char c = 'A'; // 'A' 是右值
-
表达式计算结果:
int sum = a + b; // a + b 的计算结果是一个临时值,是右值
-
函数返回值(非引用类型):
int get_value() { return 100; // 返回的 100 是一个右值 } int result = get_value(); // get_value() 的返回值是右值 -
强制转换的结果:
double d = 9.8; int i = (int)d; // (int)d 是一个临时值,是右值
核心区别
| 特性 | 左值 | 右值 |
|---|---|---|
| 内存位置 | 有一个持久的、可寻址的内存位置 | 通常是一个临时值,没有持久的内存位置 |
| 赋值操作 | 可以出现在 的左边(被赋值) | 只能出现在 的右边(用于赋值) |
| 取地址 | 可以对左值使用 & 运算符(&x) |
不能对右值使用 & 运算符(&(a+b) 是非法的) |
"lvalue required" 错误的常见原因
这个错误意味着你试图在一个需要左值的地方,使用了一个右值,最常见的场景就是赋值操作。
原因 1:试图给一个右值赋值
这是最直接的原因,你把一个右值放在了 的左边。
// 错误代码 10 = x; // 错误!10 是一个字面量(右值),不能被赋值 (a + b) = 50; // 错误!a + b 的结果是一个临时值(右值),不能被赋值 get_value() = 200; // 错误!函数返回值是右值,不能被赋值
原因 2: 或 运算符的使用错误
(前置或后置递增)和 (前置或后置递减)运算符的操作数必须是一个可修改的左值。
- 前置
++i:先增加i的值,然后返回i(返回的是左值)。 - 后置
i++:先返回i的原始值(返回的是右值),然后增加i的值。
常见错误:
// 错误代码 1: 给右值使用后置自增
int x = 5;
(x + 1)++; // 错误! (x + 1) 是一个右值,不能作为 ++ 的操作数
// 错误代码 2: 给右值使用前置自增
int y = 10;
++(y * 2); // 错误! (y * 2) 是一个右值,不能作为 ++ 的操作数
// 错误代码 3: 给函数返回值使用自增(如果函数返回 int 类型)
int get_num() { return 42; }
get_num()++; // 错误! get_num() 的返回值是右值
原因 3: 作为初始化的一部分时, 左边必须是声明符
在 C 语言中, 在声明语句中(如 int x = 10;)是初始化的一部分,它左边的必须是变量名(一个声明符),而不是一个通用的表达式。
// 错误代码
int a = 5, b = 10;
(a + b) = 15; // 错误!这不是初始化,是赋值,a+b是右值。
// 另一个初始化相关的错误
int arr[5];
arr = {1, 2, 3, 4, 5}; // 错误!在C99标准之后,这种聚合初始化只能在声明时使用。
// 正确的做法是使用循环或memcpy等。
原因 4:作为 sizeof 的操作数(一个特殊的反例)
这是一个需要注意的特例。sizeof 运算符可以接受一个右值,它只关心该类型的大小,而不关心其值是否存在。
// 正确代码 int x = 10; size_t size1 = sizeof(x); // x 是左值,正确 size_t size2 = sizeof(x + 5); // x + 5 是右值,但 sizeof 接受它,正确 size_t size3 = sizeof(100); // 100 是右值,但 sizeof 接受它,正确
如果你看到 sizeof(some_rvalue) 是合法的,不要感到困惑。lvalue required 错误通常不在这里发生。
如何修复 "lvalue required" 错误?
修复方法的核心思想是:将右值转换成左值,或者用一个左值来替代它。
修复 1:为右值创建一个左值变量
如果你需要先计算一个值,然后后续可能会修改它,就把它存入一个变量中。
// 错误代码 (a + b) = 50; // 修复方法 int temp = a + b; // a + b 的结果被存入 temp,temp 是左值 temp = 50; // 现在对左值 temp 赋值,正确
修复 2:使用正确的自增/自减操作数
确保 或 的操作数是一个可修改的变量。
// 错误代码 (x + 1)++; // 修复方法 int temp = x + 1; temp++; // 正确 // 或者,如果意图是修改 x x++; // 正确
修复 3:检查赋值语句的左边
确保 的左边是一个有效的、可修改的左值(如变量、数组元素、解引用指针等)。
// 错误代码
arr = {1, 2, 3};
// 修复方法 (C99)
int arr[5] = {1, 2, 3, 4, 5}; // 在声明时初始化
// 或者使用循环 (通用方法)
int arr2[5];
for (int i = 0; i < 5; i++) {
arr2[i] = i + 1;
}
| 错误场景示例 | 问题原因 | 修复方法 |
|---|---|---|
10 = x; |
给右值(字面量)赋值 | 将 x 放在左边,10 放在右边。 |
(a + b) = 50; |
给右值(表达式结果)赋值 | 创建一个临时变量 int temp = a + b;,temp = 50; |
(x + 1)++; |
对右值(表达式结果)使用 运算符 | 创建临时变量或直接对 x 进行操作。 |
get_value() = 200; |
对右值(函数返回值)赋值 | 修改函数使其返回一个指针或左值引用(如果语言支持),或者改变逻辑。 |
理解左值和右值的区别是掌握 C 语言内存管理和表达式行为的关键,当你遇到 "lvalue required" 错误时,停下来想一想:我放在这个位置的表达式,真的代表一个可以写入的内存地址吗?通常答案就清晰了。
