C++ array 容器

平时需要存放一组个数固定的同类型数据时,除了传统的 C 风格原生数组之外,C++ 标准库还在 <array> 头文件中提供了更安全好用的 std::array 容器。

简单来说,std::array 就相当于 “升级版的固定长度数组”。它既保留了原生数组长度固定、没有额外内存开销的优点,又像 std::vector 那样自带 .size() 获取长度、越界检查、整体赋值和直接比较等实用功能。

C++ array 的定义与初始化

在 C++ 中,std::array 是一个类模板,定义容器时需要同时写明元素的数据类型和固定个数。

语法:

#include <array>

std::array<元素类型, 元素个数> 变量名 = {元素1, 元素2, ...};

说明:

尖括号 <> 里需要填入两个参数:第 1 个是元素的数据类型,第 2 个是编译阶段就已经确定的固定元素个数。比如 std::array<int, 5> 就表示 “固定保存 5 个 int 整数的 array 容器”。

示例 1:创建并遍历 array

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> scores = {88, 92, 76, 95, 84};

    for (int score : scores) {
        std::cout << score << ' ';
    }
    std::cout << '\n';
    return 0;
}

运行结果如下。

88 92 76 95 84

分析:

上面这个例子非常简单,我们定义了一个固定保存 5 个整数的 array 容器 scores,随后使用范围 for 循环依次把里面的元素打印出来,遍历写法与 std::vector 完全一致。

在初始化 std::array 时,除了把所有元素全部写齐之外,我们还可以只给出前几个元素,或者直接写成空花括号 {}:

// 方式 1
std::array<int, 5> a = {88, 92, 76, 95, 84};

// 方式 2
std::array<int, 5> b = {1, 2};

// 方式 3
std::array<int, 5> c{};

上面 3 种写法中,方式 1 给出了全部 5 个初始值。方式 2 只给出了前两个元素 1 和 2,剩余的 3 个位置会自动补 0。方式 3 使用空花括号 {},会将全部 5 个位置都初始化为 0。

这里小伙伴们要特别注意:如果在函数内部只写 std::array<int, 5> d; 而不加花括号,里面的 5 个整数和未初始化的局部数组一样,装的都是不确定的随机垃圾值。因此想创建全为 0 的 array 时,记得加上空花括号 {},同时初始值的个数也不能超过声明的长度。

提示 std::array 的 “固定长度” 指的是元素个数固定,不能调用 push_back() 动态增加或删除元素,并不是里面的元素值不能修改。只要 array 变量本身没有加 const 修饰,里面的元素依然可以正常修改。

访问、修改与批量填充元素

作为标准容器,std::array 提供了一套统一的元素读写、长度查询与首尾定位函数,常用操作如下表所示。

std::array 常用操作
操作写法 功能说明
arr[i] 访问下标为 i 的元素,不检查下标是否越界
arr.at(i) 访问下标为 i 的元素,越界时抛出异常
arr.front() 访问第一个元素
arr.back() 访问最后一个元素
arr.fill(x) 将容器中的所有元素统一修改为值 x
arr.size() 返回容器的元素个数
arr.empty() 判断容器是否为空,长度为 0 时返回 true
arr.begin() / arr.end() 返回首尾迭代器,可配合通用算法使用

1. 使用 [] 与 at() 访问和修改元素

在 C++ 中,访问 std::array 指定位置的元素既可以使用中括号 [],也可以调用成员函数 at(),它们的下标都从 0 开始计算。

两者的主要区别在于是否检查越界。[] 只管直接按位置读写,不做边界检查。而 at() 会在访问前先检查下标是否合法,一旦越界就会抛出 std::out_of_range 异常让程序报错停下,避免悄悄读写到错误的内存。

示例 2:访问与修改 array 元素

#include <array>
#include <iostream>
#include <string>

int main() {
    std::array<std::string, 3> colors = {"red", "green", "blue"};

    std::cout << colors.size() << '\n';
    std::cout << colors[0] << '\n';
    std::cout << colors.at(1) << '\n';

    colors[2] = "yellow";    // 修改最后一个元素
    std::cout << colors.back() << '\n';
    return 0;
}

运行结果如下。

3
red
green
yellow

分析:

在这个例子中,colors.size() 返回元素个数 3,colors[0] 取出第一个元素 "red",colors.at(1) 取出第二个元素 "green"。

随后我们通过 colors[2] = "yellow"; 把最后一个元素修改为 "yellow",再调用 colors.back() 将其打印出来。平时使用下标访问元素时,大家要确保下标处在 0 到 colors.size() - 1 的合法范围内。

