第6.9章: C 风格数组参数
概述
在 C++ 中,数组的行为与大多数其他类型不同。当你把数组传给函数时, 数组会退化为指向其首元素的指针。这意味着除非你显式 传入大小,函数无法知道数组有多大。
在本章中,你将学到:
- 数组如何退化为指针
- 为什么
int const arr[]与int const* arr完全相同 - 为什么应当避免参数中具有误导性的
[]语法 - 如何正确声明接收数组的函数
- 为什么数组大小信息会丢失
- 多维数组如何工作
- 指针算术与数组索引的关系
- 为什么 C 风格数组参数不安全
- 为什么
::fast_io::array与::fast_io::vector是更安全的替代品
1. 数组退化为指针
当你把数组传给函数时,它会自动转换(退化)为指向其首元素的 指针。
void print_first(int const* p)
{
::fast_io::io::println("first element = ", *p);
}
int main()
{
int arr[3]{10, 20, 30};
print_first(arr); // arr decays to int*
}
在 print_first 内部,函数完全不知道原始数组
有多大。
2. 不要让 [] 骗了你
在函数参数列表中,int arr[] 或
int const arr[] 这种语法并不表示“传入了一个数组”。
它只是写指针的另一种方式。
下面三个声明100% 完全相同:
void f(int const arr[]);
void f(int const arr[10]);
void f(int const* arr);
三者都表示:
“f 接受一个指向 const int 的指针。”
函数参数列表中的方括号既不保留数组大小,也不改变类型。 它们的存在只是为了可读性。
在本教程中,我们在真实代码里会避免使用具有误导性的
arr[] 语法,但在讲解陷阱时仍会展示它——因为你在实际
代码中会见到它。
3. 数组大小会丢失
由于数组退化为指针,函数无法知道数组大小。
这个例子故意使用 int const arr[] 来演示
这个陷阱:
void print_size(int const arr[])
{
::fast_io::io::println(sizeof(arr));
// prints sizeof(int*) — NOT the size of the array!
}
这就是真实代码中不应使用 arr[] 的原因。
你必须显式传入大小:
void print_all(int const* arr, std::size_t n)
{
for(std::size_t i{}; i != n; ++i)
::fast_io::io::println(arr[i]);
}
4. 多维数组
只有第一维会退化。其余维度必须是已知的。
void print_matrix(int const (*arr)[4], std::size_t rows)
{
for(std::size_t r{}; r != rows; ++r)
for(std::size_t c{}; c != 4; ++c)
::fast_io::io::print(arr[r][c], ' ');
}
这是合法的,因为编译器知道每一行的大小。
但下面这种写法非法:
void bad(int const arr[][]); // ❌ error — second dimension unknown
5. 指针算术
在函数内部,数组索引不过就是指针算术:
void demo(int const* p)
{
::fast_io::io::println(p[2]); // same as *(p + 2)
}
这就是 C 风格数组参数既强大又危险的原因。
6. const 正确性
如果函数不应修改数组,请使用 int const*。
void print_all(int const* arr, std::size_t n);
7. 为什么 C 风格数组参数不安全
C 风格数组参数有几个问题:
- 大小丢失了
- 没有边界检查
- 没有所有权信息
- 容易意外溢出
- 容易弄错指针类型
由于这些原因,现代 C++ 代码尽可能避免裸数组参数。
8. 优先使用 ::fast_io::array 与 ::fast_io::vector
fast_io 库提供了更安全、更简洁的容器,可以轻松地
传入或返回函数。
固定大小数组
void process(::fast_io::array<int, 4> arr)
{
for(auto e : arr)
::fast_io::io::println(e);
}
动态大小数组
void process(::fast_io::vector<int> v)
{
for(auto e : v)
::fast_io::io::println(e);
}
返回 vector
::fast_io::vector<::std::size_t> make_sequence(std::size_t n)
{
::fast_io::vector<::std::size_t> v;
for(std::size_t i{}; i != n; ++i)
v.push_back(i);
return v; // efficient: NRVO + move semantics
}
按 const 引用传递 vector
void process(::fast_io::vector<int> const& v)
{
for(auto e : v)
::fast_io::io::println(e);
}
这些容器:
- 自带大小信息
- 易于按值或按引用传递
- 避免指针退化
- 避免标准库容器造成的 ODR 问题
- 是仅头文件的,并且在各翻译单元之间保持一致
9. 按值传递与按引用传递的开销
理解参数传递的开销有助于你写出高效的代码。
| 类型 | 按值传递 | 按引用传递 |
|---|---|---|
int const arr[] / int const* arr |
廉价(复制指针) | 廉价(复制引用) |
::fast_io::vector<T> |
廉价(三指针结构体) | 最廉价 |
::fast_io::array<T, N> |
复制全部 N 个元素 | 廉价 |
::fast_io::vector 被设计为传递开销很低。它的行为
就像一个包含三个指针(begin、end、capacity)的小结构体。
::fast_io::array 是固定大小的,所以按值传递会复制
所有元素。按引用传递可避免这笔开销。
要点
- 数组传给函数时会退化为指针。
int const arr[]与int const* arr完全相同。- 数组大小信息会丢失;你必须显式传入它。
- 多维数组中只有第一维会退化。
- 数组索引就是指针算术。
- C 风格数组参数不安全,且容易出错。
::fast_io::array与::fast_io::vector是更安全、更简洁的替代品。- 按值传递
::fast_io::vector很廉价;按引用传递最廉价。