第6.9章: C 风格数组参数

English

概述

在 C++ 中,数组的行为与大多数其他类型不同。当你把数组传给函数时, 数组会退化为指向其首元素的指针。这意味着除非你显式 传入大小,函数无法知道数组有多大。

在本章中,你将学到:

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);
}

这些容器:

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 是固定大小的,所以按值传递会复制 所有元素。按引用传递可避免这笔开销。

要点