C++20中的span容器怎么使用

寻技术 C/C++编程 2023年07月11日 99

这篇“C++20中的span容器怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C++20中的span容器怎么使用”文章吧。

    一.span容器

    span
    是 C++20 中引入的一个新的标准容器,它用于表示连续的一段内存区间,类似于一个轻量级的只读数组容器。

    span
    是一个轻量级非拥有式容器,它提供了对连续内存的引用
    span
    的主要用途是作为函数参数,可以避免不必要的内存拷贝,并且可以防止悬垂指针和空指针引用的问题

    它的定义在头文件 <

    span>
    中,并位于
    std
    命名空间中。
    span
    包含了一个指向连续内存区域的指针以及它所占用的大小,可以通过它来访问这个内存区域中的元素。

    span
    主要用于以下场景:
    • 作为函数的参数,用于指示函数需要处理的数据范围;

    • 作为类的成员变量,用于表示对象所管理的内存区域;

    • 作为数组的视图,用于访问数组的一部分

    二.span的用法

    下面是几种 span 的用法示例:

    1.将数组转换为 span:

    int arr[] = {1, 2, 3, 4, 5};
    span<int> s(arr, 5);

    这里将一个整型数组

    arr
    转换为 span 类型,并使用数组首地址和元素个数作为参数。

    2.使用 span 来遍历一个容器:

    vector<int> vec = {1, 2, 3, 4, 5};
    for (auto&& x : span(vec)) {
        cout << x << " ";
    }

    这里使用

    span(vec)
    来构造一个 span 对象,遍历其中的元素并输出。

    3.使用 span 来获取子序列:

    int arr[] = {1, 2, 3, 4, 5};
    span<int> s(arr, 5);
    auto s1 = s.subspan(1, 3);

    这里将一个 span 对象

    s
    分割为从第 1 个元素开始,长度为 3 的子序列,并将结果存储到
    s1
    中。

    4.将 span 转换为其他容器类型:

    int arr[] = {1, 2, 3, 4, 5};
    span<int> s(arr, 5);
    vector<int> vec(s.begin(), s.end());

    这里使用

    s.begin()
    s.end()
    将 span 对象
    s
    转换为迭代器范围,并使用这个迭代器范围构造一个 vector 容器
    vec

    三.span的底层原理

    下面为 span的简化版源码,用于展示其基本实现:

    template<typename T, std::size_t Extent = std::dynamic_extent>
    class span {
    public:
        // 定义迭代器类型
        using iterator = T*;
        using const_iterator = const T*;
     
        // 构造函数
        constexpr span() noexcept : data_(nullptr), size_(0) {}
        constexpr span(T* ptr, std::size_t count) : data_(ptr), size_(count) {}
        template <std::size_t N>
        constexpr span(T(&arr)[N]) noexcept : data_(arr), size_(N) {}
        template <typename Container>
        constexpr span(Container& c) noexcept : data_(c.data()), size_(c.size()) {}
     
        // 拷贝构造函数和拷贝赋值运算符
        constexpr span(const span& other) noexcept = default;
        span& operator=(const span& other) noexcept = default;
     
        // 访问元素和迭代器操作
        constexpr T* data() const noexcept { return data_; }
        constexpr std::size_t size() const noexcept { return size_; }
        constexpr bool empty() const noexcept { return size_ == 0; }
        constexpr T& operator[](std::size_t idx) const { return data_[idx]; }
        constexpr T& front() const { return data_[0]; }
        constexpr T& back() const { return data_[size_-1]; }
        constexpr iterator begin() const noexcept { return data_; }
        constexpr iterator end() const noexcept { return data_ + size_; }
        constexpr const_iterator cbegin() const noexcept { return data_; }
        constexpr const_iterator cend() const noexcept { return data_ + size_; }
     
    private:
        T* data_;  // 元素指针
        std::size_t size_;  // 元素数量
    };

    具体实现方式是通过指针来引用连续的一段内存,从而实现 span 的基本功能。由于 span 没有实际的内存所有权,所以它不能拥有或释放内存。它只是提供了对现有内存块的访问。

    标准库中的 span 还提供了一些其他的功能,比如对子区间的切片和子区间的迭代器等。实际的实现可能会更加复杂,但其基本的思想是一致的。

    四.span 与 array ,vector ,数组指针 的区别

    1. span 与 array ,vector的区别

    span
    是 C++20 中新增的一个轻量级容器,用于表示一段连续的内存区域,它不负责管理内存空间,也不会拥有所指向内存的所有权,只是提供一种方便的方式来操作内存区域,因此可以看做是一个只读的“裸指针”。

    array
    vector
    相比,
    span
    主要区别在于它不拥有自己的存储空间,而是引用了另一个数组或容器的内存空间。因此,当我们需要使用一个连续的内存块时,可以使用
    span
    来代替
    array
    vector

    具体来说,

    array
    是一个固定大小的数组容器,其大小在编译时就确定了,不能动态改变。
    vector
    是一个动态增长的数组容器,可以动态分配内存,并在需要时扩大容量。而
    span
    是一个非拥有型的容器,可以看作是一个指向连续内存区域的引用,可以指向任何类型的元素。

    在使用方面,

    array
    vector
    可以用来存储数据,并通过下标或迭代器来访问其中的元素;
    span
    则更多地用来表示一段内存区域
    并提供类似于迭代器的操作来访问其中的元素(就是 只读),如
    begin
    end
    rbegin
    rend
    等。

    总之,

    span
    array
    vector
    三者各有所长,可以根据实际需求来选择使用。

    2. span 与 数组指针的区别

    在C++中,数组和指针是密不可分的,它们常常被一起使用。然而,数组和指针不是相同的东西,它们有自己的属性和限制。同样地,

    span
    和指针也有很多区别,这里列举几点:

    span
    是一个封装了数组指针和长度的轻量级容器,它提供了对数组的安全访问。指针只是一个指向内存位置的地址,没有长度信息。因此,使用指针时需要显式地传递长度信息,否则可能会导致缓冲区溢出等问题。

    span
    支持范围操作,它可以使用STL中的算法和其他支持范围操作的库进行操作。指针只能通过指针运算和下标操作来访问和操作数据。

    span
    是可传递性的,可以传递到函数中作为参数,而指针不能。这是因为在函数中传递指针时,我们必须显式地传递指针所指向的内存块的大小,否则函数无法确定内存块的大小。

    span
    是一个类模板,可以指定数据类型和长度类型。指针只能指向特定类型的数据。

    总的来说,

    span
    比指针更安全,更灵活,更易于使用,是一种更好的数组容器类型。

    五.span的优点

    std::span
    的主要优点如下:

    轻量级:

    std::span
    本身只是一个轻量级的非拥有式容器,没有自己的内存管理,因此可以在不分配内存的情况下轻松地传递和操作数据。同时,
    std::span
    的内存布局与原始数组相同,因此不需要进行数据的复制或重排。

    安全性:

    std::span
    具有边界检查机制,可以避免访问越界等错误,从而提高代码的安全性。

    可组合性:

    std::span
    可以与其他容器类型进行组合,例如可以从
    std::vector
    std::array
    中创建
    std::span
    ,或将
    std::span
    转换为
    std::vector
    std::array

    易于扩展:由于

    std::span
    只是一个非拥有式容器,因此可以轻松地将其用作接口的一部分,并以此扩展接口的功能。

    总之,

    std::span
    是一个非常实用的工具,可以方便地对数据进行访问和处理,同时也可以提高代码的可读性、可维护性和安全性。
    关闭

    用微信“扫一扫”