C++20新特性之Range庫
之前我們已經(jīng)學(xué)習(xí)過C++20的概念與約束。今天我們就來學(xué)習(xí)繼承了概念與約束的標(biāo)準(zhǔn)庫新的功能庫Range庫
官方文檔:Standard library header <ranges> (C++20) - cppreference.com
Range庫介紹
前言
Ranges 是對 STL 算法與迭代器體系的一次根本性重構(gòu)。 傳統(tǒng) STL 算法用一對迭代器 [begin, end) 表示操作范圍,而 Ranges 將這個"范圍"抽象為一等公民——一個可以直接傳遞的對象。
核心變化:
// C++17 傳統(tǒng)寫法:必須傳兩個迭代器 std::sort(vec.begin(), vec.end()); // C++20 Ranges 寫法:傳一個 range 對象即可 std::ranges::sort(vec);
這個看似微小的語法變化背后,是整個泛型編程范式的升級。
包含內(nèi)容
整個庫基于 <ranges> 頭文件,分為四大層次:
1. 核心概念(Concepts)—— 類型系統(tǒng)的基石
| Concept | 含義 |
|---|---|
| std::ranges::range | 任何有 begin() 和 end() 的類型 |
| std::ranges::sized_range | 可以在常數(shù)時間獲取大小的 range |
| std::ranges::view | 輕量級、O(1) 拷貝/移動的 range |
| std::ranges::input_range | 至少支持輸入迭代器的 range |
| std::ranges::forward_range | 支持前向迭代器 |
| std::ranges::bidirectional_range | 支持雙向迭代器 |
| std::ranges::random_access_range | 支持隨機(jī)訪問迭代器 |
| std::ranges::contiguous_range | 內(nèi)存連續(xù)布局(如 vector, array, span) |
| std::ranges::common_range | begin() 和 end() 返回相同類型的 range |
2. 算法(Algorithms)——std::ranges::命名空間
所有傳統(tǒng) STL 算法的 Range 版本,約束在 std::ranges 命名空間下。與傳統(tǒng)算法有兩個關(guān)鍵區(qū)別:
- 第一個參數(shù)是 range 對象,而非迭代器對
- 返回類型更豐富:不再只返回一個迭代器,而是返回包含更多信息的結(jié)構(gòu)化結(jié)果
// 傳統(tǒng) STL:返回迭代器,要找插入位置還得再算 auto it = std::find(vec.begin(), vec.end(), 42); // C++20 Ranges:接受 range,返回迭代器 auto it = std::ranges::find(vec, 42);
3. Range 適配器(Range Adaptors)—— 惰性管道
這是 Ranges 庫最具革命性的部分。適配器是惰性求值的視圖轉(zhuǎn)換,通過 | 管道運算符串聯(lián),構(gòu)成聲明式的數(shù)據(jù)處理流水線:
| 適配器 | 作用 |
|---|---|
| std::views::filter(pred) | 按謂詞過濾元素 |
| std::views::transform(fn) | 對每個元素應(yīng)用函數(shù) |
| std::views::take(n) | 取前 n 個元素 |
| std::views::drop(n) | 跳過前 n 個元素 |
| std::views::take_while(pred) | 取滿足條件的連續(xù)前綴 |
| std::views::drop_while(pred) | 丟棄滿足條件的連續(xù)前綴 |
| std::views::reverse | 反轉(zhuǎn)范圍 |
| std::views::join | 展平嵌套 range |
| std::views::split(delim) | 按分隔符切分 |
| std::views::common | 轉(zhuǎn)換為 common_range |
| std::views::keys / values | 提取 pair/tuple 的 key 或 value |
| std::views::iota | 生成遞增序列 |
| std::views::all | 將 range 包裝為 view |
4. 工廠(Factories / Range Factories)
不需要輸入 range,直接生成 range 的工具:
std::views::iota(start, end)— 生成[start, end)整數(shù)序列std::views::empty<T>— 空 rangestd::views::single(x)— 只包含單一元素的 rangestd::views::istream<T>(stream)— 從輸入流讀取
優(yōu)勢
消滅迭代器對——代碼量減半
// C++17:寫兩遍容器名 std::sort(v.begin(), v.end()); auto it = std::find_if(v.begin(), v.end(), pred); // C++20:容器名只出現(xiàn)一次 std::ranges::sort(v); auto it = std::ranges::find_if(v, pred);
這不是語法糖——它消除了迭代器不匹配的可能性,從類型系統(tǒng)層面杜絕了 sort(v1.begin(), v2.end()) 這類經(jīng)典 bug。
管道組合——從命令式到聲明式
這是最重要的心智模型轉(zhuǎn)變。傳統(tǒng)寫法是命令式的:你需要一步步告訴 CPU 要做什么。管道寫法是聲明式的:你描述數(shù)據(jù)應(yīng)該經(jīng)過怎樣的變換。
// C++17 命令式:需要中間容器,多次遍歷
std::vector<int> even;
std::copy_if(v.begin(), v.end(), std::back_inserter(even),
[](int x) { return x % 2 == 0; });
std::vector<int> squared;
std::transform(even.begin(), even.end(), std::back_inserter(squared),
[](int x) { return x * x; });
auto it = std::find_if(squared.begin(), squared.end(),
[](int x) { return x > 100; });
// C++20 聲明式:一次遍歷,零拷貝,惰性求值
auto result = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::views::drop_while([](int x) { return x <= 100; });
for (auto x : result) { /* 只在遍歷時才真正計算 */ }
惰性求值——零開銷抽象
管道中的每個適配器在定義時不執(zhí)行任何計算。計算被推遲到消費時才發(fā)生,且所有變換融合成單次遍歷。沒有中間分配,沒有額外拷貝。
更豐富的返回值
傳統(tǒng) std::find 只返回一個迭代器,還需要重新計算到開頭的距離等操作。Range 算法返回結(jié)構(gòu)化的結(jié)果對象:
// C++17
auto it = std::find(v.begin(), v.end(), 42);
auto pos = std::distance(v.begin(), it); // 又遍歷一次
// C++20:直接通過結(jié)構(gòu)化綁定獲取所需信息
struct find_result { iterator found; bool success; }; // 偽代碼示例
// 實際例子——ranges::sort 返回 borrowed_iterator_t<R>
投影(Projection)——無需 Lambda 包裝器
每個 Range 算法都支持一個 Projection 參數(shù),可以在比較/操作之前對元素做變換,而無需手動寫 lambda:
// C++17:lambda 包裝比較
std::sort(v.begin(), v.end(),
[](const Person& a, const Person& b) { return a.age < b.age; });
// C++20:投影 + 默認(rèn)比較
std::ranges::sort(v, std::less{}, &Person::age);
// ^^^^^^^^^ ^^^^^^^^^^^^
// 比較函數(shù) 投影(要對什么排序)
Concepts 約束——編譯期安全帶
Range 算法通過 Concept 對其輸入做了嚴(yán)格約束。如果一個類型不滿足 input_range,調(diào)用 ranges::find 在編譯期就會給出清晰的錯誤信息,而不是在實例化后拋出幾十行的模板錯誤。
int x = 42; // std::ranges::sort(x); // 編譯錯誤,清晰提示 int 不是 range
對內(nèi)置數(shù)組和std::span的原生支持
int arr[] = {3, 1, 4, 1, 5};
std::ranges::sort(arr); // 直接對 C 數(shù)組排序,不需要 std::begin/std::endRange視圖
視圖的核心特點與優(yōu)勢
視圖 (View) 是 Ranges 庫的核心抽象,具有以下關(guān)鍵特征:
- O(1) 拷貝/移動 — 視圖不擁有數(shù)據(jù),只持有底層 range 的引用或輕量級狀態(tài)
- 惰性求值 — 定義管道時不執(zhí)行任何計算,消費時才按需計算
- 融合遍歷 — 多個視圖通過
|串聯(lián)后,編譯器將其融合為等效的單次手寫循環(huán) - 不修改原數(shù)據(jù) — 視圖是"數(shù)據(jù)的觀察窗口",變換操作生成新視圖而不修改底層容器
- 編譯期類型安全 — 基于 Concepts 約束,視圖間的非法連接會在編譯期被攔截
適配器視圖
需要輸入 range,對數(shù)據(jù)做變換
| 適配器 | 功能 | 典型場景 |
|---|---|---|
| filter(pred) | 保留滿足謂詞的元素 | 篩選偶數(shù)、有效記錄 |
| transform(fn) | 對每個元素應(yīng)用映射函數(shù) | 類型轉(zhuǎn)換、提取字段 |
| take(n) | 取前 n 個元素后停止 | 分頁、TOP-N 查詢 |
| drop(n) | 跳過前 n 個元素 | 跳過表頭、去除前導(dǎo) |
| take_while(pred) | 取滿足條件的連續(xù)前綴 | 讀取到終止標(biāo)記 |
| drop_while(pred) | 丟棄滿足條件的連續(xù)前綴 | 去除前導(dǎo)空白 |
| reverse | 反轉(zhuǎn) range(需 bidirectional) | 逆序遍歷 |
| split(delim) | 按分隔符切割(range of ranges) | 字符串分詞 |
| join | 展平嵌套 range | 二維轉(zhuǎn)一維 |
| keys | 提取 pair/tuple 的第一個元素 | 遍歷 map 的 key |
| values | 提取 pair/tuple 的第二個元素 | 遍歷 map 的 value |
| all | 將 range 包裝為 view | 顯式開啟管道 |
| common | 統(tǒng)一 begin() 和 end() 返回類型 | 適配遺留接口 |
代碼示例:
#include <algorithm>
#include <iostream>
#include <ranges>
#include <vector>
#include <string>
#include <map>
#include <list>
#include <sstream>
namespace r = std::ranges;
namespace v = std::views;
template<typename R>
void print_range(std::string_view name, R&& r) {
std::cout << name << ": ";
for (auto&& e : r) std::cout << e << ' ';
std::cout << '\n';
}
// ----------------------------------------------------------------------------
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// ================================================================
// 1. filter —— 按謂詞過濾,保留滿足條件的元素
// ================================================================
{
auto even = data | v::filter([](int x) { return x % 2 == 0; });
print_range("filter(偶數(shù))", even); // 2 4 6 8 10
}
// ================================================================
// 2. transform —— 對每個元素施加映射函數(shù)
// ================================================================
{
auto squared = data | v::transform([](int x) { return x * x; });
print_range("transform(平方)", squared); // 1 4 9 16 25 36 49 64 81 100
}
// ================================================================
// 3. 管道組合:filter + transform —— 惰性求值,單次遍歷
// ================================================================
{
auto pipeline = data
| v::filter([](int x) { return x % 2 == 0; }) // 先過濾偶數(shù)
| v::transform([](int x) { return x * x; }); // 再平方
print_range("filter + transform(偶數(shù)的平方)", pipeline); // 4 16 36 64 100
}
// ================================================================
// 4. take(n) —— 取前 n 個元素,達(dá)到 n 個后立即停止遍歷
// drop(n) —— 跳過前 n 個元素
// ================================================================
{
auto first3 = data | v::take(3);
print_range("take(3)", first3); // 1 2 3
auto skip3 = data | v::drop(3);
print_range("drop(3)", skip3); // 4 5 6 7 8 9 10
// 與 filter 組合:取前3個偶數(shù)
auto first3even = data
| v::filter([](int x) { return x % 2 == 0; })
| v::take(3);
print_range("前3個偶數(shù)", first3even); // 2 4 6
}
// ================================================================
// 5. take_while —— 取滿足條件的連續(xù)前綴
// drop_while —— 丟棄滿足條件的連續(xù)前綴
// ================================================================
{
auto prefix = data | v::take_while([](int x) { return x < 5; });
print_range("take_while(x < 5)", prefix); // 1 2 3 4
auto suffix = data | v::drop_while([](int x) { return x < 5; });
print_range("drop_while(x < 5)", suffix); // 5 6 7 8 9 10
}
// ================================================================
// 6. reverse —— 反轉(zhuǎn) range(要求 bidirectional_range)
// ================================================================
{
auto reversed = data | v::reverse;
print_range("reverse", reversed); // 10 9 8 7 6 5 4 3 2 1
}
// ================================================================
// 7. split —— 按分隔符切割 range
// 返回的是一個 range of ranges,每個子 range 是一段
// ================================================================
{
std::string text = "hello world cpp ranges";
auto words = text | v::split(' ');
std::cout << "split(' '): ";
for (auto word : words) {
std::cout << '\'';
for (char ch : word) std::cout << ch;
std::cout << "' ";
}
std::cout << '\n'; // 'hello' 'world' 'cpp' 'ranges'
}
// ================================================================
// 8. join —— 展平嵌套的 range(range of ranges → flat range)
// ================================================================
{
std::vector<std::vector<int>> nested = {{1, 2}, {3, 4, 5}, {6}};
auto flat = nested | v::join;
print_range("join(展平嵌套)", flat); // 1 2 3 4 5 6
// join 與 split 配合:先切割再展平(類似于去除分隔符)
std::string text = "a-b-c";
auto parts = text | v::split('-');
// 把 '-' 替換為 ' '
// 注意:這里 split 產(chǎn)生的 subrange 再 join 需要 C++23 的 join_with
// 此處演示 join 的基本用法
}
// ================================================================
// 9. keys / values —— 提取關(guān)聯(lián)容器的鍵/值
// ================================================================
{
std::map<int, std::string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
std::cout << "keys: ";
for (auto k : m | v::keys) std::cout << k << ' '; // 1 2 3
std::cout << '\n';
std::cout << "values: ";
for (auto v : m | v::values) std::cout << v << ' '; // one two three
std::cout << '\n';
}
// ================================================================
// 10. all —— 將任意 range 包裝為 view
// 當(dāng)你想使用管道語法但手上的容器不是 view 時,all 是橋梁
// ================================================================
{
auto v = data | v::all; // vector → view
auto r = v | v::reverse; // 管道串聯(lián)
print_range("all | reverse", r); // 10 9 8 7 6 5 4 3 2 1
// 實際上 | 運算符已經(jīng)隱式做了這個轉(zhuǎn)換,顯式使用 all 的場景較少
}
// ================================================================
// 11. common —— 將 sentinel 類型與 iterator 類型統(tǒng)一
// 某些遺留代碼要求 begin() 和 end() 返回相同類型
// ================================================================
{
auto filtered = data | v::filter([](int x) { return x > 5; });
// filtered 的 begin() 和 end() 類型不同(sentinel != iterator)
// static_assert(!r::common_range<decltype(filtered)>);
auto common = filtered | v::common;
// common 的 begin() 和 end() 返回相同類型
static_assert(r::common_range<decltype(common)>);
print_range("common", common); // 6 7 8 9 10
}
// ================================================================
// 12. 投射(Projection) —— 不是適配器,但常與 Range 算法配合
// 所有 std::ranges 算法都支持 projection 參數(shù)
// ================================================================
{
struct Person { std::string name; int age; };
std::vector<Person> people = {
{"Charlie", 35}, {"Alice", 30}, {"Bob", 25}
};
// 按年齡排序,使用 projection 而非手寫 lambda
r::sort(people, std::less{}, &Person::age);
std::cout << "projection(按age排序): ";
for (auto& p : people) std::cout << p.name << '(' << p.age << ") ";
std::cout << '\n';
}
return 0;
}
工廠視圖
自身生成 range
| 工廠 | 功能 | 典型場景 |
|---|---|---|
| iota(start) | 無限遞增序列 [start, ∞) | 配合 take 生成索引 |
| iota(start, end) | 有限遞增序列 [start, end) | 生成數(shù)值區(qū)間 |
| empty<T> | 空 range | 占位/空結(jié)果 |
| single(x) | 只包含單一元素的 range | 插入單元素到管道 |
| istream<T>(stream) | 從輸入流惰性讀取 | 處理大數(shù)據(jù)文件 |
| counted(it, n) | 迭代器 + 計數(shù)構(gòu)造 range | 對接指針+長度 API |
代碼示例:
#include <iostream>
#include <ranges>
#include <vector>
#include <string>
#include <sstream>
#include <iterator>
#include <cassert>
namespace r = std::ranges;
namespace v = std::views;
template<typename R>
void print_range(std::string_view name, R&& r) {
std::cout << name << ": ";
for (auto&& e : r) std::cout << e << ' ';
std::cout << '\n';
}
// ----------------------------------------------------------------------------
int main() {
// ================================================================
// 1. iota —— 生成遞增序列,類似 Python 的 range()
// 語法: v::iota(start, end) 生成 [start, end) 的整數(shù)
// 也可: v::iota(start) 生成 [start, ∞) 的無界序列
// ================================================================
{
// 有界 iota:指定上限
auto seq = v::iota(1, 11); // 1, 2, 3, ..., 10
print_range("iota(1, 11)", seq); // 1 2 3 4 5 6 7 8 9 10
// 無界 iota:從某個值開始,無限延伸(必須配合 take 使用)
// 直接迭代無界 iota 會導(dǎo)致無限循環(huán)!
auto inf_seq = v::iota(100); // 100, 101, 102, ...
auto first5 = inf_seq | v::take(5);
print_range("iota(100) | take(5)", first5); // 100 101 102 103 104
}
// ================================================================
// 1b. iota 不只是整數(shù) —— 任何支持 ++ 和 < 的類型都可用
// ================================================================
{
// 生成大寫字母序列
auto alpha = v::iota('A', 'H'); // A, B, C, D, E, F, G
std::cout << "iota('A','H'): ";
for (char c : alpha) std::cout << c << ' ';
std::cout << '\n';
}
// ================================================================
// 2. empty<T> —— 生成空 range(不包含任何元素)
// 常用于需要"空結(jié)果"占位的場景
// ================================================================
{
auto e = v::empty<int>;
std::cout << "empty<int> size: " << r::distance(e) << '\n'; // 0
assert(r::empty(e)); // 確認(rèn)是空 range
}
// ================================================================
// 3. single(x) —— 生成只包含單一元素的 range
// 常與 join 配合使用,將單元素插入管道流
// ================================================================
{
auto s = v::single(42);
print_range("single(42)", s); // 42
static_assert(r::size(s) == 1);
assert(*s.begin() == 42);
}
// ================================================================
// 4. istream<T> —— 從輸入流讀取,作為 range
// 按需從流中惰性讀取,不一次性加載所有數(shù)據(jù)
// ================================================================
{
std::istringstream iss("10 20 30 40 50");
auto numbers = v::istream<int>(iss);
std::cout << "istream<int>: ";
for (int x : numbers) std::cout << x << ' '; // 10 20 30 40 50
std::cout << '\n';
}
// ================================================================
// 4b. istream 管道組合 —— 從流讀取后立即過濾/變換
// 無需先存到 vector,直接惰性處理
// ================================================================
{
std::istringstream iss("1 2 3 4 5 6 7 8 9 10");
auto result = v::istream<int>(iss)
| v::filter([](int x) { return x % 2 == 0; })
| v::transform([](int x) { return x * x; })
| v::take(3);
std::cout << "istream管道(偶數(shù)的平方前3個): ";
for (int x : result) std::cout << x << ' '; // 4 16 36
std::cout << '\n';
}
// ================================================================
// 5. counted —— 從迭代器 + 計數(shù)構(gòu)造 range
// 接收 (iterator, count) 對,生成大小固定的 range
// 常用于處理遺留代碼中"指針+長度"的 API
// ================================================================
{
std::vector<int> v = {10, 20, 30, 40, 50, 60, 70, 80};
// 從第3個元素(30)開始,取4個
auto sub = v::counted(v.begin() + 2, 4);
print_range("counted(begin+2, 4)", sub); // 30 40 50 60
// 實質(zhì)等價于 r::subrange(begin, begin + count)
}
// ================================================================
// 6. 綜合示例:工廠 + 適配器 構(gòu)建完整管道
// 生成前20個自然數(shù),保留3的倍數(shù),平方后取前5個
// ================================================================
{
auto pipeline = v::iota(1) // 工廠: 1,2,3,...
| v::filter([](int x) { return x % 3 == 0; }) // 保留3的倍數(shù): 3,6,9,...
| v::transform([](int x) { return x * x; }) // 平方: 9,36,81,...
| v::take(5); // 取前5個
print_range("綜合管道", pipeline); // 9 36 81 144 225
}
// ================================================================
// 6b. 綜合示例:斐波那契數(shù)列生成
// 利用 iota 和 transform 可以生成任意序列
// ================================================================
{
// 技巧: iota(0) 提供索引 n=0,1,2,...,transform 將 n 映射為 Fib(n)
// 但這里需要狀態(tài),用單行管道做不直觀,展示另一種思路:
// 用 iota 產(chǎn)生索引,通過迭代計算 Fib
// 更直接的 Fibonacci:使用結(jié)構(gòu)化綁定 + iota
auto fib = v::iota(0)
| v::transform([a = 0ull, b = 1ull](int) mutable {
auto result = a;
a = b;
b = result + b;
return result;
})
| v::take(10);
std::cout << "Fibonacci(前10項): ";
for (auto f : fib) std::cout << f << ' '; // 0 1 1 2 3 5 8 13 21 34
std::cout << '\n';
}
return 0;
}綜合實踐
按部分篩選員工然后加薪資
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <ranges>
#include <string>
#include <vector>
namespace r = std::ranges;
namespace v = std::views;
// ============================================================================
// 員工薪資分析系統(tǒng) — C++20 Ranges 綜合應(yīng)用
// ============================================================================
struct Employee {
std::string name;
std::string department;
double salary;
};
// ============================================================================
// 功能1: 按部門分組統(tǒng)計平均薪資
// ============================================================================
// C++20 沒有 std::views::group_by(那是 C++23 的特性),因此采用 map 聚合,
// 配合 range-based for 遍歷。每個部門的 salary 被累加后求均值。
void avgSalaryByDept(const std::vector<Employee>& employees) {
// <部門, {薪資總和, 人數(shù)}>
std::map<std::string, std::pair<double, int>> dept_stats;
for (const auto& emp : employees) {
auto& [total, count] = dept_stats[emp.department];
total += emp.salary;
++count;
}
std::cout << "\n===== 各部門平均薪資 =====\n";
std::cout << std::left;
for (const auto& [dept, stats] : dept_stats) {
const auto& [total, count] = stats;
std::cout << " " << std::setw(12) << dept
<< " : " << std::fixed << std::setprecision(2)
<< total / count << '\n';
}
}
// ============================================================================
// 功能2: 找出薪資最高的員工
// ============================================================================
// 使用 std::ranges::max_element,通過 projection 指定比較對象為 salary 成員。
// 比傳統(tǒng) lambda 版本更簡潔: r::max_element(v, less{}, &Employee::salary)
void findHighestPaid(const std::vector<Employee>& employees) {
auto it = r::max_element(employees, std::less{}, &Employee::salary);
std::cout << "\n===== 薪資最高員工 =====\n";
if (it == employees.end()) {
std::cout << " (無數(shù)據(jù))\n";
return;
}
std::cout << " " << it->name
<< " (" << it->department << ")"
<< " : " << std::fixed << std::setprecision(2)
<< it->salary << '\n';
}
// ============================================================================
// 功能3: 篩選技術(shù)部員工并加薪10%,然后打印
// ============================================================================
// filter 視圖篩選技術(shù)部,展開 for 循環(huán)中修改 salary 后輸出新舊對比。
// 注意: filter 返回的是 view,對 view 中元素的修改會反映回原容器。
void raiseITDept(std::vector<Employee>& employees) {
std::cout << "\n===== 技術(shù)部加薪10% =====\n";
std::cout << std::left;
// 管道: 保留技術(shù)部 → 遍歷修改
auto it_emps = employees
| v::filter([](const Employee& e) { return e.department == "技術(shù)部"; });
for (auto& emp : it_emps) {
double old_salary = emp.salary;
emp.salary *= 1.10;
std::cout << " " << std::setw(10) << emp.name
<< " : " << std::setw(8) << old_salary
<< " -> " << std::fixed << std::setprecision(2)
<< emp.salary << '\n';
}
}
// ============================================================================
// 統(tǒng)一入口: 依次執(zhí)行上述三項分析
// ============================================================================
void analyzeEmployees(std::vector<Employee>& employees) {
avgSalaryByDept(employees);
findHighestPaid(employees);
raiseITDept(employees);
}
// ============================================================================
// main
// ============================================================================
int main() {
std::vector<Employee> staff = {
{"張三", "技術(shù)部", 25000.0},
{"李四", "技術(shù)部", 28000.0},
{"王五", "技術(shù)部", 22000.0},
{"趙六", "市場部", 18000.0},
{"孫七", "市場部", 20000.0},
{"周八", "人事部", 15000.0},
{"吳九", "技術(shù)部", 35000.0},
{"鄭十", "人事部", 16000.0},
{"馮十一", "市場部", 19000.0},
};
std::cout << std::fixed << std::setprecision(2);
std::cout << "===== 員工薪資分析系統(tǒng) =====\n";
std::cout << "共 " << staff.size() << " 名員工\n";
analyzeEmployees(staff);
}本期內(nèi)容到這里就結(jié)束了,喜歡請點個贊謝謝
到此這篇關(guān)于C++20新特性之Range庫的文章就介紹到這了,更多相關(guān)C++20 Range庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)LeetCode(203.移除鏈表元素)
這篇文章主要介紹了C++實現(xiàn)LeetCode(203.移除鏈表元素),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
C++聲明extern變量和extern函數(shù)的用法
extern關(guān)鍵字可以用來聲明變量和函數(shù)作為外部變量或者外部函數(shù)供其它文件使用,所以本文給大家介紹了C++聲明extern變量和extern函數(shù)的用法,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-11-11

