原生数组和std::array异同

目录

1.概述

2.std::array用法详解

2.1.构造std::array

2.2.元素访问

2.3.迭代器

2.4.容量

2.5.修改器

3.std::array的存储

4.std::get访问元素

5.std::swap交换

6.std::to_array

7.std::tuple_size

8.std::tuple_element

9.总结


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的不确定值)。聚合初始化就是从初始化器列表来初始化聚合体,其也是列表初始化的一种方式

聚合初始化 - cppreference.com

聚合初始化的语法如下:

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访问元素

C++之std::tuple(一) : 使用精讲(全)-CSDN博客

C++三剑客之std::variant(一) : 使用_std::variant用法-CSDN博客

        在讲解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 起

交换 lhsrhs 的内容。调用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必须是常量表达式(即编译时已知);不支持大小更改操作(调整大小、插入、擦除等)。

参考:

std::array - cppreference.com

  • 38
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值