std::array 简介:固定长度数组的优势与使用

在课程《容器与数组简介》中,我们概述了容器与数组的概念,总结如下:

  • 容器为**无名对象(元素)**的集合提供存储空间。
  • 数组在内存中连续存储元素,可通过下标实现快速、直接的随机访问。
  • C++ 常用的三种数组类型:std::vectorstd::array 和 C 风格数组。

在课程《std::vector 的重新调整大小与容量》中,我们指出数组分为两类:

  • 固定长度数组(fixed-size arrays):长度在实例化时必须已知且不可更改;C 风格数组与 std::array 均属此类。
  • 动态数组(dynamic arrays):运行期可调整长度;std::vector 即动态数组。

上一章我们重点讲解了 std::vector:它速度快、易用且功能丰富,因此成为需要数组容器时的首选。

为何不一律使用动态数组

动态数组功能强大,但也伴随权衡:

  • 性能略逊于固定长度数组(大量非故意重分配时才易察觉)。
  • std::vectorconstexpr 支持极为有限。

现代 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

constconstexpr

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 的元素。
[显示解答]

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"