第 18.16 節

std::span

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

你要寫一個函數,處理一段連續數據——可以是 vector、array、C 數組。在 C++20 之前,你有兩種選擇:

  1. const std::vector<T>&:只能接收 vector,不能接收 array 或 C 數組。
  2. const T* data, size_t size:能接收所有,但丟失了大小信息,容易寫錯。

std::span 提供了統一的只讀(或可寫)視圖,不擁有數據,只是一個"窗口",可以統一處理所有連續內存容器。

這個特性是什麼

std::span<T> 是 C++20 引入的視圖類型,它不擁有數據,只是指向一段連續內存的指針 + 大小的輕量包裝。你可以把它理解為"對數組的引用"。

C++ 標準版本

C++20

需要的頭文件

#include <span>

基本語法

// span 不拥有数据,只是观察
std::span<T> sp;             // 空 span
std::span<T> sp(ptr, size);  // 从指针 + 大小构造
std::span<T> sp(vector);     // 从 vector 构造
std::span<T> sp(array);      // 从 std::array 构造
std::span<T> sp(c_array);    // 从 C 数组构造

// 常用操作
sp.size();      // 元素个数
sp.empty();     // 是否为空
sp[i];          // 下标访问
sp.front();     // 第一个元素
sp.back();      // 最后一个元素
sp.data();      // 底层指针
sp.subspan(pos, count);  // 子视图
sp.first(n);    // 前 n 个元素的子视图
sp.last(n);     // 后 n 个元素的子视图

常用用法

用法說明
void f(std::span<const int> data)接收任何連續 int 容器
std::span<int>(vec)從 vector 創建
std::span<int>(arr)從 C 數組創建
sp.subspan(offset, count)創建子視圖(無拷貝)

示例代碼

示例 1:用 span 統一接收不同容器類型

#include <iostream>
#include <span>
#include <vector>
#include <array>

// 一个函数处理所有类型的连续 int 数据
void print_data(std::span<const int> data)
{
    std::cout << "size = " << data.size() << ": ";
    for (int n : data)
    {
        std::cout << n << " ";
    }
    std::cout << "\n";
}

int main()
{
    int c_arr[] = {1, 2, 3, 4, 5};              // C 数组
    std::vector<int> vec = {10, 20, 30};         // vector
    std::array<int, 4> arr = {100, 200, 300, 400};  // std::array

    print_data(c_arr);   // ✅ C 数组
    print_data(vec);      // ✅ vector
    print_data(arr);      // ✅ std::array

    return 0;
}

運行結果

size = 5: 1 2 3 4 5 
size = 3: 10 20 30 
size = 4: 100 200 300 400 

示例 2:在示例 1 基礎上,span 的子視圖(subspan)

#include <iostream>
#include <span>
#include <vector>

int main()
{
    std::vector<int> vec = {10, 20, 30, 40, 50, 60};

    std::span<int> sp(vec);

    // 取中间 3 个元素(从索引 1 开始,取 3 个)
    auto mid = sp.subspan(1, 3);
    std::cout << "mid: ";
    for (int n : mid) std::cout << n << " ";
    std::cout << "\n";

    // 取前 2 个
    auto first2 = sp.first(2);
    std::cout << "first 2: ";
    for (int n : first2) std::cout << n << " ";
    std::cout << "\n";

    // 取后 3 个
    auto last3 = sp.last(3);
    std::cout << "last 3: ";
    for (int n : last3) std::cout << n << " ";
    std::cout << "\n";

    // 子视图修改会影响原数据!
    mid[0] = 999;
    std::cout << "after modifying mid[0] = 999\n";
    std::cout << "original vec[1] = " << vec[1] << "\n";

    return 0;
}

運行結果

mid: 20 30 40 
first 2: 10 20 
last 3: 40 50 60 
after modifying mid[0] = 999
original vec[1] = 999

示例 3:在示例 2 基礎上,寫一個求和的函數

#include <iostream>
#include <span>
#include <vector>

// 计算 span 中所有元素的和
double sum(std::span<const double> data)
{
    double total = 0.0;
    for (double x : data)
    {
        total += x;
    }
    return total;
}