2. 使用 fill() 批量填充元素

如果我们想把 std::array 中的所有元素一次性改成同一个值,不需要手写循环逐个赋值,直接调用成员函数 fill() 就可以了。

示例 3:使用 fill() 批量填充

#include <array>
#include <iostream>

int main() {
    std::array<int, 4> flags{};
    flags.fill(7);    // 将全部元素修改为 7

    for (int n : flags) {
        std::cout << n << ' ';
    }
    std::cout << '\n';
    return 0;
}

运行结果如下。

7 7 7 7

分析:

flags.fill(7); 这句代码表示把容器里的 4 个元素全部修改为 7。

此外,std::array 也支持起止迭代器。如果需要对元素排序,引入 <algorithm> 头文件后即可调用 std::sort() 函数,比如写成 std::sort(flags.begin(), flags.end());,用法与 std::vector 完全相同。

整体赋值、比较与函数传参

相比传统的 C 风格原生数组,std::array 在复制、比较以及作为参数传给函数时要省心很多。

1. 整体赋值与大小比较

前面学习数组时我们知道,原生数组不能直接写 arr1 = arr2 来复制内容,也不能使用 arr1 == arr2 比较里面的元素,因为原生数组名在表达式中会自动退化为内存地址。

而使用 std::array 时,只要两个容器的元素类型和长度完全相同,我们就可以直接使用 = 整体复制,也可以直接使用 ==、!=、<、> 按位置逐个比较元素。请看下面例子。

示例 4:复制与比较 array

#include <array>
#include <iostream>

int main() {
    std::array<int, 3> a = {10, 20, 30};
    std::array<int, 3> b = a;    // 使用 = 整体复制给 b

    if (a == b) {
        std::cout << "修改前:a 与 b 相等\n";
    }

    b[0] = 99;
    if (a != b) {
        std::cout << "修改后:a 与 b 不相等\n";
    }
    return 0;
}

运行结果如下。

修改前:a 与 b 相等
修改后:a 与 b 不相等

分析:

std::array<int, 3> b = a; 会把 a 里的 3 个整数完整复制一份给 b,此时对应位置上的元素完全一致,所以 a == b 成立。当把 b[0] 修改为 99 之后,再次比较时发现第 0 个位置的值不同,a != b 成立,而且原容器 a 中的数据不会受到影响。

需要注意的是,尖括号里的 “元素个数” 也是容器类型的一部分。比如 std::array<int, 3> 和 std::array<int, 4> 在 C++ 里属于两个完全不同的类型,彼此不能互相赋值或比较:

std::array<int, 3> a = {1, 2, 3};
std::array<int, 3> b = {4, 5, 6};
std::array<int, 4> c = {1, 2, 3, 4};

// 正确方式
a = b;

// 错误方式
a = c;

2. 作为函数参数传递

把原生数组传给函数时,数组会退化成指针并丢失长度信息,往往还得额外多传一个长度参数。而 std::array 是一个完整的容器对象,传给函数后依然可以直接调用 .size() 或使用范围 for 循环。为了避免复制整份数组,只读访问时我们通常配合 const 采用引用传递。

示例 5:将 array 传给函数

#include <array>
#include <iostream>

void printArray(const std::array<int, 4>& nums) {
    std::cout << "元素个数:" << nums.size() << '\n';
    for (int n : nums) {
        std::cout << n << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::array<int, 4> data = {10, 20, 30, 40};
    printArray(data);
    return 0;
}

运行结果如下。

元素个数:4
10 20 30 40

分析:

printArray() 函数的形参写成了 const std::array<int, 4>& nums,表示以只读引用的方式借用外部传入的 data 容器。这样既省去了复制整份数组的开销,又能直接在函数内调用 nums.size() 获取元素个数 4。

同时也要留意,形参类型里已经固定写明了长度 4,因此这个函数只能接收 std::array<int, 4> 类型的实参。

三种数组的选型对比

原生数组、std::array 和 std::vector 都可以按顺序存放一组同类型的数据,平时写代码时只需要看 “元素个数在编译阶段能不能固定下来” 即可。如果写代码时还不确定最终有多少个数据,或者运行过程中需要动态增删元素,优先选择 std::vector。

如果元素个数从一开始就是固定的,比如保存一周 7 天的气温、一年 12 个月的销量或三维坐标的 3 个分量,推荐使用 std::array。它没有额外的内存开销,又比原生数组更安全省心。只有在对接 C 语言兼容接口或维护老项目代码时,才需要直接使用原生数组。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号