C++ STL 容器详解:std::array 完全指南
固定大小数组的现代 C++ 封装,兼顾性能与安全
一、概述
std::array 是 C++11 标准库中引入的容器,它封装了固定大小的原生数组,提供了 STL 容器的通用接口(如迭代器、大小查询、边界检查等),同时保持了原生数组的零开销特性。
头文件: <array>
名字空间:std
核心特点:
- 数据结构: 数组,空间连续,大小固定。
- 固定大小:对象定义时大小确定,跟普通的数组一样性质,不可动态增长或缩减。
- 连续存储:元素在内存中连续排列,兼容 C 风格 API(通过
data()获取首地址)。- 零开销:性能与原生数组完全相同,不引入额外运行时负担。即数据个数多于数组元素数量,越界。
二、类模板参数
类模板声明:
template <class T, std::size_t N >
struct array;
T: 元素类型
N: 元素个数(编译期常量,类型为std::size_t)
std::array 的内部实现通常类似于:
template<typename T, size_t N>
struct array {
T _M_elems[N]; // 原生数组
// ... 成员函数
};
由于是聚合类型,没有虚函数、没有私有成员,因此对象大小就是
N * sizeof(T)。
三、初始化方法
3.1 默认初始化
std::array<int, 5> arr1;
std::array<int, 5> arr2 = {};
arr1:默认构造函数,未指定初始值,元素值不确定(局部变量)
arr2:全部元素零初始化:0 0 0 0 0
3.2 聚合初始化(推荐)
聚合初始化即大括号{ }形式初始化,保持原生数组的特点。 int a[5] = {1,2,3,4,5};
方式1:等号 + 花括号,初始化部分元素,剩余元素默认初始化0
std::array<int, 5> a1 = {1, 2, 3};
a1:1 2 3 0 0
初始化部分元素,剩余元素默认初始化0。
方式2:直接花括号(C++14起单层即可)
std::array<int, 3> a2 {1, 2, 3}; // 1 2 3
std::array<int, 3> a3 {{1, 2, 3}}; // 1 2 3
C++11使用双层大括号,C++14后单层双层均可,一般不用双层大括号,麻烦。
方式3:二维数组:3 x 2。3行2列。
std::array<std::array<int, 2>, 3> a4 = {1,2,3,4,5,6};
可以理解成如下:
1 2
3 4
5 6
3.3 拷贝/移动构造
函数原型:
array() noexcept; //默认构造
array(const array& other); //拷贝构造
array(array&& other) noexcept; //移动构造
示例:
std::array<int, 4> a1 = {1,2,3,4}; // 聚合构造
std::array<int, 4> a2 = src; // 拷贝构造
std::array<int, 4> a3 = std::move(src); // 移动构造(元素逐移动)
3.4 重新赋值:赋值运算符:=
std::array<int, 6> a = { 1,2,3,4,5,6 };
a = { 7,8,9,6,5,3 }; //重新赋值,不能超过6个元素
注意:不能超过元素个数。a是6个元素的数组,最大赋值不能超过6个。
四、元素获取函数
4.1 元素引用:at( )
带边界检查的访问,越界抛出
std::out_of_range
函数原型:
T& at(size_type pos);
const T& at(size_type pos) const;
示例:
int main(void)
{
std::array<int, 6> a1 = { 1,2,3,4,5,6 };
cout << a1.at(2) << endl; // 3
a1.at(2) = 10; // 1,2,10,4,5,6
// a1.at(7) = 56; // 越界,严重错误
std::array<std::array<int, 2>, 3> a2{ 1, 2, 3, 4, 5, 6};
cout << a2.at(2).at(0) << endl; // 5
a2.at(2).at(0) = 10; // 1, 2, 3, 4, 10, 6
return 0;
}
4.2 元素引用:下标运算符:[ ]
函数原型:
T& operator[](size_type pos);
const T& operator[](size_type pos) const;
示例:
int main(void)
{
std::array<int, 6> a1 = { 1,2,3,4,5,6 };
cout << a1[2] << endl; // 3
a1[2] = 10; // 1,2,10,4,5,6
// a1[7] = 56; // 越界,严重错误
std::array<std::array<int, 2>, 3> a2{ 1, 2, 3, 4, 5, 6};
cout << a2[2][0] << endl; // 5
a2[2][0] = 10; // 1, 2, 3, 4, 10, 6
return 0;
}
4.3 首元素引用:front( )
函数原型:
T& front();
const T& front() const;
示例:
std::array<int, 6> a1 = { 1,2,3,4,5,6 };
cout << a1.front() << endl; // 1
a1.front() = 10; // 10,2,3,4,5,6
cout << a1.front() << endl; // 10
4.4 尾元素引用:back( )
函数原型:
T& back();
const T& back() const;
示例:
std::array<int, 6> a1 = { 1,2,3,4,5,6 };
cout << a1.back() << endl; // 6
a1.back() = 10; // 1,2,3,4,5,10
cout << a1.back() << endl; // 10
4.5 数组首地址:data( )
返回指向底层数组的首地址。
函数原型:
T* data() noexcept;
const T* data() const noexcept;
示例:
std::array<int, 6> a1 = { 1,2,3,4,5,6 };
int* p = a1.data();
for (int i = 0; i < 5; i++)
cout << p[i] << ' '; // 1 2 3 4 5
五、迭代器
iterator : 正向迭代器
const_iterator : 正向常量迭代器
reverse_iterator :反向迭代器
const_reverse_iterator :反向常量迭代器
5.1 array迭代器运算符:
1. 解引用运算符:* , ->
| 运算符 | 说明 |
|---|---|
*it | 返回迭代器当前指向元素的引用 |
-> | 元素是(类)类型,访问元素的成员 |
2. 递增/递减运算符: ++ , --
| 运算符 | 说明 |
|---|---|
++it | 前置递增,迭代器指向下一个元素,返回新迭代器的引用 |
it++ | 后置递增,迭代器指向下一个元素,返回原迭代器的副本 |
--it | 前置递减,迭代器指向前一个元素,返回新迭代器的引用 |
it-- | 后置递减,迭代器指向前一个元素,返回原迭代器的副本 |
3. 算术运算符: + , - , += , -=
| 运算符 | 说明 |
|---|---|
it + n | 返回迭代器向后移动 n 个位置的新迭代器 |
n + it | 同上 |
it - n | 返回迭代器向前移动 n 个位置的新迭代器 |
it += n | 迭代器向后移动 n 个位置,返回自身引用 |
it -= n | 迭代器向前移动 n 个位置,返回自身引用 |
it1 - it2 | 返回两个迭代器之间的距离(ptrdiff_t),可为负数 |
4. 下标运算符 : [ ]
| 运算符 | 说明 |
|---|---|
it[n] | 等价于 *(it + n),返回偏移 n 个位置的元素的引用 |
5. 比较运算符: == , != , > , < , >= , <=
| 运算符 | 说明 |
|---|---|
== 、!= | 判断两个迭代器是否指向同一位置,同位置返回1,不同返回0 |
< 、 > | 比较迭代器的前后关系(位置),前 < 后 |
<= 、 >= | 小于等于 / 大于等于,前 < 后 |
5.2 正向迭代器:begin() / end()
函数原型:
iterator begin();
iterator end();
可读可写。
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
std::array<int, 6>::iterator itb = a.begin();
auto ite = a.end(); // auto更方便
while (itb != ite) {
std::cout << *itb++ << std::endl;
}
return 0;
}
5.3 常量正向迭代器:cbegin() / cend()
只读。
函数原型:
const_iterator begin() const; 常函数
const_iterator cbegin() const;
const_iterator end() const; 常函数
const_iterator cend() const;
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
std::array<int, 6>::const_iterator itb = a.cbegin();
auto ite = a.cend(); // auto更方便
while (itb != ite) {
std::cout << *itb++ << std::endl;
}
return 0;
}
5.4 反向迭代器:rbegin() / rend()
反向,可读可写。
函数原型:
reverse_iterator rbegin();
reverse_iterator rend();
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
std::array<int, 6>::reverse_iterator itb = a.rbegin();
auto ite = a.rend(); // auto更方便
while (itb != ite) {
std::cout << *itb++ << ' '; // 6 5 4 3 2 1
}
return 0;
}
5.5 常量反向迭代器:crbegin() / crend()
反向,只读。
函数原型:
const_reverse_iterator rbegin() const; 常对象
const_reverse_iterator crbegin() const;
const_reverse_iterator rend() const; 常对象
const_reverse_iterator crend() const;
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
std::array<int, 6>::const_reverse_iterator itb = a.rbegin();
auto ite = a.rend(); // auto更方便
while (itb != ite) {
std::cout << *itb++ << ' '; // 6 5 4 3 2 1
}
return 0;
}
六、其他成员函数
6.1 元素个数:size( )
函数原型:
bool empty() const;
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
cout << a.size() << endl; // 6
cout << a.max_size() << endl; // 6
return 0;
}
6.2 素数个数:max_size( )
返回
N(与size()相同)。
函数原型:
size_type max_size() const;
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
cout << a.size() << endl; // 6
cout << a.max_size() << endl; // 6
return 0;
}
6.3 判断空:empty( )
函数原型:
空返回1,非空返回0
bool empty() const;
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
cout << a.empty() << endl; // 0 非空
std::array<int, 0> d;
cout << d.empty() << endl; // 1 空
return 0;
}
6.4 所有元素赋值:fill( )
函数原型:
将数组所有元素设置为value
void fill( const T& value );
示例:
int main(void)
{
std::array<int, 6> a = { 1,2,3,4,5,6 };
a.fill(8); // 8 8 8 8 8 8
}
6.5 交换对象元素:swap( )
交换两个数组的内容(需同类型同大小)。元素个数不同语法报错。
函数原型:
void swap( array& other );
示例:
std::array<int, 6> a = { 1,2,3,4,5,6 };
std::array<int, 6> c = { 8,8,8,8,8,8 };
a.swap(c); // 元素交换
七、非成员函数
7.1 比较运算符:==, !=, <, <=, >, >=
函数原型:
template< class T, std::size_t N >
bool operator==( const std::array<T, N>& lhs,const std::array<T, N>& rhs );
template< class T, std::size_t N >
bool operator!=( const std::array<T, N>& lhs,const std::array<T, N>& rhs );
template< class T, std::size_t N >
bool operator< ( const std::array<T, N>& lhs,const std::array<T, N>& rhs );
template< class T, std::size_t N >
bool operator<=( const std::array<T, N>& lhs,const std::array<T, N>& rhs );
template< class T, std::size_t N >
bool operator> ( const std::array<T, N>& lhs,const std::array<T, N>& rhs );
template< class T, std::size_t N >
bool operator>=( const std::array<T, N>& lhs,const std::array<T, N>& rhs );
示例:
std::array<int,3> a = {1,2,3};
std::array<int,3> b = {1,2,4};
std::cout << (a < b) << std::endl; // 1 (true)
std::array采用字典序比较。
示例:
结构体类型元素需重载结构体比较的运算符。如下:
struct Node
{
int a;
int d;
};
bool operator>(const Node& l, const Node& r)
{
return (l.a > r.a);
}
bool operator<(const Node& l, const Node& r)
{
return (l.a < r.a);
}
int main(void)
{
std::array<Node, 3> a = { 1,2, 3,4, 5,6};
std::array<Node, 3> c = { 8,8, 8,8, 8,8 };
cout << (a < c) << endl;
return 0;
}
7.2 编译期元素引用:get( )
延伸阅读:
C++17 添加了结构化绑定,可与
std::array配合使用。C++20 的
std::to_array进一步简化了创建过程。拥抱现代 C++,从使用
std::array开始!
函数原型:
namespace std {
// 获取普通左值引用
template<size_t I, class T, size_t N>
constexpr T& get(array<T,N>& arr) noexcept;
// 获取 const 左值引用
template<size_t I, class T, size_t N>
constexpr const T& get(const array<T,N>& arr) noexcept;
// 获取右值引用(移动语义)
template<size_t I, class T, size_t N>
constexpr T&& get(array<T,N>&& arr) noexcept;
// 获取 const 右值引用(极少使用)
template<size_t I, class T, size_t N>
constexpr const T&& get(const array<T,N>&& arr) noexcept;
}
示例:
int main(void)
{
std::array<int, 6> a = { 1,2, 3,4, 5,6};
get<1>(a) = 10; // 赋值
int& d = get<1>(a);
return 0;
}
关键特性:
| 特性 | 说明 |
|---|---|
| 编译期索引 | 索引 必须是编译期常量(如字面量、constexpr 变量),不能是运行时变量 |
| 边界检查 | 索引越界(I >= N)会导致编译错误(而非运行时异常) |
| 返回值类型 | 根据 arr 的值类别返回相应的引用(左值、const 左值、右值引用) |
与 []/at()的区别 | operator[] 使用运行时索引,无边界检查,越界运行异常at(i)运行时索引,需要边界检查std::get 使用编译期索引,越界直接编译失败 |
| noexcept | 始终不抛出异常 |
5.3 std::to_array (C++20)
从内建数组或初始化列表创建
std::array,自动推导大小。
函数原型:
template< class T, std::size_t N >
constexpr std::array<std::remove_cv_t<T>, N> to_array( T (&a)[N] );
template< class T, std::size_t N >
constexpr std::array<std::remove_cv_t<T>, N> to_array( T (&&a)[N] );
示例:
int main() {
// 从 C 风格数组创建
int carr[] = {1, 2, 3, 4};
auto arr1 = std::to_array(carr); // std::array<int, 4>
// 从字面量创建(字符串)
auto arr2 = std::to_array("hello"); // std::array<char, 6> 包含 '\0'
// 从初始化列表创建(需要显式指定类型)
auto arr3 = std::to_array<int>({10, 20, 30}); // std::array<int, 3>
}
八、总结
8.1. 与原生数组的对比
| 特性 | std::array | 原生数组 T[N] |
|---|---|---|
| 大小信息 | 有 size() 成员,编译期常量 | 无,需额外传递 |
| 边界检查 | at() 提供运行时检查 | 无 |
| 赋值/拷贝 | 支持 = 赋值,可拷贝 | 不支持直接赋值 |
| 作为函数参数 | 按值传递(实际是拷贝)或引用传递 | 退化为指针 |
| 与 STL 算法兼容 | 完全兼容(提供迭代器) | 需使用 std::begin/end |
| 性能 | 零开销,等同原生 | 基准 |
| 内存位置 | 栈上(与原生相同) | 栈上 |
8.2. 性能与边界安全最佳实践
- 默认使用
at()用于调试:在开发阶段启用边界检查捕获越界错误;发布版本可回退到operator[]以获得最高性能。- 使用
fill()统一赋值:比手动循环更简洁,且可能被编译器优化。- 利用
std::array作缓冲区:结合data()调用 C 库函数,例如write(fd, arr.data(), arr.size());。- 避免大数组按值传递:如果
N很大,拷贝开销不可忽略,应使用const std::array<T,N>&。
8.3 特点总结
| 适用场景 | 说明 |
|---|---|
| ✅ 需要固定大小的数组 | 编译期已知元素个数 |
| ✅ 需要与 STL 算法无缝协作 | 迭代器接口完备 |
| ✅ 需要边界检查的可选支持 | at() 成员函数 |
| ✅ 追求零开销抽象 | 性能与原生数组完全相同 |
| ❌ 需要动态扩容 | 请使用 std::vector |
| ❌ 大小在运行时才能确定 | 请使用 std::vector 或动态分配 |
std::array 填补了原生数组和 std::vector 之间的空白,是现代 C++ 中固定大小数组的首选类型。尽量用它替代原生数组,以获得类型安全、大小信息自包含以及丰富的成员函数支持。