数组名不是指针,更非“常指针”;其类型为int[5],sizeof(arr)返回整个数组字节数,&arr类型为int(*)[5],与指针类型有本质区别。

数组名不是指针,更不是“常指针”——但绝大多数时候它会自动变成 int* 类型的右值
这是最常被误传的结论。很多教材说“数组名是常量指针”,但它在 C++ 类型系统里根本就不是指针类型。证据很直接:sizeof(arr) 返回的是整个数组字节数,而 sizeof(p)(p 是 int*)永远是 8(64 位平台);对数组取地址:&arr 的类型是 int(*)[5](指向含 5 个 的数组),不是 int**。这些行为只有原生数组类型才能解释。
真正发生的是「数组衰变」(array decay):在绝大多数表达式语境中(如赋值、传参、算术运算),数组名会隐式转换为指向首元素的指针——而且是纯右值(prvalue),不能取地址、不可自增、不可赋值。它不是变量,也不占用存储空间。
-
int arr[5] = {1,2,3,4,5};→arr本身是类型int[5] -
int* p = arr;→ 这里arr衰变为int*右值,再初始化p -
arr++;→ 编译错误:lvalue required as increment operand(arr不是左值) -
&arr和&arr[0]地址数值相同,但类型不同、+1偏移量不同
什么时候数组名不会衰变?——三个关键例外场景
理解衰变的“例外”,才能真正掌控数组类型。这三点直接影响模板推导、函数重载和 sizeof 行为:
-
sizeof(arr):返回5 * sizeof(int),而非sizeof(int*) -
&arr:得到类型为int(*)[5]的指针,(&arr) + 1指向内存中下一个“5元组”起始位置(偏移5*sizeof(int)) - 作为函数形参时用引用语法:
void f(int (&a)[5])—— 此时a是数组引用,不衰变,能保尺寸、可传入sizeof
漏掉这些例外,写泛型代码或封装数组工具时就会意外丢失维度信息,比如把 std::array 当成裸数组用却得不到编译期长度。
立即学习“C++免费学习笔记(深入)”;
为什么 int* p = arr; 看起来像“数组名是指针”?——衰变 vs 显式声明的本质区别
这个赋值之所以成立,是因为编译器插入了隐式转换,不是因为 arr 本身就是指针。对比下面两行:
int arr[3] = {1,2,3};
int* p = arr; // ✅ 合法:arr 衰变为 int* 右值,用于初始化 p
int* q = &arr; // ❌ 错误:&arr 类型是 int(*)[3],不能隐式转为 int*关键差异点:
-
p是变量,可修改(p++、p = &arr[2]);arr本身不可修改、不可取地址(作为左值) -
arr[2]是语法糖,等价于*((arr) + 2)—— 注意括号:先衰变,再指针加法 - 传递给函数时,
void f(int* x)接收的是衰变后的指针,完全丢失数组长度;而void f(int (&x)[5])或template才真正保留数组身份void f(int (&x)[N])
实际踩坑:二维数组传参与 int (*)[N] 指针的必要性
当你写 void process(int mat[][4]),其实等价于 void process(int (*mat)[4]) —— 这里必须提供列数,因为编译器需要知道每行占多少字节来计算 mat+1 的偏移。如果只写 int** mat,你就失去了连续内存布局保证,也无法用 mat[i][j] 安全访问(除非手动分配成指针数组)。
- 正确方式(栈上二维数组):
int grid[3][4]; process(grid);,形参必须是int (*m)[4]或int m[][4] - 错误直觉:
int** p = grid;→ 编译失败:类型不匹配 - 衰变只到“首元素”层级:二维数组
int a[2][3]的首元素是int[3],所以衰变成int (*)[3],不是int**
这种类型细节在对接 C 风格 API 或做高性能数值计算时无法绕过——以为“数组名就是指针”而强行用 int* 接收二维数组,轻则越界读写,重则静默 UB。










