std::array 的长度与索引:深入理解固定大小数组的长度与下标

在《std::vector 与无符号长度及下标问题》中,我们讨论了标准库容器将长度与索引设为无符号类型所带来的困境。由于 std::array 也属于标准库容器,因此同样面临这一问题。本节将回顾如何获取 std::array 的长度并进行索引。由于 std::vectorstd::array 接口相似,内容将与前述讨论保持一致;但因只有 std::array 完全支持 constexpr,我们将重点探讨这一点。

开始前,请先复习“符号转换属于收缩转换,except 当 constexpr”这一要点(见《std::vector 与无符号长度及下标问题》)。


std::array 的长度类型为 std::size_t

std::array 的实现如下:

template<typename T, std::size_t N> // N 为非类型模板参数
struct array;

可见,表示长度的非类型模板参数 N 类型为 std::size_t,这是一个大型无符号整数类型。

相关内容:

  • 类模板(含结构模板)
  • 非类型模板参数

因此,定义 std::array 时,长度实参必须为 std::size_t 或可转换为 std::size_t常量表达式。由于该值必须是 constexpr,若使用带符号整数,编译期即可安全转换为 std::size_t,不构成收缩转换。

旁注:
C++23 前无 std::size_t 字面量后缀,因编译期 int → std::size_t 转换已足够;C++23 引入 UZ 后缀,便于区分 0int)与 0UZstd::size_t)。


std::array 的长度与索引类型 size_type 始终为 std::size_t

std::vector 一样,std::array 提供嵌套 typedef size_type,即容器长度/索引的别名。对于 std::arraysize_type 始终是 std::size_t 的别名。

注意:长度模板参数显式使用 std::size_t 而非 size_type,因为 size_type 此时尚未定义;其余位置均使用 size_type


获取 std::array 长度的三种方式

  1. 成员函数 size()
    返回无符号 size_type 长度:

    #include <array>
    #include <iostream>
    
    int main()
    {
        constexpr std::array arr{9.0, 7.2, 5.4, 3.6, 1.8};
        std::cout << "length: " << arr.size() << '\n'; // 返回 size_type
    }
    

    输出:length: 5

    std::string/std::string_view 不同,std::array 仅提供 size(),无 length()

  2. C++17 非成员函数 std::size()
    std::array 内部调用 size(),返回无符号 size_type

    std::cout << "length: " << std::size(arr);
    
  3. C++20 非成员函数 std::ssize()
    返回带符号长度(通常为 std::ptrdiff_t):

    std::cout << "length: " << std::ssize(arr);
    

以 constexpr 方式获取长度

由于 std::array 长度是 constexpr,上述函数即使对非 constexpr 对象也返回 constexpr 值,可在常量表达式中使用:

std::array arr{9, 7, 5, 3, 1}; // 非常量对象
constexpr int len{std::size(arr)}; // OK:返回 constexpr std::size_t,可安全转为 int

Visual Studio 误报 C4365;已向微软反馈。

语言缺陷:若 std::array 作为函数参数按(const)引用传递,上述函数返回非 constexpr 值。C++23 的 P2280 已修正,当前编译器支持有限。
解决:将函数改为模板,以非类型模板参数接收长度。


索引方式

1. operator[]at()

  • operator[]:无边界检查,越界即 UB。
  • at():运行时边界检查,越界抛出 std::out_of_range
    推荐避免 at(),因我们通常提前检查或需要编译期检查。

二者均期望索引类型为 size_typestd::size_t)。

  • constexpr 索引:编译期转换,无符号问题。
  • constexpr 带符号索引:可能触发符号转换警告。

2. std::get<I>() —— 编译期边界检查

当索引为 constexpr 时,可用 std::get<I>() 模板函数:

constexpr std::array prime{2, 3, 5, 7, 11};
std::cout << std::get<3>(prime); // OK:7
std::cout << std::get<9>(prime); // 编译错误:越界

内部含 static_assert,越界即停止编译。
std::get 只能用于 constexpr 索引。


小测验

问题 1
std::array 初始化字符 'h','e','l','l','o'

  • 输出数组长度;
  • 分别用 operator[]at()std::get() 打印下标 1 的元素。

期望输出:

The length is 5
eee

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

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

公众号二维码

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