Ch 27: C++20 Ranges 范围库
- 理解 Ranges 库的核心优势
- 掌握常用视图操作(filter、transform、take、drop)
- 学会构建管道式数据处理
- 理解惰性求值机制
- 熟练使用 std::ranges 算法
27.1 为什么需要 Ranges
Section titled “27.1 为什么需要 Ranges”Python 列表推导的局限性
Section titled “Python 列表推导的局限性”# Python - 立即求值,创建中间列表nums = range(1, 11) # 惰性生成器evens = [x for x in nums if x % 2 == 0] # 创建列表squares = [x**2 for x in evens] # 创建另一个列表# squares = [4, 16, 36, 64, 100]
# 问题:创建了中间列表(内存浪费)# 问题:无法处理无限序列C++ std::ranges 的优势
Section titled “C++ std::ranges 的优势”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// C++20 - 惰性求值,不创建中间容器auto evens = nums | std::views::filter([](int x) { return x % 2 == 0; });auto squares = evens | std::views::transform([](int x) { return x * x; });
// 实际计算只在迭代时发生for (int n : squares) { std::cout << n << " "; // 4 16 36 64 100}
// 可以处理无限序列auto infinite_odds = std::views::iota(1) // 1, 2, 3, ... | std::views::filter([](int x) { return x % 2 != 0; }); // 1, 3, 5, ...
// take(5) 让无限序列变成有限for (int n : infinite_odds | std::views::take(5)) { std::cout << n << " "; // 1 3 5 7 9}Ranges vs 传统算法
Section titled “Ranges vs 传统算法”#include <vector>#include <algorithm>#include <ranges>
std::vector<int> data = {3, 1, 4, 1, 5, 9, 2, 6};
// 传统方式:需要中间容器std::vector<int> filtered;std::copy_if(data.begin(), data.end(), std::back_inserter(filtered), [](int x) { return x > 3; });std::sort(filtered.begin(), filtered.end());
// Ranges 方式:惰性,简洁auto result = data | std::views::filter([](int x) { return x > 3; }) | std::views::common_view // 转为普通视图 | std::ranges::to<std::vector>; // C++23 收集到容器27.2 std::views::filter
Section titled “27.2 std::views::filter”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 过滤偶数auto is_even = [](int x) { return x % 2 == 0; };for (int n : nums | std::views::filter(is_even)) { std::cout << n << " "; // 2 4 6 8 10}
std::cout << "\n";
// 过滤大于 5 的数for (int n : nums | std::views::filter([](int x) { return x > 5; })) { std::cout << n << " "; // 6 7 8 9 10}filter 与管道组合
Section titled “filter 与管道组合”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 组合:先过滤后转换auto result = nums | std::views::filter([](int x) { return x % 2 == 0; }) // 2, 4, 6, 8, 10 | std::views::transform([](int x) { return x * x; }); // 4, 16, 36, 64, 100
for (int n : result) { std::cout << n << " ";}std::cout << "\n";
// 多次过滤auto result2 = nums | std::views::filter([](int x) { return x > 2; }) // 3-10 | std::views::filter([](int x) { return x < 8; }) // 3-7 | std::views::filter([](int x) { return x % 2 == 0; }); // 4, 6
for (int n : result2) { std::cout << n << " "; // 4 6}收集结果到容器
Section titled “收集结果到容器”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 方法1:使用 for 循环手动收集std::vector<int> filtered;for (int n : nums | std::views::filter([](int x) { return x % 2 == 0; })) { filtered.push_back(n);}
// 方法2:std::ranges::copy_tostd::vector<int> filtered2;std::ranges::copy(nums | std::views::filter([](int x) { return x % 2 == 0; }), std::back_inserter(filtered2));
// 方法3:C++23 ranges::to// std::vector<int> filtered3 = nums// | std::views::filter([](int x) { return x % 2 == 0; })// | std::ranges::to<std::vector>(); // C++2327.3 std::views::transform
Section titled “27.3 std::views::transform”#include <vector>#include <ranges>#include <iostream>#include <string>
std::vector<int> nums = {1, 2, 3, 4, 5};
// 平方for (int n : nums | std::views::transform([](int x) { return x * x; })) { std::cout << n << " "; // 1 4 9 16 25}std::cout << "\n";
// 字符串转换std::vector<std::string> words = {"hello", "world"};for (const std::string& s : words | std::views::transform([](const std::string& x) { std::string r = x; r[0] = std::toupper(r[0]); return r;})) { std::cout << s << " "; // Hello World}std::cout << "\n";transform 与其他视图组合
Section titled “transform 与其他视图组合”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5};
// 完整管道:过滤 -> 转换 -> 变换auto result = nums | std::views::filter([](int x) { return x > 2; }) // 3, 4, 5 | std::views::transform([](int x) { return x * x; }) // 9, 16, 25 | std::views::transform([](int x) { return x + 1; }); // 10, 17, 26
for (int n : result) { std::cout << n << " "; // 10 17 26}std::cout << "\n";
// 使用 std::views::common 将视图转为可重复迭代auto view = nums | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * 10; });
// 多次迭代(需要 common_view 或 ref_view)for (int n : view) { std::cout << n << " ";}27.4 std::views::iota
Section titled “27.4 std::views::iota”#include <views>#include <iostream>
// iota - 生成无限或有限序列// C++23 ranges 命名空间for (int i : std::views::iota(1, 10)) { // 1 到 9 std::cout << i << " ";}std::cout << "\n";// 1 2 3 4 5 6 7 8 9
// 从 0 开始,默认结束为无限for (int i : std::views::iota(0) | std::views::take(5)) { std::cout << i << " "; // 0 1 2 3 4}std::cout << "\n";
// 无限序列 + takefor (int i : std::views::iota(1) | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::take(10)) { std::cout << i << " "; // 2 4 6 8 10 12 14 16 18 20}std::cout << "\n";iota 生成复杂序列
Section titled “iota 生成复杂序列”#include <vector>#include <ranges>#include <iostream>#include <iomanip>
// 生成斐波那契数列(无限)auto fibonacci = []() { return std::views::iota(0) | std::views::transform([](int n) { // 斐波那契计算 if (n == 0) return 0; if (n == 1) return 1; int a = 0, b = 1; for (int i = 2; i <= n; ++i) { int temp = a + b; a = b; b = temp; } return b; });};
int main() { std::cout << "Fibonacci: "; for (int f : fibonacci() | std::views::take(15)) { std::cout << f << " "; } std::cout << "\n";
// 生成质数 auto is_prime = [](int n) { if (n < 2) return false; for (int i = 2; i * i <= n; ++i) { if (n % i == 0) return false; } return true; };
std::cout << "Primes: "; for (int p : std::views::iota(2) | std::views::filter(is_prime) | std::views::take(20)) { std::cout << p << " "; } std::cout << "\n";}27.5 常用视图操作
Section titled “27.5 常用视图操作”take 和 drop
Section titled “take 和 drop”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// take - 取前 N 个std::cout << "First 3: ";for (int n : nums | std::views::take(3)) { std::cout << n << " "; // 1 2 3}std::cout << "\n";
// drop - 跳过前 N 个std::cout << "Skip first 3: ";for (int n : nums | std::views::drop(3)) { std::cout << n << " "; // 4 5 6 7 8 9 10}std::cout << "\n";
// drop_while - 跳过满足条件的元素std::cout << "After first even: ";for (int n : nums | std::views::drop_while([](int x) { return x % 2 != 0; })) { std::cout << n << " "; // 2 3 4 5 6 7 8 9 10}std::cout << "\n";
// take_while - 取满足条件的元素std::cout << "Until 5: ";for (int n : nums | std::views::take_while([](int x) { return x < 5; })) { std::cout << n << " "; // 1 2 3 4}std::cout << "\n";reverse 和 stride
Section titled “reverse 和 stride”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// reverse - 反向std::cout << "Reversed: ";for (int n : nums | std::views::reverse) { std::cout << n << " "; // 10 9 8 7 6 5 4 3 2 1}std::cout << "\n";
// stride(C++23)- 步进// for (int n : nums | std::views::stride(2)) {// std::cout << n << " "; // 1 3 5 7 9 (C++23)// }
// 组合:反向后取前3std::cout << "Last 3: ";for (int n : nums | std::views::reverse | std::views::take(3)) { std::cout << n << " "; // 10 9 8}std::cout << "\n";split 和 lazy_split(C++23)
Section titled “split 和 lazy_split(C++23)”#include <vector>#include <ranges>#include <iostream>#include <string>
std::string text = "hello world cpp";
// split(C++23)- 按分隔符分割// for (auto word : text | std::views::split(' ')) {// for (char c : word) {// std::cout << c;// }// std::cout << "\n";// }
// all - 将容器转为视图std::vector<int> nums = {1, 2, 3, 4, 5};auto view = std::views::all(nums);
for (int n : view) { std::cout << n << " ";}std::cout << "\n";unique(C++23 去重)
Section titled “unique(C++23 去重)”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 1, 2, 2, 2, 3, 1, 1};
// unique(C++23)- 去除相邻重复// for (int n : nums | std::views::unique) {// std::cout << n << " "; // 1 2 3 1// }
// 注意:unique 只去除相邻的重复// 要完全去重,需要先排序27.6 管道式操作详解
Section titled “27.6 管道式操作详解”管道操作顺序
Section titled “管道操作顺序”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 顺序不同,结果可能不同!
// 先过滤后取 - 取前3个偶数std::cout << "filter then take: ";for (int n : nums | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::take(3)) { std::cout << n << " "; // 2 4 6}std::cout << "\n";
// 先取后过滤 - 取前3个中过滤偶数std::cout << "take then filter: ";for (int n : nums | std::views::take(3) | std::views::filter([](int x) { return x % 2 == 0; })) { std::cout << n << " "; // 2}std::cout << "\n";#include <string>#include <ranges>#include <iostream>#include <cctype>
std::string text = "Hello World";
// 转换为大写std::string upper;for (char c : text | std::views::transform(::toupper)) { upper += c;}std::cout << upper << "\n"; // HELLO WORLD
// 统计字符auto is_vowel = [](char c) { c = std::tolower(c); return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';};
int vowel_count = 0;for (char c : text | std::views::filter(is_vowel)) { ++vowel_count;}std::cout << "Vowels: " << vowel_count << "\n";
// 字符串反转std::string reversed;for (char c : text | std::views::reverse) { reversed += c;}std::cout << "Reversed: " << reversed << "\n"; // dlroW olleH复杂数据处理管道
Section titled “复杂数据处理管道”#include <vector>#include <ranges>#include <iostream>#include <string>#include <sstream>
struct Student { std::string name; int score;};
std::vector<Student> students = { {"Alice", 85}, {"Bob", 92}, {"Charlie", 78}, {"Diana", 95}, {"Eve", 88}, {"Frank", 72}};
// 管道:过滤不及格 -> 按分数排序 -> 取前3 -> 提取姓名std::cout << "Top 3 passing students:\n";int rank = 1;for (const auto& name : students | std::views::filter([](const Student& s) { return s.score >= 80; }) | std::views::transform([](const Student& s) { return s.name; }) | std::views::transform([](const std::string& n) { return n + "!"; }) | std::views::take(3)) { std::cout << rank++ << ". " << name << "\n";}
// 分数统计auto passing_scores = students | std::views::filter([](const Student& s) { return s.score >= 80; }) | std::views::transform([](const Student& s) { return s.score; });
double avg = 0;int count = 0;for (int score : passing_scores) { avg += score; ++count;}if (count > 0) { avg /= count; std::cout << "Average passing score: " << avg << "\n";}27.7 std::ranges 算法
Section titled “27.7 std::ranges 算法”std::ranges::find 系列
Section titled “std::ranges::find 系列”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {5, 3, 8, 1, 9, 2, 7};
// find - 找第一个满足条件的元素auto it = std::ranges::find(nums, 8);if (it != nums.end()) { std::cout << "Found at index: " << std::distance(nums.begin(), it) << "\n";}
// find_if - 找第一个满足谓词的元素auto it2 = std::ranges::find_if(nums, [](int x) { return x > 6; });if (it2 != nums.end()) { std::cout << "First > 6: " << *it2 << "\n"; // 8}
// find_if_not - 找第一个不满足谓词的元素auto it3 = std::ranges::find_if_not(nums, [](int x) { return x < 5; });if (it3 != nums.end()) { std::cout << "First >= 5: " << *it3 << "\n"; // 5}std::ranges::count 系列
Section titled “std::ranges::count 系列”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 2, 4, 2, 5, 2};
// count - 计数等于指定值的元素int twos = std::ranges::count(nums, 2);std::cout << "Count of 2: " << twos << "\n"; // 4
// count_if - 计数满足条件的元素int evens = std::ranges::count_if(nums, [](int x) { return x % 2 == 0; });std::cout << "Count of evens: " << evens << "\n"; // 5std::ranges::sort 和相关
Section titled “std::ranges::sort 和相关”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {5, 2, 8, 1, 9, 3};
// sort - 排序std::ranges::sort(nums);for (int n : nums) { std::cout << n << " ";}std::cout << "\n";
// is_sorted - 检查是否已排序std::cout << "is_sorted: " << std::ranges::is_sorted(nums) << "\n"; // 1
// partial_sort - 部分排序(找前 N 个最小)std::vector<int> data = {5, 2, 8, 1, 9, 3};std::ranges::partial_sort(data, data.begin() + 3); // 前3个最小for (int n : data) { std::cout << n << " ";}std::cout << "\n";
// stable_sort - 稳定排序(保持相等元素的相对顺序)std::ranges::transform
Section titled “std::ranges::transform”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5};std::vector<int> result(nums.size());
// transform - 转换并写入另一个容器std::ranges::transform(nums, result.begin(), [](int x) { return x * x; });for (int n : result) { std::cout << n << " "; // 1 4 9 16 25}std::cout << "\n";
// 两个范围的操作std::vector<int> a = {1, 2, 3};std::vector<int> b = {10, 20, 30};std::vector<int> c(3);
std::ranges::transform(a, b, c.begin(), std::plus<>{});for (int n : c) { std::cout << n << " "; // 11 22 33}std::cout << "\n";std::ranges::reduce(C++23 可用,C++17/20 用 accumulate)
Section titled “std::ranges::reduce(C++23 可用,C++17/20 用 accumulate)”#include <vector>#include <numeric>#include <iostream>#include <functional>
std::vector<int> nums = {1, 2, 3, 4, 5};
// accumulate(C++20 前的 reduce)int sum = std::accumulate(nums.begin(), nums.end(), 0);std::cout << "Sum: " << sum << "\n"; // 15
int product = std::accumulate(nums.begin(), nums.end(), 1, std::multiplies<>{});std::cout << "Product: " << product << "\n"; // 120
// 使用 transform + accumulateint sum_of_squares = std::accumulate( nums.begin(), nums.end(), 0, [](int acc, int x) { return acc + x * x; });std::cout << "Sum of squares: " << sum_of_squares << "\n"; // 5527.8 完整示例:数据处理管道
Section titled “27.8 完整示例:数据处理管道”#include <vector>#include <ranges>#include <iostream>#include <string>#include <algorithm>
struct Product { std::string name; std::string category; double price; int stock;};
std::vector<Product> inventory = { {"Laptop", "Electronics", 999.99, 5}, {"Mouse", "Electronics", 29.99, 50}, {"Desk", "Furniture", 299.99, 10}, {"Chair", "Furniture", 199.99, 8}, {"Keyboard", "Electronics", 79.99, 30}, {"Monitor", "Electronics", 349.99, 15}, {"Book: C++", "Books", 49.99, 100}, {"Book: Python", "Books", 39.99, 80}};
int main() { // 管道:Electronics 类别 -> 有库存 -> 按价格排序 -> 取前3 -> 提取名称 std::cout << "=== Top 3 Affordable Electronics ===\n";
auto affordable_electronics = inventory | std::views::filter([](const Product& p) { return p.category == "Electronics" && p.stock > 0; }) | std::views::transform([](const Product& p) { return std::pair(p.name, p.price); }) | std::views::filter([](const auto& pair) { return pair.second < 500.0; });
// 排序需要先收集(ranges 排序算法作用于容器) std::vector<std::pair<std::string, double>> sorted( affordable_electronics.begin(), affordable_electronics.end()); std::ranges::sort(sorted, {}, &std::pair<std::string, double>::second);
int count = 0; for (const auto& [name, price] : sorted | std::views::take(3)) { std::cout << ++count << ". " << name << " - $" << price << "\n"; }
// 计算 Electronics 总库存值 std::cout << "\n=== Electronics Inventory Value ===\n";
double total_value = 0; for (const auto& p : inventory | std::views::filter([](const Product& p) { return p.category == "Electronics"; })) { total_value += p.price * p.stock; } std::cout << "Total value: $" << total_value << "\n";
// 最贵的商品 std::cout << "\n=== Most Expensive Items ===\n"; auto all_items = inventory | std::views::transform([](const Product& p) { return std::pair(p.name, p.price); });
std::vector<std::pair<std::string, double>> items( all_items.begin(), all_items.end()); auto max_it = std::ranges::max_element(items, {}, &std::pair<std::string, double>::second); std::cout << "Most expensive: " << max_it->first << " - $" << max_it->second << "\n";}27.9 惰性求值详解
Section titled “27.9 惰性求值详解”什么是惰性求值
Section titled “什么是惰性求值”#include <vector>#include <ranges>#include <iostream>
std::vector<int> nums = {1, 2, 3, 4, 5};
auto view = nums | std::views::filter([](int x) { std::cout << "filter(" << x << ") "; return x % 2 == 0; }) | std::views::transform([](int x) { std::cout << "transform(" << x << ") "; return x * x; });
std::cout << "View created, no computation yet.\n";// 输出:View created, no computation yet.
// 开始迭代才真正计算std::cout << "First element: ";for (int n : view | std::views::take(1)) { std::cout << "result=" << n << "\n";}// 输出:filter(1) filter(2) transform(2) result=4// 只有真正需要的元素被处理即时求值 vs 惰性求值
Section titled “即时求值 vs 惰性求值”#include <vector>#include <ranges>#include <numeric>
// 即时求值:一次性计算所有std::vector<int> nums = {1, 2, 3, 4, 5};
auto immediate_sum_of_squares = []() { std::vector<int> filtered; for (int n : nums) { if (n % 2 == 0) filtered.push_back(n); } std::vector<int> transformed; for (int n : filtered) { transformed.push_back(n * n); } return std::accumulate(transformed.begin(), transformed.end(), 0);};
// 惰性求值:按需计算auto lazy_sum_of_squares = []() { return std::accumulate( nums | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * x; }), 0 );};
// lazy 版本可能只处理必需元素就停止// (如果算法是短路的,如 find)视图的注意事项
Section titled “视图的注意事项”#include <vector>#include <ranges>#include <iostream>
// 视图通常不缓存结果auto view = std::views::iota(1) | std::views::transform([](int x) { std::cout << "computing " << x << "\n"; return x; });
// 第一次迭代std::cout << "First iteration:\n";for (int i : view | std::views::take(3)) { std::cout << "got " << i << "\n";}
// 第二次迭代 - 重新计算!std::cout << "Second iteration:\n";for (int i : view | std::views::take(3)) { std::cout << "got " << i << "\n";}
// 如果需要缓存,使用 ranges::to 或手动收集27.10 章节总结
Section titled “27.10 章节总结”Ranges vs 传统方式:
| 特性 | 传统算法 | Ranges |
|---|---|---|
| 求值时机 | 即时(创建中间容器) | 惰性(按需计算) |
| 组合方式 | 函数嵌套 | 管道 | 操作符 |
| 无限序列 | 不支持 | 支持 |
| 代码可读性 | 嵌套调用 | 线性管道 |
常用视图速查:
| 视图 | 说明 | 示例 |
|---|---|---|
filter(pred) | 过滤元素 | v | filter(odd) |
transform(f) | 转换元素 | v | transform(square) |
take(n) | 取前 n 个 | v | take(5) |
drop(n) | 跳过前 n 个 | v | drop(3) |
reverse | 反向 | v | reverse |
iota(n) | 产生序列 | iota(1, 10) |
all | 容器转视图 | all(vec) |
管道组合原则:
filter后take- 先过滤后限制数量take后filter- 先限制后过滤(结果可能为空)transform放最后 - 转换结果
下章预告:ch28 学习 C++20 其他现代特性,包括 consteval、std::format、std::span 和三路比较运算符。