在课程《容器与数组简介》中,我们概述了容器与数组的概念,总结如下:
- 容器为**无名对象(元素)**的集合提供存储空间。
- 数组在内存中连续存储元素,可通过下标实现快速、直接的随机访问。
- C++ 常用的三种数组类型:
std::vector
、std::array
和 C 风格数组。
在课程《std::vector
的重新调整大小与容量》中,我们指出数组分为两类:
- 固定长度数组(fixed-size arrays):长度在实例化时必须已知且不可更改;C 风格数组与
std::array
均属此类。 - 动态数组(dynamic arrays):运行期可调整长度;
std::vector
即动态数组。
上一章我们重点讲解了 std::vector
:它速度快、易用且功能丰富,因此成为需要数组容器时的首选。
为何不一律使用动态数组?
动态数组功能强大,但也伴随权衡:
- 性能略逊于固定长度数组(大量非故意重分配时才易察觉)。
std::vector
对constexpr
支持极为有限。
现代 C++ 中,第二点尤为关键:constexpr
数组能让代码更健壮、编译器优化更彻底。凡能用 constexpr
数组之处,皆应使用——此时 std::array
便是首选容器。
最佳实践
- 需要
constexpr
数组 → 使用std::array
- 非
constexpr
场景 → 使用std::vector
定义 std::array
std::array
定义于 <array>
头文件,接口设计接近 std::vector
,但声明方式不同:
#include <array> // std::array
#include <vector> // std::vector
int main()
{
std::array<int, 5> a{}; // 长度 5 的 int 数组
std::vector<int> b(5); // 长度 5 的 int 向量(对比)
}
模板参数说明:
- 第 1 个实参:元素类型
- 第 2 个实参:长度(非类型模板实参)
相关内容:非类型模板参数见课程《非类型模板参数》。
长度必须是常量表达式
与运行时可变长的 std::vector
不同,std::array
长度必须是编译期常量:
#include <array>
int main()
{
std::array<int, 7> a{}; // 字面量常量
constexpr int len{8};
std::array<int, len> b{}; // constexpr 变量
enum Colors { red, green, blue, max_colors };
std::array<int, max_colors> c{}; // 枚举器
#define DAYS_PER_WEEK 7
std::array<int, DAYS_PER_WEEK> d{}; // 宏(不推荐,推荐改用 constexpr)
}
不允许使用运行时值或非 constexpr
变量:
#include <array>
#include <iostream>
void foo(const int length) // 运行时常量
{
std::array<int, length> e{}; // 错误:length 不是常量表达式
}
int main()
{
int n{};
std::cin >> n;
std::array<int, n> f{}; // 错误:n 非常量
}
零长度 std::array
允许定义长度 0:
#include <array>
#include <iostream>
int main()
{
std::array<int, 0> arr{};
std::cout << arr.empty(); // 输出 1(true)
}
零长度 std::array
无数据成员,访问元素(含 operator[]
)将导致未定义行为;可用 empty()
检测。
聚合初始化
std::array
是聚合体(aggregate),无构造函数,使用聚合初始化:
#include <array>
int main()
{
std::array<int, 6> fib{ 0, 1, 1, 2, 3, 5 }; // 拷贝列表初始化
std::array<int, 5> prime{ 2, 3, 5, 7, 11 }; // 列表初始化(推荐)
return 0;
}
初始化规则:
- 未提供初始化器时,元素默认初始化(
int
等内置类型会未初始化)。 - 推荐值初始化(空花括号
{}
)以保证零初始化:
std::array<int, 5> a{}; // 所有元素零初始化(推荐)
- 初始化器数量超过长度 → 编译错误。
- 不足时,剩余元素值初始化:
std::array<int, 4> b{1, 2}; // b[2]=0, b[3]=0
const
与 constexpr
std::array
可声明为 const
:
const std::array<int, 5> prime{ 2, 3, 5, 7, 11 };
整体为 const
,元素隐式为 const
。
完全支持 constexpr
,这是使用 std::array
的核心原因:
constexpr std::array<int, 5> prime{ 2, 3, 5, 7, 11 };
最佳实践
尽可能将 std::array
设为 constexpr
;若无法 constexpr
,考虑 std::vector
。
C++17 CTAD(类模板实参推导)
C++17 起可用 CTAD 自动推导元素类型与长度:
constexpr std::array a1{ 9, 7, 5, 3, 1 }; // 推导为 std::array<int, 5>
constexpr std::array a2{ 9.7, 7.31 }; // 推导为 std::array<double, 2>
若编译器不支持 C++17,需显式提供模板实参。
部分模板实参省略
CTAD 不支持“只省略长度”或“只省略类型”。C++20 起可用 std::to_array
:
#include <array>
constexpr auto arr1 = std::to_array<int, 5>({ 9, 7, 5, 3, 1 });
constexpr auto arr2 = std::to_array<int>({ 9, 7, 5, 3, 1 }); // 推导长度
constexpr auto arr3 = std::to_array({ 9, 7, 5, 3, 1 }); // 推导类型与长度
std::to_array
会产生临时对象并拷贝,开销较大;仅在无法从初始化器推导类型时使用。
通过下标访问元素
与 std::vector
一样,使用 operator[]
:
#include <array>
#include <iostream>
int main()
{
constexpr std::array<int, 5> prime{ 2, 3, 5, 7, 11 };
std::cout << prime[3]; // 输出 7
std::cout << prime[9]; // 未定义行为
}
operator[]
不检查越界。后续课程将介绍其他索引方式。
小测验
问题 1
std::array
使用何种初始化方式?
[显示解答]
问题 2
定义一个 std::array
,存储全年每天的高温(精确到 0.1 度)。
[显示解答]
问题 3
用 std::array
初始化字符 'h','e','l','l','o'
,并打印下标 1 的元素。
[显示解答]