因为C++标准规定数组名在绝大多数表达式中自动转换为指向首元素的指针,该过程称“数组退化”;函数参数列表不支持直接传递完整数组类型,故传参时必退化为指针,导致sizeof失效、长度丢失,唯一保留维度的方式是使用数组引用(T(&)[N])或std::span等现代方案。

为什么传数组会变成指针?
因为 C++ 标准规定:**数组名在绝大多数表达式中会自动转换为指向首元素的指针**,这个过程叫“数组退化”(decay)。这不是编译器偷懒,而是语言设计使然——函数参数列表不支持直接传递完整数组类型(除非用引用或模板封装)。
常见错误现象:sizeof(arr) 在函数内返回指针大小(通常是 8),而不是原数组字节数;std::size(arr) 编译失败;for (auto& x : arr) 报错“range-based for requires array or container”。
- 只有 3 种场景不发生 decay:取地址
&arr、sizeof(arr)、decltype(arr) - 函数声明里写
void f(int arr[10])和void f(int* arr)完全等价,方括号里的数字被忽略 - 如果真需要知道长度,必须额外传一个
size_t n参数,或者改用std::array/std::vector
怎么阻止 decay?用引用绑定原始数组
唯一能保留数组维度信息的方式是用**引用参数**,语法是 void f(int (&arr)[N]),其中 N 是编译期常量。这样 sizeof(arr) 正确,std::size(arr) 可用,范围 for 也能工作。
使用场景:需要泛型处理固定大小数组(比如矩阵行、RGB 像素缓冲区、硬件寄存器映射);或想在编译期做长度检查(如静态断言 static_assert(N == 3))。
立即学习“C++免费学习笔记(深入)”;
-
N必须是字面量或 constexpr 表达式,不能是变量 - 不同长度的数组会实例化不同函数重载,
f(arr1)和f(arr2)调用的是两个函数 - 不能用于动态分配数组(
new int[10]返回的是int*,无法绑定到int(&)[10])
模板 + auto 推导:更灵活的免 decay 方案
用函数模板配合引用形参,让编译器自动推导 N,比手写 int (&)[N] 更实用:
template <size_t N>
void process(const int (&arr)[N]) {
static_assert(N > 0);
for (size_t i = 0; i < N; ++i) { /* ... */ }
}这比裸指针安全,也比 std::vector 零开销。但要注意:它仍只接受栈上数组或全局数组,不接受 std::array(后者是类类型,需另写重载)。
- 若要同时支持
int[5]和std::array<int, 5>,得加一个template<class T, size_t N> void process(const std::array<T,N>&) - 推导出的
N是编译期常量,可用于std::array<int, N>构造或栈分配 - 别写成
template<class T> void f(T arr)—— 这又触发 decay 成T*
std::span 是什么?C++20 的现代解法
std::span 是 C++20 引入的非拥有视图类型,本质是轻量级的 {ptr, size} 封装,能安全替代裸指针+长度组合,且支持从数组、std::vector、std::array 等构造。
它不阻止 decay(构造时仍会从数组名取指针),但把“指针+长度”逻辑收束到一个类型里,避免手动传两个参数,也杜绝了指针悬空或长度错配问题。
- 声明为
void f(std::span<const int> s),调用f(arr)或f(vec)都合法 - 内部可直接用
s.size()、s.data()、s[i],语义清晰 - 注意:它不存储数据,也不管理生命周期,传入的数组必须比 span 活得久
C++ 数组 decay 是根植于 C 兼容性的底层机制,绕不开,但有明确路径控制——要么用引用锁死尺寸,要么用模板推导,要么交给 std::span 统一抽象。最容易被忽略的是:即使用了 std::span,传入栈数组时仍会发生一次 decay(转成指针),只是后续使用不再暴露这个细节。


















