目录
1.概述
std::array
是C++容器库提供的一个固定大小数组的容器。其与内置的数组相比,是一种更安全、更容易使用的数组类型。std::array
在头文件<array>中定义,其声明如下:
template<
class T,
std::size_t N
> struct array; //C++11 起
std::array
是一个聚合类型,其语义等同于保有一个C语言风格数组T[N]
作为其唯一非静态数据成员的结构体,但其不同于C数组的是它不会自动退化为T*
。同时该结构体结合了C风格数组的性能、可访问性和容器的优点(可获取大小、支持赋值和随机访问等)。
2.std::array用法详解
2.1.构造std::array
std::array的构造遵循聚合初始化的规则初始化 array(注意默认初始化可以导致非类的T的不确定值)。聚合初始化就是从初始化器列表来初始化聚合体,其也是列表初始化的一种方式。
聚合初始化的语法如下:
std::array只能用聚合初始化来构造。示例如下:
std::array<int, 5> arrayInt{ 11, 1, 8, 5, 50 };
int* pData = arrayInt.data();
std::sort(arrayInt.begin(), arrayInt.end());
2.2.元素访问
operator[ ]
定义如下:
示例代码如下:
#include <array>
#include <iostream>
int main()
{
std::array<int,4> numbers{2, 4, 6, 8};
std::cout << "Second element: " << numbers[1] << '\n';
numbers[0] = 5;
std::cout << "All numbers:";
for (auto i : numbers)
std::cout << ' ' << i;
std::cout << '\n';
}
at
定义如下:
at用于访问指定的元素,同时进行越界检查,该函数返回位于指定位置pos的元素的引用,如果pos不在容器的范围内,则抛出std::out_of_range
异常。示例代码如下:
std::array<int,3> data = { 11, 23, 388};
std::cout<<data.at(1)<<std::endl; //23
data.at(1)=18; //此时data={11, 18, 388}
data.at(6) = 6; //越界,抛出std::out_of_range异常
front:用于访问容器的第一个元素,其返回值为容器首元素的引用。比较简单就不举例了。
back: 主要功能是用来访问容器最后一个元素,其返回值为容器最后一个元素的引用。比较简单就不举例了。
data
定义如下:
返回指向作为元素存储工作的底层数组的指针。返回的指针使得范围 [
data(),
data() +
size())
始终是有效范围,即使容器为空(此时 data()
不可解引用)。
示例如下:
#include <cstddef>
#include <iostream>
#include <span>
#include <array>
void pointer_func(const int* p, std::size_t size)
{
std::cout << "data = ";
for (std::size_t i = 0; i < size; ++i)
std::cout << p[i] << ' ';
std::cout << '\n';
}
void span_func(std::span<const int> data) // C++20 起
{
std::cout << "data = ";
for (const int e : data)
std::cout << e << ' ';
std::cout << '\n';
}
int main()
{
std::array<int, 4> container{1, 2, 3, 4};
// container.data() 优于 &container[0]
pointer_func(container.data(), container.size());
// std::span (C++20) 相比分开的指针/大小是更安全的选择。
span_func({container.data(), container.size()});
}
2.3.迭代器
同STL中的其它容器一样,同样有正向迭代器begin、end和cbegin、cend和反向迭代器rbegin、rend和crbegin、crend。
begin和cbegin返回指向deque首元素的迭代器,end和cend返回指向deque末元素后一元素的迭代器。如果array为空,则返回的迭代器将等于end或cend。end和cend指向deque末元素后一元素的迭代器,该元素的表现为占位符,试图访问它将导致未定义行为。
示例如下:
#include <algorithm>
#include <array>
#include <iomanip>
#include <iostream>
int main()
{
std::cout << std::boolalpha;
std::array<int, 0> empty;
std::cout << "1) "
<< (empty.begin() == empty.end()) << ' ' // true
<< (empty.cbegin() == empty.cend()) << '\n'; // true
// *(empty.begin()) = 42; // => 运行时的未定义行为
std::array<int, 4> numbers{5, 2, 3, 4};
std::cout << "2) "
<< (numbers.begin() == numbers.end()) << ' ' // false
<< (numbers.cbegin() == numbers.cend()) << '\n' // false
<< "3) "
<< *(numbers.begin()) << ' ' // 5
<< *(numbers.cbegin()) << '\n'; // 5
*numbers.begin() = 1;
std::cout << "4) " << *(numbers.begin()) << '\n'; // 1
// *(numbers.cbegin()) = 42; // 编译时错误:
// 只读变量不可赋值
// 打印所有元素
std::cout << "5) ";
std::for_each(numbers.cbegin(), numbers.cend(), [](int x)
{
std::cout << x << ' ';
});
std::cout << '\n';
constexpr std::array constants{'A', 'B', 'C'};
static_assert(constants.begin() != constants.end()); // OK
static_assert(constants.cbegin() != constants.cend()); // OK
static_assert(*constants.begin() == 'A'); // OK
static_assert(*constants.cbegin() == 'A'); // OK
// *constants.begin() = 'Z'; // 编译时错误:
// 只读变量不可赋值
}
rbegin和crbegin返回指向array首元素的逆向迭代器。它对应非逆向array的末元素,若array为空,则返回的迭代器等于rend或crend。rend和crend返回指向逆向deque末元素后一元素的逆向迭代器,它对应非逆向array首元素的前一元素,此元素表现为占位符,试图访问它导致未定义行为。
示例如下:
#include <algorithm>
#include <array>
#include <iostream>
#include <string>
#include <string_view>
int main()
{
constexpr std::array<std::string_view, 8> data = {"▁","▂","▃","▄","▅","▆","▇","█"};
std::array<std::string, std::size(data)> arr;
std::copy(data.cbegin(), data.cend(), arr.begin());
// ^ ^ ^
auto print = [](const std::string_view s) { std::cout << s << ' '; };
print("Print 'arr' in direct order using [cbegin, cend):\t");
std::for_each(arr.cbegin(), arr.cend(), print);
// ^ ^
print("\n\nPrint 'arr' in reverse order using [crbegin, crend):\t");
std::for_each(arr.crbegin(), arr.crend(), print);
// ^^ ^^
print("\n");
}
2.4.容量
empty() : 检查容器是否无元素,即是否 begin() == end()。
size() : 返回容器中的元素数,即 std::distance(begin(), end())。
max_size() : 返回根据系统或库实现限制的容器可保有的元素最大数量,即对于最大容器的 std::distance(begin(), end())。
2.5.修改器
fill
定义如下:
示例如下:
#include <array>
#include <cstddef>
#include <iostream>
int main()
{
constexpr std::size_t xy = 4;
using Cell = std::array<unsigned char, 8>;
std::array<Cell, xy * xy> board;
board.fill({0xE2, 0x96, 0x84, 0xE2, 0x96, 0x80, 0, 0}); // "▄▀";
for (std::size_t count{}; Cell c : board)
std::cout << c.data() << ((++count % xy) ? "" : "\n");
}
swap
定义如下:
示例如下:
std::array<int, 3> a1{1, 2, 3}, a2{4, 5, 6};
auto it1 = a1.begin(); //*it1 = 1
auto it2 = a2.begin(); //*it2 = 4
int &ref1 = a1[1]; // ref1 = 2
int &ref2 = a2[1]; // ref1 = 5
std::cout << *it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
// 打印结果为1 4 2 5
a1.swap(a2);
// 此时a1 = {4, 5, 6},a2 = {1, 2, 3}
std::cout << *it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
// 打印结果为4 1 5 2
/*注:
交换后迭代器与引用保持与原 array 关联,
例如it1仍指向元素 a1[0] ,ref1仍指代 a1[1] */
3.std::array的存储
翻看std::array(vs2019)的内部实现:
template <class _Ty, size_t _Size>
class array { // fixed size array of values
public:
using value_type = _Ty;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = _Ty*;
using const_pointer = const _Ty*;
using reference = _Ty&;
using const_reference = const _Ty&;
using iterator = _Array_iterator<_Ty, _Size>;
using const_iterator = _Array_const_iterator<_Ty, _Size>;
using reverse_iterator = _STD reverse_iterator<iterator>;
using const_reverse_iterator = _STD reverse_iterator<const_iterator>;
...
_NODISCARD _CONSTEXPR17 _Ty* data() noexcept {
return _Elems;
}
_NODISCARD _CONSTEXPR17 const _Ty* data() const noexcept {
return _Elems;
}
[[noreturn]] void _Xran() const {
_Xout_of_range("invalid array<T, N> subscript");
}
_Ty _Elems[_Size];
};
从上述代码可以看出,std::array内部就是用的原生数组存储数据,所示说在某些情况下std::array可以替换原生数组。知道其内部存储原理,不看源码就很容易猜到std::array的内部接口实现。
4.std::get访问元素
在讲解std::tuple、std::variant时,我们可以通过std::get<N>访问值它们的元素值。同样也可以用std::get来访问std::array的值。
由于std::array内部结构比较简单,std::get在std::array的实现也比较简单,实现如下:
template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr _Ty& get(array<_Ty, _Size>& _Arr) noexcept {
static_assert(_Idx < _Size, "array index out of bounds");
return _Arr._Elems[_Idx];
}
template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr const _Ty& get(const array<_Ty, _Size>& _Arr) noexcept {
static_assert(_Idx < _Size, "array index out of bounds");
return _Arr._Elems[_Idx];
}
template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr _Ty&& get(array<_Ty, _Size>&& _Arr) noexcept {
static_assert(_Idx < _Size, "array index out of bounds");
return _STD move(_Arr._Elems[_Idx]);
}
template <size_t _Idx, class _Ty, size_t _Size>
_NODISCARD constexpr const _Ty&& get(const array<_Ty, _Size>&& _Arr) noexcept {
static_assert(_Idx < _Size, "array index out of bounds");
return _STD move(_Arr._Elems[_Idx]);
}
示例代码如下:
#include <array>
#include <iostream>
int main()
{
std::array<int, 3> arr;
// 设置值:
std::get<0>(arr) = 1;
std::get<1>(arr) = 2;
std::get<2>(arr) = 3;
// 获取值:
std::cout << '(' << std::get<0>(arr)
<< ',' << std::get<1>(arr)
<< ',' << std::get<2>(arr)
<< ")\n";
}
5.std::swap交换
std::swap(std::array)
函数是为std::array
特化std::swap
算法。其函数声明如下:
template< class T, std::size_t N >
void swap( std::array<T, N>& lhs,
std::array<T, N>& rhs ); //C++11 起, C++17 前
template< class T, std::size_t N >
void swap( std::array<T, N>& lhs,
std::array<T, N>& rhs ) noexcept(); //C++17 起, C++20 前
template< class T, std::size_t N >
constexpr void swap( std::array<T, N>& lhs,
std::array<T, N>& rhs ) noexcept(); //C++20 起
交换 lhs
与 rhs
的内容。调用lhs.swap(rhs)
。其具体用法如下:
std::array<int, 3> a1{1, 2, 3}, a2{4, 5, 6};
auto it1 = a1.begin(); //*it1 = 1
auto it2 = a2.begin(); //*it2 = 4
int &ref1 = a1[1]; // ref1 = 2
int &ref2 = a2[1]; // ref1 = 5
std::cout << *it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
// 打印结果为1 4 2 5
std::swap(a1, a2);
// 此时a1 = {4, 5, 6},a2 = {1, 2, 3}
std::cout << *it1 << ' ' << *it2 << ' ' << ref1 << ' ' << ref2 << '\n';
// 打印结果为4 1 5 2
6.std::to_array
std::to_array
函数声明如下:
template<class T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N]); //C++20 起
template<class T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&&a)[N]); //C++20 起
std::to_array
函数可以从一维内建数组 a
创建 std::array
对象,从 a
的对应元素复制初始化 std::array
的元素。不支持复制或移动多维内建数组。其实 to_array 实现起来也不是很难,就需要在编译的时候把a[N]的一个个元素取出来再用聚合初始化{}来构造出一个std::array,这个时候就要用到了std::index_sequence和std::make_index_sequence:
C++14之std::index_sequence和std::make_index_sequence_std::make_index_sequence<4> 的递归展开过程-CSDN博客
翻看源码(vs2019)的具体实现也的确如此:
template <class _Ty, size_t _Size, size_t... _Idx>
_NODISCARD constexpr array<remove_cv_t<_Ty>, _Size> _To_array_lvalue_impl(
_Ty (&_Array)[_Size], index_sequence<_Idx...>) {
return {{_Array[_Idx]...}};
}
template <class _Ty, size_t _Size, size_t... _Idx>
_NODISCARD constexpr array<remove_cv_t<_Ty>, _Size> _To_array_rvalue_impl(
_Ty(&&_Array)[_Size], index_sequence<_Idx...>) {
return {{_STD move(_Array[_Idx])...}};
}
template <class _Ty, size_t _Size>
_NODISCARD constexpr array<remove_cv_t<_Ty>, _Size> to_array(_Ty (&_Array)[_Size]) {
static_assert(!is_array_v<_Ty>, "N4830 [array.creation]/1: "
"to_array does not accept multidimensional arrays.");
static_assert(is_constructible_v<_Ty, _Ty&>, "N4830 [array.creation]/1: "
"to_array requires copy constructible elements.");
return _To_array_lvalue_impl(_Array, make_index_sequence<_Size>{});
}
template <class _Ty, size_t _Size>
_NODISCARD constexpr array<remove_cv_t<_Ty>, _Size> to_array(_Ty(&&_Array)[_Size]) {
static_assert(!is_array_v<_Ty>, "N4830 [array.creation]/4: "
"to_array does not accept multidimensional arrays.");
static_assert(is_move_constructible_v<_Ty>, "N4830 [array.creation]/4: "
"to_array requires move constructible elements.");
return _To_array_rvalue_impl(_STD move(_Array), make_index_sequence<_Size>{});
}
to_array的具体实现函数_To_array_rvalue_impl就是这么干的。
其具体用法如下:
#include <array>
#include <memory>
#include <string_view>
#include <type_traits>
#include <utility>
// 创建 string_view 的 constexpr 数组
constexpr auto w1n = std::to_array<std::string_view>({
"Mary", "Patricia", "Linda", "Barbara", "Elizabeth", "Jennifer"
});
static_assert(std::is_same_v<decltype(w1n), const std::array<std::string_view, 6>>);
static_assert(w1n.size() == 6 and w1n[5] == "Jennifer");
int main()
{
// 复制字符串字面量
auto a1 = std::to_array("foo");
static_assert(a1.size() == 4);
// 推导元素类型和长度
auto a2 = std::to_array({0, 2, 1, 3});
static_assert(std::is_same_v<decltype(a2), std::array<int, 4>>);
// 推导长度而元素类型指定
// 发生隐式转换
auto a3 = std::to_array<long>({0, 1, 3});
static_assert(std::is_same_v<decltype(a3), std::array<long, 3>>);
auto a4 = std::to_array<std::pair<int, float>>(
{{3, 0.0f}, {4, 0.1f}, {4, 0.1e23f}});
static_assert(a4.size() == 3);
// 创建不可复制的 std::array
auto a5 = std::to_array({std::make_unique<int>(3)});
static_assert(a5.size() == 1);
// 错误:不支持复制多维数组
// char s[2][6] = {"nice", "thing"};
// auto a6 = std::to_array(s);
}
7.std::tuple_size
它的声明如下:
template< class T, std::size_t N >
struct tuple_size< std::array<T, N> > :
std::integral_constant<std::size_t, N> //C++11 起
{ };
其提供作为编译时常量表达式访问std::array
中元素数量的方法。用法示例如下:
#include <iostream>
#include <array>
template<class T>
void test(T t)
{
int a[std::tuple_size<T>::value]; // 能用于编译时
std::cout << std::tuple_size<T>::value << '\n';
}
int main()
{
std::array<float, 3> arr;
test(arr); //输出 3
}
8.std::tuple_element
它的声明如下:
template< std::size_t I, class T, std::size_t N >
struct tuple_element<I, std::array<T, N> >; //C++11 起
使用类 tuple 接口,提供 array 元素类型的编译时带下标访问。具体使用方法如下:
#include <array>
#include <tuple>
#include <type_traits>
int main()
{
// 定义 array 并获取位于位置 0 的元素类型
std::array<int, 10> data{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
using T = std::tuple_element<0, decltype(data)>::type; // int
static_assert(std::is_same_v<T, int>);
const auto const_data = data;
using CT = std::tuple_element<0, decltype(const_data)>::type; // const int
// tuple_element 的结果取决于 tuple 式类型的 cv 限定
static_assert(!std::is_same_v<T, CT>);
static_assert(std::is_same_v<CT, const int>);
}
9.总结
std::array的优点:
固定大小:std::array的大小在编译时确定,这有助于避免运行时的大小检查和可能的性能开销。
更好的类型安全:与原生数组相比,std::array提供了更好的类型安全,因为它是一个类模板,而不是简单的数组类型。
支持STL算法:由于std::array提供了迭代器接口,因此可以很方便地与STL算法一起使用。
成员函数和接口:std::array提供了一系列有用的成员函数,如size(), empty(), begin(), end()等,这些功能在原生数组上是没有的。
std::array的缺点:
性能开销:如果元素类型具有较高的复制/分配成本,则可能会变慢(重新排序元素需要复制/移动它们)。
不能动态变化大小:虽然固定大小在某些情况下是一个优点,但它也限制了std::array的灵活性。如果需要动态大小的容器,那么应该考虑使用std::vector或其他动态数组类型;在使用array容器的时候,其size必须是常量表达式(即编译时已知);不支持大小更改操作(调整大小、插入、擦除等)。
参考: