第5.7章: C 风格数组退化

English

概述

C 风格数组有一种特殊行为,称为数组退化。 在大多数表达式中,C 风格数组会自动转换(或“退化”)为 指向其首元素的指针。这是 C++ 中最重要的规则之一, 它解释了为什么数组和指针虽然并不相同,却常常看起来可以互换。

在本章中,你将学到:

1. 什么是 C 风格数组退化?

当 C 风格数组出现在大多数表达式中时,它会自动转换为 指向其首元素的指针。


int a[3]{10, 20, 30};

int *p = a;   // array decays to pointer to 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. 退化何时不发生

在以下情况下不发生退化:


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]。 它们会安全地计算出正确的指针:

这是为 C 风格数组获取迭代器的首选现代方式。

8. 为什么退化可能危险

退化之后,指针不再知道数组的大小。 这可能导致:

例如:


int a[3]{10, 20, 30};
int *p = a;

std::size_t s = sizeof(p);   // size of pointer, NOT array

这是常见的缺陷来源。

要点