第 18.16 節
std::span
0瀏覽次數0訪問次數--跳出率--平均停留
本節解決什麼問題
你要寫一個函數,處理一段連續數據——可以是 vector、array、C 數組。在 C++20 之前,你有兩種選擇:
- 傳
const std::vector<T>&:只能接收 vector,不能接收 array 或 C 數組。 - 傳
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 的部分場景)。
使用建議
- 函數參數優先用
std::span<const T>:代替const vector<T>&,更通用。 - span 只是視圖:不擁有數據,必須確保底層數據存活。
- 子視圖零拷貝:
subspan等操作非常高效。 - C++20 纔有 span:如果你的項目用 C++17,可以用
std::string_view(字符串版的 span)或第三方庫。 - 只讀參數優先
span<const T>:這樣 vector、array、C 數組都能傳,而且不會被函數修改。
小結
std::span<T>是不擁有數據的連續內存視圖。- 可以統一接收 vector、array、C 數組——一個參數類型覆蓋所有。
- 支持子視圖切片(
subspan、first、last),零拷貝。 - span 的生命週期不能超過底層數據。
- 只適用於連續內存容器。