原生数组名是左值但不可赋值:有地址、可取址、生命周期明确,但因数组类型不可复制而禁止整体赋值;数组元素arr[i]恒为左值;花括号列表{1,2,3}非表达式,不参与值类别判定;std::array支持赋值与移动,原生数组不支持。

原生数组名是左值,但不能直接赋值
原生数组名(如 int arr[5] 中的 arr)本身是左值:它有固定内存地址、可取地址(&arr 合法)、生命周期由作用域决定。但它**不能出现在赋值运算符左侧**,因为数组类型不可复制——arr = other_arr 会编译报错,这不是因为它不是左值,而是 C++ 禁止对数组类型做整体赋值。
常见错误现象:error: invalid array assignment。这不是左值/右值判断出错,而是类型系统限制。
-
int arr[3] = {1,2,3};→arr是左值(&arr合法,sizeof(arr)返回整个数组大小) -
int* p = arr;→ 这里arr发生隐式退化为指针(右值),等价于&arr[0],但这个转换结果是纯右值 -
decltype(arr)是int[3](左值类型);decltype(arr + 0)是int*(右值表达式)
数组元素(如 arr[i])总是左值
arr[i] 表示对数组的下标访问,只要 i 在合法范围内,其结果就是一个左值:它对应某个确定内存位置的变量,可取地址、可赋值、可绑定到左值引用。
注意:即使数组本身是 const(const int arr[3] = {...}),arr[i] 仍是左值,只是不可修改(即 const 左值)。
立即学习“C++免费学习笔记(深入)”;
-
arr[0] = 42;→ 合法(非 const 数组) -
int& r = arr[1];→ 合法(arr[1]是左值,能绑定左值引用) -
int&& rr = arr[2];→ 编译失败(左值不能绑定到右值引用) -
&arr[0]→ 合法,返回指向首元素的指针
数组字面量(如 {1,2,3})不是表达式,不参与左值/右值分类
花括号初始化列表({1,2,3})本身**不是 C++ 表达式**,没有类型、不能取地址、也不能单独存在。它只在特定上下文中被解释:用于初始化、聚合初始化或 std::initializer_list 构造。
所以你不会看到 {1,2,3} 被归类为左值或右值——它压根不参与值类别(value category)判定。真正参与分类的是它所初始化的对象(如 int arr[3] = {1,2,3}; 中的 arr)或推导出的临时对象(如 auto x = {1,2,3}; 推导为 std::initializer_list<int></int>,此时 x 是左值)。
-
auto il = {1,2,3};→il是左值(变量名),类型是std::initializer_list<int></int> -
func({1,2,3});→ 传入的是一个临时std::initializer_list<int></int>对象,该临时对象是纯右值 - 直接写
{1,2,3} = ...或&{1,2,3}→ 编译错误,语法非法
std::array 和原生数组的关键区别点
很多人混淆原生数组和 std::array,关键在于后者是类模板,支持拷贝和赋值——这意味着 std::array 对象本身既是左值,也能作为赋值目标;而它的元素行为与原生数组一致(a[i] 是左值)。
这种差异直接影响移动语义:原生数组没有移动构造函数,std::array 有(但内部仍逐个移动元素)。如果你需要可移动、可赋值的“数组语义”,必须用 std::array 或 std::vector,而非原生数组。
-
std::array<int> a = {1,2,3}, b;</int>→b = a;合法(拷贝赋值) -
int arr1[3], arr2[3]; arr2 = arr1;→ 编译失败 -
std::array<int>& ar = a;</int>→ 合法(绑定到左值引用) -
std::move(a)→ 返回std::array<int>&&</int>,是将亡值(xvalue),可触发移动语义
原生数组的左值性容易被忽略,因为它“看起来像值”却不可赋值;而它的退化行为(arr → int*)又制造出右值,这种隐式转换是实际编码中最常引发误解的地方。


















