定长数组
中文

定长数组

上一节用“把一个模块拆到多个文件里”结束了第 7 章。这一章把焦点从项目布局拉回 到普通数据本身:定长数组、字符缓冲区,以及对连续存储做借用视图。

定长数组会把一组已知数量的元素直接存放在自己内部。它自己拥有这些值,而它的长 度也是类型的一部分。当你一开始就知道自己需要多少个元素时,数组就是 scpp 目前 最简单的序列类型。

对下面每个可以运行的例子,把文件保存成 arrays.scpp,然后这样编译运行:

scpp arrays.scpp -o arrays
./arrays

对于那些本来就应该被拒绝的例子,如果你想让编译器输出逐字匹配,就按诊断块里给 出的描述性文件名保存。

用固定元素个数声明数组

T[N] 的意思是“一个元素类型为 T、长度为 N 的数组”。

import std;

int main() {
    int scores[4]{};
    scores[0] = 10;
    scores[1] = 20;
    scores[2] = 30;
    scores[3] = 40;

    std::println("{} {}", scores[0], scores[3]);
    return 0;
}

输出:

10 40

scores 直接拥有四个 int 值。这里的 [4] 不是只给人看的注释;它本身就是变 量类型的一部分,每一个合法下标都必须落在这个固定长度里。

空花括号也很重要。就今天的 scpp 来说,T[N]{} 是最稳妥的起点:先把整个数组初 始化好,然后再把你想要的元素一个个填进去。

auto& 做 range-based for 可以原地修改每个元素

第 3.5 节已经用过 range-based for 来依次读取数组元素。把循环变量写成 auto& 时,拿到的就是每个元素的可变引用。

import std;

int main() {
    int values[3]{};
    int next = 1;

    for (auto& value : values) {
        value = next * 10;
        next = next + 1;
    }

    for (int value : values) {
        std::println("{}", value);
    }

    return 0;
}

输出:

10
20
30

第一层循环里的每个 value 都引用着数组里的真实元素,不是一份拷贝。通过这个 引用写入,改到的就是数组自己。

数组长度也可以来自前面定义的 constexpr

长度不一定非得手写成字面量。只要是前面已经可用、并且能解析成正整数长度的常量 表达式,也一样可以。

import std;

int main() {
    constexpr int count = 4;
    int values[count]{};
    values[3] = 9;

    std::println("{} {}", count, values[3]);
    return 0;
}

输出:

4 9

这样通常比到处重复同一个字面量更清楚。关键不在于这个名字恰好叫 count,而在 于它是个 constexpr,所以编译器能在程序运行前就把数组长度算出来。

数组长度必须是常量表达式

普通的运行期变量不能拿来当数组长度。

int main() {
    int count = 4;
    int values[count]{};
    return 0;
}

编译器输出:

array_nonconst_bound_fail.scpp: error: 3:16: expression is not a constant expression: identifier 'count' is not available

这里的 scpp 不支持“运行期才知道长度的局部数组”。编译器必须一开始就知道 values 的完整大小,所以这个长度必须在编译阶段就能确定,不能等到运行时再发现。

明显越界的常量下标会被立刻拒绝

因为长度本来就是类型的一部分,所以只要一个常量下标明显不可能合法,编译器就能 直接在编译期拒绝它。

int main() {
    int values[3]{};
    return values[3];
}

编译器输出:

array_out_of_bounds_compile.scpp:3:12: error: array subscript 3 is out of bounds for array of size 3
 3 |     return values[3];
   |            ^

int values[3] 来说,合法下标只有 012。既然源码里直接写出了 3,编 译器就已经有足够的信息在程序运行前把它拦下来。

今天请按元素逐个填值,不要直接写一个值列表

在普通 C++ 里,int scores[4]{10, 20, 30, 40}; 会是第一节那个例子的自然写法。 但在今天的 scpp 里,这种“多个元素一起写在花括号里”的初始化还不支持。

int main() {
    int scores[4]{10, 20, 30, 40};
    return 0;
}

编译器输出:

array_brace_init.scpp:2:5: error: brace-initialization of this member requires exactly one expression
 2 |     int scores[4]{10, 20, 30, 40};
   |     ^

这也就是为什么本节所有可运行示例都先从 T[N]{} 开始,再把元素一个个赋值进去。

到目前为止,定长数组的规则是

下一节仍然会继续讲数组,不过会把元素类型收窄到 char,把这块定长存储当成一个 跟 C 兼容的文本缓冲区来用。


← 上一章:把模块拆到不同文件中 · 目录