// 计算 span 中所有元素的平均值
double average(std::span<const double> data)
{
    if (data.empty()) return 0.0;
    return sum(data) / data.size();
}

int main()
{
    std::vector<double> scores = {85.5, 92.0, 78.5, 90.0, 88.0};
    double c_arr[] = {1.1, 2.2, 3.3};

    std::cout << "sum(scores) = " << sum(scores) << "\n";
    std::cout << "avg(scores) = " << average(scores) << "\n";
    std::cout << "sum(c_arr) = " << sum(c_arr) << "\n";
    std::cout << "avg(c_arr) = " << average(c_arr) << "\n";

    return 0;
}

運行結果

sum(scores) = 434
avg(scores) = 86.8
sum(c_arr) = 6.6
avg(c_arr) = 2.2

運行結果

見上方每個示例的"運行結果"。

示例中的關鍵語法解釋

示例講了什麼新出現的語法為什麼這樣寫注意事項
示例 1統一接收不同容器std::span<const int> 作為參數一個函數處理 vector/array/C 數組span 只是視圖,不擁有數據
示例 2子視圖操作subspan()first()last()零拷貝切片,可以局部處理大數組子視圖修改影響原數據
示例 3用 span 寫通用函數sum(std::span<const double>)通用算法 + 任意連續容器函數內部用 range-for 和 .size() 即可

span<const T>span<T> 的區別

寫法能否修改底層數據常見用途
std::span<const T>不能修改只讀處理、統計、打印、校驗
std::span<T>可以修改填充緩衝區、歸一化數據、原地濾波

span 不擁有數據,所以“是否 const”非常重要。函數參數默認優先寫 std::span<const T>,只有明確要修改原數據時才寫 std::span<T>

示例 4:只讀 span 和可寫 span

#include <iostream>
#include <span>
#include <vector>

void print(std::span<const int> data)
{
    for (int n : data)
    {
        std::cout << n << " ";
    }
    std::cout << "\n";
}

void add_offset(std::span<int> data, int offset)
{
    for (int& n : data)
    {
        n += offset;
    }
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<int> values = {10, 20, 30};

    print(values);
    add_offset(values, 5);
    print(values);

    return 0;
}

運行結果

10 20 30
15 25 35

print 只讀,所以用 span<const int>add_offset 要原地修改,所以用 span<int>。這比傳 vector<int>& 更通用,因為它也可以接收 array 和 C 數組。

常見錯誤

錯誤 1:span 比它引用的數據活得久

std::span<int> make_span()
{
    std::vector<int> v = {1, 2, 3};
    return std::span<int>(v);  // ❌ v 是局部变量,span 成为悬挂视图!
}

正確做法:span 只是視圖,必須確保底層數據比 span 活得久。

錯誤 2:用 span 接收臨時對象

std::span<const int> sp = std::vector<int>{1, 2, 3};  // ❌ 临时对象已销毁!

正確做法:先創建具名的容器,再傳給 span。

錯誤 3:認為 span 可以用於不連續容器

std::list<int> lst = {1, 2, 3};
std::span<int> sp(lst);  // ❌ list 不连续!

正確做法:span 只適用於連續內存容器(vector、array、C 數組、deque 的部分場景)。

使用建議

  1. 函數參數優先用 std::span<const T>:代替 const vector<T>&,更通用。
  2. span 只是視圖:不擁有數據,必須確保底層數據存活。
  3. 子視圖零拷貝subspan 等操作非常高效。
  4. C++20 才有 span:如果你的項目用 C++17,可以用 std::string_view(字符串版的 span)或第三方庫。
  5. 只讀參數優先 span<const T>:這樣 vector、array、C 數組都能傳,而且不會被函數修改。

小結

  • std::span<T> 是不擁有數據的連續內存視圖。
  • 可以統一接收 vector、array、C 數組——一個參數類型覆蓋所有。
  • 支持子視圖切片(subspanfirstlast),零拷貝。
  • span 的生命週期不能超過底層數據。
  • 只適用於連續內存容器。
音乐页