C++ 数组名是常指针吗 C++数组衰变与指针区别解析【理论】

4次阅读

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

C++ 数组名是常指针吗 C++数组衰变与指针区别解析【理论】

数组名不是指针,更不是“常指针”——但绝大多数时候它会自动变成 int* 类型的右值

这是最常被误传的结论。很多教材说“数组名是常量指针”,但它在 c++ 类型系统里根本就不是指针类型。证据很直接:sizeof(arr) 返回的是整个数组字节数,而 sizeof(p)pint*)永远是 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。

text=ZqhQzanResources