平时需要存放一组个数固定的同类型数据时,除了传统的 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 提供了一套统一的元素读写、长度查询与首尾定位函数,常用操作如下表所示。
| 操作写法 | 功能说明 |
|---|---|
| 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 语言兼容接口或维护老项目代码时,才需要直接使用原生数组。
