第5.7章: C 风格数组退化
概述
C 风格数组有一种特殊行为,称为数组退化。 在大多数表达式中,C 风格数组会自动转换(或“退化”)为 指向其首元素的指针。这是 C++ 中最重要的规则之一, 它解释了为什么数组和指针虽然并不相同,却常常看起来可以互换。
在本章中,你将学到:
- 什么是 C 风格数组退化
- 退化何时发生
- 退化何时不发生
- 为什么
sizeof(a)与sizeof(p)不同 - 为什么函数参数无法按值接收数组
- 退化如何与指针算术交互
- 如何使用
::std::ranges::begin(a)与::std::ranges::end(a)
1. 什么是 C 风格数组退化?
当 C 风格数组出现在大多数表达式中时,它会自动转换为 指向其首元素的指针。
int a[3]{10, 20, 30};
int *p = a; // array decays to pointer to a[0]
退化之后:
a变成&a[0]- 数组的大小信息丢失了
a (array):
Address: 1000
Memory: [ 10 ][ 20 ][ 30 ]
p (pointer):
Address: 2000
Memory: [ 1000 ] ← pointer to a[0]
2. 为什么会发生退化?
C 风格数组不能被拷贝或赋值。 它们也不能按值传给函数。
为了让数组能在表达式和函数调用中使用,C++ 会自动 把它们转换为指针。
3. 退化何时发生
退化在大多数表达式中发生,包括:
- 赋值给指针
- 把数组传给函数
- 比较
- 指针算术
int a[3]{10, 20, 30};
int *p = a; // decay
int *q = a + 1; // decay, then pointer arithmetic
bool same = (a == p); // decay, then compare pointers
4. 退化何时不发生
在以下情况下不发生退化:
- sizeof(a)
- &a(对整个数组取地址)
- decltype(a)
- 某些上下文中的模板实参推导
int a[3]{10, 20, 30};
std::size_t s1 = sizeof(a); // 3 * sizeof(int)
std::size_t s2 = sizeof(a[0]); // sizeof(int)
int *p = a; // decay
std::size_t s3 = sizeof(p); // size of pointer, NOT array
关键规则:
sizeof(a) 给出整个数组的大小。
sizeof(p) 给出一个指针的大小。
5. 函数参数中的退化
你不能把 C 风格数组按值传给函数。 相反,数组会退化为指针。
void f(int *p); // receives pointer, not array
int a[3]{10, 20, 30};
f(a); // a decays to &a[0]
即使你写成:
void g(int a[3]);
它仍会被解释为:
void g(int *a); // array size is ignored
6. 退化与指针算术
退化之后,该指针的行为就像任何其他指向首元素的指针一样。
int a[3]{10, 20, 30};
int *p = a; // decay
int *q = p + 2;
int x = *q; // 30
这就是数组索引能工作的原因:
a[i] // means *(a + i)
因为 a 退化成了指针。
7. 使用 ::std::ranges::begin(a) 与 ::std::ranges::end(a)
现代 C++ 不再依赖退化,而是提供了显式的函数来获取 C 风格数组的迭代器:
#include
int a[3]{10, 20, 30};
int *first = ::std::ranges::begin(a);
int *last = ::std::ranges::end(a); // one past the end
这些函数不会求值 a[N]。
它们会安全地计算出正确的指针:
::std::ranges::begin(a)→&a[0]::std::ranges::end(a)→&a[0] + N
这是为 C 风格数组获取迭代器的首选现代方式。
8. 为什么退化可能危险
退化之后,指针不再知道数组的大小。 这可能导致:
- 越界的指针算术
- 错误地使用
sizeof - 类型信息丢失
例如:
int a[3]{10, 20, 30};
int *p = a;
std::size_t s = sizeof(p); // size of pointer, NOT array
这是常见的缺陷来源。
要点
- C 风格数组在大多数表达式中会自动退化为指针。
- 退化把
a转换为&a[0]。 - 退化会丢失数组的大小信息。
sizeof(a)给出数组大小;sizeof(p)给出指针大小。- 函数参数无法按值接收数组;它们接收到的是指针。
- 由于退化,数组索引
a[i]定义为*(a + i)。 - 使用
::std::ranges::begin(a)与::std::ranges::end(a)获取数组迭代器。 - 指针就是 C 风格数组的迭代器。
- 退化可能危险,因为指针不再知道数组大小。