C++学习:六个月从底子到就业——C++20:范围(Ranges)进阶 ...

打印 上一主题 下一主题

主题 2017|帖子 2017|积分 6051

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
C++学习:六个月从底子到就业——C++20:范围(Ranges)进阶

   本文是我C++学习之旅系列的第五十二篇技能文章,也是第三阶段"当代C++特性"的第十四篇,深入探究C++20范围(Ranges)库的高级特性。本文承接上一篇关于Ranges底子的内容,进一步探索更复杂的用法。查看完备系列目次了解更多内容。
  引言

在上一篇文章中,我们介绍了C++20范围库的底子知识,包括范围概念、视图底子和管道操作等。本文将更进一步,探索范围库的高级特性和利用技巧,包括复杂视图组合、自定义视图、性能优化策略以及与其他C++20特性的结合利用。
通过本文,你将学习如何构建更复杂、更强盛的数据处置惩罚管道,创建自己的视图适配器,并优化Ranges代码的性能。无论是处置惩罚复杂数据转换还是设计流式处置惩罚库,这些高级技能都将极大提拔你的C++编程能力。
目次



  • 引言
  • 复杂视图组合
  • 自定义视图
  • 性能优化
  • 与其他C++20特性的结合
  • 现实应用案例
  • 调试与故障扫除
  • Ranges最佳实践
  • 总结
复杂视图组合

深度嵌套视图

处置惩罚复杂的嵌套数据结构是Ranges库的一个强盛用例。考虑一个学校-班级-门生的嵌套结构:
  1. #include <ranges>
  2. #include <vector>
  3. #include <string>
  4. #include <iostream>
  5. struct Student {
  6.     std::string name;
  7.     int score;
  8. };
  9. struct Class {
  10.     std::string name;
  11.     std::vector<Student> students;
  12. };
  13. struct School {
  14.     std::string name;
  15.     std::vector<Class> classes;
  16. };
  17. void nested_view_example() {
  18.     std::vector<School> schools = {
  19.         {"第一中学", {
  20.             {"高一(1)班", {{"张三", 85}, {"李四", 92}, {"王五", 78}}},
  21.             {"高一(2)班", {{"赵六", 90}, {"钱七", 86}, {"孙八", 79}}}
  22.         }},
  23.         {"第二中学", {
  24.             {"高一(1)班", {{"周九", 95}, {"吴十", 89}, {"郑十一", 82}}},
  25.             {"高一(2)班", {{"冯十二", 88}, {"陈十三", 75}, {"褚十四", 93}}}
  26.         }}
  27.     };
  28.    
  29.     // 创建复杂的嵌套视图:查找所有90分以上的学生
  30.     auto high_scorers = schools
  31.         | std::views::transform([](const School& school) { return school.classes; })
  32.         | std::views::join  // 展平学校->班级
  33.         | std::views::transform([](const Class& cls) { return cls.students; })
  34.         | std::views::join  // 展平班级->学生
  35.         | std::views::filter([](const Student& student) { return student.score >= 90; })
  36.         | std::views::transform([](const Student& student) { return student.name; });
  37.    
  38.     // 输出结果
  39.     std::cout << "90分以上的学生:" << std::endl;
  40.     for (const auto& name : high_scorers) {
  41.         std::cout << "- " << name << std::endl;
  42.     }
  43. }
复制代码
这个例子展示了如何利用join视图展平嵌套数据结构,并结合transform和filter视图创建复杂的数据处置惩罚管道。
条件组合

有时我们需要根据不同条件应用不同的视图组合:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. enum class FilterType { None, Even, Odd, Prime };
  5. bool is_prime(int n) {
  6.     if (n <= 1) return false;
  7.     if (n <= 3) return true;
  8.     if (n % 2 == 0 || n % 3 == 0) return false;
  9.     for (int i = 5; i * i <= n; i += 6) {
  10.         if (n % i == 0 || n % (i + 2) == 0) return false;
  11.     }
  12.     return true;
  13. }
  14. // 根据条件创建不同的视图组合
  15. auto create_filtered_view(const std::vector<int>& numbers, FilterType filter_type) {
  16.     switch (filter_type) {
  17.         case FilterType::Even:
  18.             return numbers | std::views::filter([](int n) { return n % 2 == 0; });
  19.         
  20.         case FilterType::Odd:
  21.             return numbers | std::views::filter([](int n) { return n % 2 != 0; });
  22.         
  23.         case FilterType::Prime:
  24.             return numbers | std::views::filter([](int n) { return is_prime(n); });
  25.         
  26.         case FilterType::None:
  27.         default:
  28.             return std::views::all(numbers);
  29.     }
  30. }
复制代码
此模式答应根据运行时条件动态选择合适的视图组合,对于构建机动的数据处置惩罚体系非常有效。
递归视图

固然C++20 Ranges库本身不直接支持递归视图,但我们可以结合传统方法处置惩罚树形结构:
  1. #include <ranges>
  2. #include <vector>
  3. #include <memory>
  4. #include <iostream>
  5. struct TreeNode {
  6.     int value;
  7.     std::vector<std::shared_ptr<TreeNode>> children;
  8.    
  9.     TreeNode(int val) : value(val) {}
  10.     void add_child(int child_value) {
  11.         children.push_back(std::make_shared<TreeNode>(child_value));
  12.     }
  13. };
  14. // 递归遍历树的所有节点
  15. std::vector<int> flatten_tree(const TreeNode& node) {
  16.     std::vector<int> result = {node.value};
  17.    
  18.     for (const auto& child : node.children) {
  19.         auto child_values = flatten_tree(*child);
  20.         result.insert(result.end(), child_values.begin(), child_values.end());
  21.     }
  22.    
  23.     return result;
  24. }
  25. // 使用Ranges风格处理展平后的树
  26. void process_tree(const TreeNode& root) {
  27.     // 先展平树结构
  28.     auto flattened = flatten_tree(root);
  29.    
  30.     // 使用Ranges处理
  31.     auto even_squares = flattened
  32.                        | std::views::filter([](int n) { return n % 2 == 0; })
  33.                        | std::views::transform([](int n) { return n * n; });
  34.    
  35.     std::cout << "树中偶数节点的平方值: ";
  36.     for (int value : even_squares) {
  37.         std::cout << value << " ";
  38.     }
  39.     std::cout << std::endl;
  40. }
复制代码
这种方法固然不是纯粹的Ranges风格,但展示了如何将传统递归与Ranges结合利用。
自定义视图

视图适配器设计

创建自定义视图需要理解三个关键组件:

  • 视图类:实现范围接口的具体视图
  • 视图适配器:创建视图的函数对象
  • 适配器工厂:提供用户友爱的接口
下面我们实现一个"步进"视图,以指定步长取元素:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. #include <cassert>
  5. namespace detail {
  6.     // 步进视图 - 每隔n个元素取一个
  7.     template<std::ranges::input_range V>
  8.     class stride_view : public std::ranges::view_interface<stride_view<V>> {
  9.     private:
  10.         V base_ = V();
  11.         std::size_t stride_ = 1;
  12.         
  13.     public:
  14.         stride_view() = default;
  15.         
  16.         stride_view(V base, std::size_t stride)
  17.             : base_(std::move(base)), stride_(stride) {
  18.             assert(stride > 0);
  19.         }
  20.         
  21.         // 迭代器类实现
  22.         class iterator {
  23.             // ... 迭代器实现细节 ...
  24.             // 包含current_、end_指针和stride_步长
  25.             // 实现operator++, operator*, operator==等
  26.         };
  27.         
  28.         auto begin() {
  29.             return iterator{std::ranges::begin(base_), std::ranges::end(base_), stride_};
  30.         }
  31.         
  32.         auto end() {
  33.             return iterator{std::ranges::end(base_), std::ranges::end(base_), stride_};
  34.         }
  35.     };
  36.    
  37.     // 适配器函数对象
  38.     struct stride_fn {
  39.         template<std::ranges::input_range R>
  40.         auto operator()(R&& r, std::size_t stride) const {
  41.             return stride_view<std::views::all_t<R>>(
  42.                 std::views::all(std::forward<R>(r)), stride);
  43.         }
  44.         
  45.         // 支持管道语法 range | views::stride(n)
  46.         constexpr auto operator()(std::size_t stride) const {
  47.             return [stride](auto&& r) {
  48.                 return stride_view<std::views::all_t<decltype(r)>>(
  49.                     std::views::all(std::forward<decltype(r)>(r)), stride);
  50.             };
  51.         }
  52.     };
  53. }
  54. namespace views {
  55.     inline constexpr detail::stride_fn stride{};
  56. }
  57. void use_stride_view() {
  58.     std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  59.    
  60.     // 使用自定义stride视图
  61.     auto every_second = views::stride(numbers, 2);
  62.    
  63.     std::cout << "每隔一个元素: ";
  64.     for (int n : every_second) {
  65.         std::cout << n << " "; // 1 3 5 7 9
  66.     }
  67.     std::cout << std::endl;
  68.    
  69.     // 使用管道语法
  70.     auto every_third = numbers | views::stride(3);
  71.    
  72.     std::cout << "每隔两个元素: ";
  73.     for (int n : every_third) {
  74.         std::cout << n << " "; // 1 4 7 10
  75.     }
  76.     std::cout << std::endl;
  77. }
复制代码
通过这种方式,我们可以扩展Ranges库,实现自定义的视图适配器。
循环顾图示例

另一个有效的自定义视图是循环顾图,它无穷重复一个范围的元素:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. namespace detail {
  5.     // 循环视图 - 无限重复一个范围的元素
  6.     template<std::ranges::forward_range V>
  7.     requires std::ranges::view<V>
  8.     class cycle_view : public std::ranges::view_interface<cycle_view<V>> {
  9.     private:
  10.         V base_ = V();
  11.         
  12.     public:
  13.         cycle_view() = default;
  14.         explicit cycle_view(V base) : base_(std::move(base)) {}
  15.         
  16.         // 迭代器实现(简化版本)
  17.         // 在达到end()时跳回begin()
  18.         
  19.         auto begin() {
  20.             // 返回指向基础范围开始的迭代器
  21.             // 实际实现需要包装这个迭代器,在到达end时重置为begin
  22.         }
  23.         
  24.         // 此视图无限,使用特殊哨兵
  25.         auto end() {
  26.             return std::unreachable_sentinel;
  27.         }
  28.     };
  29.    
  30.     // 适配器函数对象
  31.     struct cycle_fn {
  32.         template<std::ranges::forward_range R>
  33.         auto operator()(R&& r) const {
  34.             return cycle_view<std::views::all_t<R>>(std::views::all(std::forward<R>(r)));
  35.         }
  36.     };
  37. }
  38. namespace views {
  39.     inline constexpr detail::cycle_fn cycle{};
  40. }
  41. void use_cycle_view() {
  42.     std::vector<int> numbers = {1, 2, 3};
  43.    
  44.     // 使用循环视图与take视图结合
  45.     std::cout << "循环显示10个元素: ";
  46.     for (int n : views::cycle(numbers) | std::views::take(10)) {
  47.         std::cout << n << " "; // 1 2 3 1 2 3 1 2 3 1
  48.     }
  49.     std::cout << std::endl;
  50. }
复制代码
这个示例展示了如何创建无穷视图,它必须与其他能限制元素数量的视图(如take)结合利用。
性能优化

避免重复计算

Ranges视图的惰性特性有时会导致重复计算,特别是多次遍历同一视图时:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. #include <chrono>
  5. // 一个高成本的操作
  6. int expensive_operation(int x) {
  7.     // 模拟耗时操作
  8.     std::this_thread::sleep_for(std::chrono::milliseconds(1));
  9.     return x * x;
  10. }
  11. void demonstrate_materialization() {
  12.     std::vector<int> numbers = {1, 2, 3, 4, 5};
  13.    
  14.     // 创建包含昂贵操作的视图
  15.     auto expensive_view = numbers
  16.         | std::views::transform([](int n) {
  17.             std::cout << "计算 " << n << " 的平方" << std::endl;
  18.             return expensive_operation(n);
  19.         });
  20.    
  21.     // 第一次遍历执行计算
  22.     int sum1 = 0;
  23.     for (int n : expensive_view) sum1 += n;
  24.    
  25.     // 第二次遍历再次执行相同计算
  26.     int sum2 = 0;
  27.     for (int n : expensive_view) sum2 += n;
  28.    
  29.     // 避免重复计算:具体化结果
  30.     std::vector<int> materialized(expensive_view.begin(), expensive_view.end());
  31.    
  32.     // 使用具体化的结果多次遍历不会重复计算
  33.     int sum3 = 0;
  34.     for (int n : materialized) sum3 += n;
  35.    
  36.     int sum4 = 0;
  37.     for (int n : materialized) sum4 += n;
  38.    
  39.     std::cout << "所有计算结果相同: "
  40.               << (sum1 == sum2 && sum2 == sum3 && sum3 == sum4) << std::endl;
  41. }
复制代码
当视图操作本钱高昂或需要多次遍历时,将结果具体化为容器能显著进步性能。
早期终止与短路求值

Ranges的惰性求值特性使得早期终止处置惩罚非常高效:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. void demonstrate_early_termination() {
  5.     // 创建大数据集
  6.     std::vector<int> large_data(10'000'000);
  7.     for (int i = 0; i < large_data.size(); ++i) {
  8.         large_data[i] = i;
  9.     }
  10.    
  11.     // 使用take实现短路求值
  12.     auto first_five_even_squares = large_data
  13.         | std::views::filter([](int n) {
  14.             // 即使数据集很大,此函数最多只会被调用10次左右
  15.             return n % 2 == 0;
  16.           })
  17.         | std::views::transform([](int n) {
  18.             // 同样,此函数最多只会被调用5次
  19.             return n * n;
  20.           })
  21.         | std::views::take(5);  // 只取前5个结果
  22.    
  23.     std::cout << "首个5个偶数的平方: ";
  24.     for (int n : first_five_even_squares) {
  25.         std::cout << n << " ";  // 0 4 16 36 64
  26.     }
  27.     std::cout << std::endl;
  28. }
复制代码
通过take视图结合惰性求值,我们可以高效处置惩罚大数据集,只计算现实需要的元素。
视图具体化策略

不同场景下,选择合适的具体化策略很重要:
  1. #include <ranges>
  2. #include <vector>
  3. #include <deque>
  4. #include <list>
  5. #include <set>
  6. #include <iostream>
  7. template<typename Container, typename View>
  8. auto materialize_to(View&& view) {
  9.     return Container(std::ranges::begin(view), std::ranges::end(view));
  10. }
  11. void demonstrate_materialization_strategies() {
  12.     std::vector<int> data = {5, 3, 1, 4, 2};
  13.    
  14.     auto filtered = data | std::views::filter([](int n) { return n % 2 == 1; });
  15.    
  16.     // 不同的具体化策略
  17.     auto vec = materialize_to<std::vector<int>>(filtered);  // 适合随机访问
  18.     auto deq = materialize_to<std::deque<int>>(filtered);   // 适合两端操作
  19.     auto lst = materialize_to<std::list<int>>(filtered);    // 适合频繁插入/删除
  20.     auto st = materialize_to<std::set<int>>(filtered);      // 适合排序/去重
  21.    
  22.     // 在C++23中,可以使用to<Container>()适配器
  23.     // auto vec2 = filtered | std::ranges::to<std::vector<int>>();
  24. }
复制代码
根据后续操作选择合适的容器类型举行具体化,可以优化性能。
与其他C++20特性的结合

Ranges与概念

Ranges库和概念(Concepts)细密结合,可以创建类型安全的数据处置惩罚函数:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. #include <type_traits>
  5. // 使用概念定义约束
  6. template<typename T>
  7. concept Numeric = std::is_arithmetic_v<T>;
  8. template<typename R>
  9. concept NumericRange = std::ranges::input_range<R> &&
  10.                       Numeric<std::ranges::range_value_t<R>>;
  11. // 使用概念约束的范围函数
  12. template<NumericRange R>
  13. auto average(const R& range) {
  14.     using ValueType = std::ranges::range_value_t<R>;
  15.     ValueType sum{};
  16.     std::size_t count = 0;
  17.    
  18.     for (const auto& value : range) {
  19.         sum += value;
  20.         ++count;
  21.     }
  22.    
  23.     return count > 0 ? sum / static_cast<ValueType>(count) : ValueType{};
  24. }
  25. void ranges_with_concepts() {
  26.     std::vector<double> values = {1.5, 2.5, 3.5, 4.5, 5.5};
  27.    
  28.     // 计算全部数据的平均值
  29.     std::cout << "平均值: " << average(values) << std::endl;
  30.    
  31.     // 计算满足条件数据的平均值
  32.     auto filtered = values | std::views::filter([](double d) { return d > 3.0; });
  33.     std::cout << "大于3的值的平均值: " << average(filtered) << std::endl;
  34.    
  35.     // 这里会编译失败,因为字符串不满足NumericRange约束
  36.     // std::vector<std::string> strings = {"a", "b", "c"};
  37.     // average(strings);
  38. }
复制代码
结合范围和概念,可以创建类型安全、语义清晰的通用数据处置惩罚函数。
Ranges与协程

Ranges可以与协程(Coroutines)结合,处置惩罚异步数据流:
  1. #include <ranges>
  2. #include <vector>
  3. #include <iostream>
  4. #include <future>
  5. #include <thread>
  6. // 异步获取数据
  7. std::future<std::vector<int>> fetch_data_async(int count) {
  8.     return std::async(std::launch::async, [count]() {
  9.         std::vector<int> result;
  10.         // 模拟异步处理
  11.         std::this_thread::sleep_for(std::chrono::milliseconds(500));
  12.         for (int i = 0; i < count; ++i) {
  13.             result.push_back(i);
  14.         }
  15.         return result;
  16.     });
  17. }
  18. // 使用Ranges处理异步数据
  19. void process_async_data() {
  20.     auto future = fetch_data_async(100);
  21.    
  22.     // 等待数据完成
  23.     auto data = future.get();
  24.    
  25.     // 使用Ranges处理数据
  26.     auto processed = data
  27.         | std::views::filter([](int n) { return n % 2 == 0; })
  28.         | std::views::transform([](int n) { return n * n; })
  29.         | std::views::take(5);
  30.    
  31.     std::cout << "处理结果: ";
  32.     for (int n : processed) {
  33.         std::cout << n << " ";  // 0 4 16 36 64
  34.     }
  35.     std::cout << std::endl;
  36. }
复制代码
随着C++20协程的普及,这种结合将使异步数据处置惩罚更加简洁高效。
现实应用案例

文本处置惩罚引擎

利用Ranges库构建一个简朴的文本处置惩罚引擎:
  1. #include <ranges>
  2. #include <vector>
  3. #include <string>
  4. #include <iostream>
  5. #include <regex>
  6. #include <map>
  7. #include <algorithm>
  8. class TextProcessor {
  9. private:
  10.     std::vector<std::string> lines_;
  11.    
  12. public:
  13.     // 从文本设置行
  14.     void set_lines(std::vector<std::string> lines) {
  15.         lines_ = std::move(lines);
  16.     }
  17.    
  18.     // 过滤包含特定子串的行
  19.     auto lines_containing(const std::string& substr) const {
  20.         return lines_
  21.              | std::views::filter([&substr](const std::string& line) {
  22.                    return line.find(substr) != std::string::npos;
  23.                });
  24.     }
  25.    
  26.     // 过滤匹配正则表达式的行
  27.     auto lines_matching(const std::regex& pattern) const {
  28.         return lines_
  29.              | std::views::filter([&pattern](const std::string& line) {
  30.                    return std::regex_search(line, pattern);
  31.                });
  32.     }
  33.    
  34.     // 应用转换函数到每一行
  35.     auto transform_lines(auto transformer) const {
  36.         return lines_ | std::views::transform(transformer);
  37.     }
  38.    
  39.     // 统计单词频率
  40.     std::map<std::string, int> word_frequency() const {
  41.         std::map<std::string, int> frequencies;
  42.         std::regex word_pattern(R"(\b\w+\b)");
  43.         
  44.         for (const auto& line : lines_) {
  45.             auto words_begin = std::sregex_iterator(line.begin(), line.end(), word_pattern);
  46.             auto words_end = std::sregex_iterator();
  47.             
  48.             for (auto it = words_begin; it != words_end; ++it) {
  49.                 std::string word = it->str();
  50.                 std::transform(word.begin(), word.end(), word.begin(),
  51.                              [](unsigned char c) { return std::tolower(c); });
  52.                 frequencies[word]++;
  53.             }
  54.         }
  55.         
  56.         return frequencies;
  57.     }
  58.    
  59.     // 获取前N个最常用词
  60.     auto top_words(int n) const {
  61.         auto freqs = word_frequency();
  62.         
  63.         std::vector<std::pair<std::string, int>> word_pairs(freqs.begin(), freqs.end());
  64.         std::ranges::sort(word_pairs, std::ranges::greater{}, &std::pair<std::string, int>::second);
  65.         
  66.         return word_pairs | std::views::take(n);
  67.     }
  68. };
  69. void text_processor_example() {
  70.     TextProcessor processor;
  71.     processor.set_lines({
  72.         "The quick brown fox jumps over the lazy dog.",
  73.         "C++ Ranges library provides functional-style operations on sequences.",
  74.         "The C++20 standard introduces many new features including concepts and coroutines.",
  75.         "Ranges allow for lazy evaluation and composition of operations."
  76.     });
  77.    
  78.     // 查找包含"fox"的行
  79.     std::cout << "包含'fox'的行:" << std::endl;
  80.     for (const auto& line : processor.lines_containing("fox")) {
  81.         std::cout << line << std::endl;
  82.     }
  83.    
  84.     // 使用正则表达式查找所有包含"C++"的行
  85.     std::regex cpp_pattern(R"(C\+\+)");
  86.     std::cout << "\n包含'C++'的行:" << std::endl;
  87.     for (const auto& line : processor.lines_matching(cpp_pattern)) {
  88.         std::cout << line << std::endl;
  89.     }
  90.    
  91.     // 转换所有行为大写
  92.     std::cout << "\n转换为大写:" << std::endl;
  93.     for (const auto& line : processor.transform_lines([](const std::string& line) {
  94.         std::string upper = line;
  95.         std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
  96.         return upper;
  97.     })) {
  98.         std::cout << line << std::endl;
  99.     }
  100.    
  101.     // 获取最常见的3个单词
  102.     std::cout << "\n最常见的3个单词:" << std::endl;
  103.     for (const auto& [word, count] : processor.top_words(3)) {
  104.         std::cout << word << ": " << count << "次" << std::endl;
  105.     }
  106. }
复制代码
这个示例展示了如何利用Ranges库构建功能丰富的文本处置惩罚工具,结合视图和范围算法举行各种文本操作。
数据分析示例

利用Ranges库简化数据分析使命:
  1. #include <ranges>
  2. #include <vector>
  3. #include <string>
  4. #include <iostream>
  5. #include <numeric>
  6. #include <iomanip>
  7. struct DataPoint {
  8.     std::string category;
  9.     double value;
  10. };
  11. class DataAnalyzer {
  12. private:
  13.     std::vector<DataPoint> data_;
  14.    
  15. public:
  16.     void add_data(std::vector<DataPoint> data) {
  17.         data_.insert(data_.end(), data.begin(), data.end());
  18.     }
  19.    
  20.     // 按类别过滤
  21.     auto filter_by_category(const std::string& category) const {
  22.         return data_ | std::views::filter([&category](const DataPoint& dp) {
  23.             return dp.category == category;
  24.         });
  25.     }
  26.    
  27.     // 计算平均值
  28.     double average(auto range) const {
  29.         double sum = 0.0;
  30.         int count = 0;
  31.         
  32.         for (const auto& dp : range) {
  33.             sum += dp.value;
  34.             ++count;
  35.         }
  36.         
  37.         return count > 0 ? sum / count : 0.0;
  38.     }
  39.    
  40.     // 找出最大值
  41.     auto max_value(auto range) const {
  42.         return std::ranges::max_element(range, {}, &DataPoint::value);
  43.     }
  44.    
  45.     // 按值排序
  46.     auto sorted_by_value(auto range) const {
  47.         auto copied = std::vector<DataPoint>(range.begin(), range.end());
  48.         std::ranges::sort(copied, {}, &DataPoint::value);
  49.         return copied;
  50.     }
  51.    
  52.     // 生成报告
  53.     void generate_report() const {
  54.         // 获取所有唯一类别
  55.         std::vector<std::string> categories;
  56.         auto category_view = data_ | std::views::transform(&DataPoint::category);
  57.         
  58.         for (const auto& cat : category_view) {
  59.             if (std::find(categories.begin(), categories.end(), cat) == categories.end()) {
  60.                 categories.push_back(cat);
  61.             }
  62.         }
  63.         
  64.         // 为每个类别生成统计
  65.         std::cout << std::left << std::setw(15) << "类别"
  66.                   << std::setw(10) << "数量"
  67.                   << std::setw(10) << "平均值"
  68.                   << std::setw(10) << "最大值" << std::endl;
  69.         std::cout << std::string(45, '-') << std::endl;
  70.         
  71.         for (const auto& category : categories) {
  72.             auto category_data = filter_by_category(category);
  73.             auto count = std::ranges::distance(category_data);
  74.             auto avg = average(category_data);
  75.             auto max_it = max_value(category_data);
  76.             auto max = max_it != data_.end() ? max_it->value : 0.0;
  77.             
  78.             std::cout << std::left << std::setw(15) << category
  79.                       << std::setw(10) << count
  80.                       << std::setw(10) << std::fixed << std::setprecision(2) << avg
  81.                       << std::setw(10) << max << std::endl;
  82.         }
  83.     }
  84. };
  85. void data_analysis_example() {
  86.     DataAnalyzer analyzer;
  87.    
  88.     // 添加样本数据
  89.     analyzer.add_data({
  90.         {"电子", 230.50}, {"服装", 120.75}, {"食品", 45.30},
  91.         {"电子", 180.99}, {"服装", 85.50}, {"食品", 32.99},
  92.         {"电子", 320.00}, {"服装", 95.75}, {"食品", 50.25},
  93.         {"电子", 195.50}, {"服装", 110.25}, {"食品", 37.50}
  94.     });
  95.    
  96.     // 生成分析报告
  97.     analyzer.generate_report();
  98.    
  99.     // 使用视图组合查找特定数据
  100.     auto expensive_electronics = analyzer.filter_by_category("电子")
  101.                               | std::views::filter([](const DataPoint& dp) {
  102.                                     return dp.value > 200.0;
  103.                                 });
  104.    
  105.     std::cout << "\n高价电子产品:" << std::endl;
  106.     for (const auto& item : expensive_electronics) {
  107.         std::cout << "价格: " << item.value << std::endl;
  108.     }
  109. }
复制代码
这个示例展示了如何利用Ranges库构建根本的数据分析工具,包括过滤、排序和聚合操作。
调试与故障扫除

常见错误模式

利用Ranges库时常见的错误:

  • 悬空引用:视图通常引用原始数据,必须确保引用对象的生命周期
  1. // 危险代码
  2. auto get_filtered() {
  3.     std::vector<int> temp = {1, 2, 3, 4, 5};
  4.     return temp | std::views::filter([](int n) { return n % 2 == 0; });
  5. } // temp超出作用域,视图引用失效
复制代码

  • 共享状态:lambda捕捉的变量在迭代过程中大概会改变
  1. std::vector<int> numbers = {1, 2, 3, 4, 5};
  2. int threshold = 3;
  3. // threshold在lambda中被捕获,如果外部修改threshold,会影响过滤结果
  4. auto above_threshold = numbers | std::views::filter([&threshold](int n) {
  5.     return n > threshold;
  6. });
  7. // 遍历过程中修改阈值
  8. for (int n : above_threshold) {
  9.     std::cout << n << " ";
  10.     threshold++; // 这会改变过滤条件!
  11. }
复制代码

  • 未预期的惰性计算:视图操作在需要结果时才实行
  1. std::vector<int> data = {1, 2, 3, 4, 5};
  2. // 这里没有实际计算任何东西
  3. auto view = data | std::views::transform([](int n) {
  4.     std::cout << "计算" << n << "的平方\n";
  5.     return n * n;
  6. });
  7. std::cout << "视图创建完成\n";
  8. // 只有在这里才开始实际计算
  9. for (int n : view) {
  10.     std::cout << "结果: " << n << "\n";
  11. }
复制代码
调试技巧

调试Ranges代码的几个有效技巧:

  • 具体化中央结果:将视图转换为容器以便检查
  1. auto view1 = data | std::views::filter(...);
  2. // 调试检查
  3. std::vector<int> debug_copy(view1.begin(), view1.end());
  4. for (auto x : debug_copy) std::cout << x << " ";
复制代码

  • 添加打印语句:在lambda中添加打印语句跟踪实行流程
  1. auto debugged_view = data | std::views::transform([](int n) {
  2.     std::cout << "处理值: " << n << std::endl;
  3.     auto result = process(n);
  4.     std::cout << "结果: " << result << std::endl;
  5.     return result;
  6. });
复制代码

  • 利用故意义的中央变量:为复杂管道的各阶段利用命名变量
  1. // 不好的做法:难以调试的单一长表达式
  2. auto result = data
  3.     | std::views::filter(...)
  4.     | std::views::transform(...)
  5.     | std::views::take(...);
  6. // 更好的做法:使用中间变量
  7. auto filtered = data | std::views::filter(...);
  8. auto transformed = filtered | std::views::transform(...);
  9. auto result = transformed | std::views::take(...);
复制代码
Ranges最佳实践

设计模式

利用Ranges库时的几种有效设计模式:

  • 管道模式:利用管道操作符链接数据处置惩罚步调
  1. auto result = data
  2.     | std::views::filter(pred)      // 第一步:过滤
  3.     | std::views::transform(func)   // 第二步:转换
  4.     | std::views::take(n);          // 第三步:限制数量
复制代码

  • 工厂函数模式:创建视图工厂函数封装常用操作
  1. // 封装常用的数据处理操作
  2. auto only_positives(auto&& range) {
  3.     return range | std::views::filter([](auto x) { return x > 0; });
  4. }
  5. auto doubled(auto&& range) {
  6.     return range | std::views::transform([](auto x) { return x * 2; });
  7. }
  8. // 组合使用
  9. auto result = data | only_positives | doubled | std::views::take(5);
复制代码

  • 适配器模式:将现有函数转换为适用于Ranges的情势
  1. // 传统函数
  2. bool is_prime(int n) {
  3.     if (n <= 1) return false;
  4.     // ... 素数检查逻辑
  5. }
  6. // 适配为视图过滤器
  7. auto prime_filter = [](auto&& range) {
  8.     return range | std::views::filter([](int n) { return is_prime(n); });
  9. };
  10. // 使用
  11. auto primes = numbers | prime_filter | std::views::take(10);
复制代码
代码风格指南

Ranges代码的推荐风格:

  • 单行展示简朴操作,多行展示复杂操作
  1. // 简单操作可以单行
  2. auto even = numbers | std::views::filter([](int n) { return n % 2 == 0; });
  3. // 复杂管道应该分行,提高可读性
  4. auto processed = data
  5.     | std::views::filter([](const auto& item) {
  6.         // 复杂过滤条件
  7.         return item.status == "active" && item.value > threshold;
  8.     })
  9.     | std::views::transform([](const auto& item) {
  10.         // 复杂转换逻辑
  11.         return process_item(item);
  12.     })
  13.     | std::views::take(10);
复制代码

  • 提取复杂谓词和变换函数
  1. // 不好的做法
  2. auto view = data | std::views::filter(
  3.     [](const auto& x) { /* 长而复杂的条件 */ }
  4. );
  5. // 好的做法
  6. auto is_valid = [](const auto& x) {
  7.     // 长而复杂的条件,现在有了名称
  8.     return /* ... */;
  9. };
  10. auto view = data | std::views::filter(is_valid);
复制代码

  • 利用故意义的中央变量
  1. // 直接处理
  2. auto result = getRawData()
  3.             | std::views::filter(is_valid)
  4.             | std::views::transform(normalize)
  5.             | std::views::transform(to_output_format);
  6. // 更清晰的分步处理
  7. auto raw_data = getRawData();
  8. auto valid_data = raw_data | std::views::filter(is_valid);
  9. auto normalized = valid_data | std::views::transform(normalize);
  10. auto formatted = normalized | std::views::transform(to_output_format);
复制代码
性能留意事项

优化Ranges代码性能的关键点:

  • 得当机遇具体化结果:对多次利用的中央结果举行具体化
  1. auto filtered = data | std::views::filter(pred);
  2. // 如果filtered会被多次使用,考虑具体化
  3. std::vector<int> filtered_vec(filtered.begin(), filtered.end());
  4. // 现在可以高效地多次使用
  5. process1(filtered_vec);
  6. process2(filtered_vec);
复制代码

  • 避免重复昂贵的计算:对高本钱操作的结果举行缓存
  1. // 如果transform中的操作很昂贵,可以考虑缓存结果
  2. auto expensive_calc = data | std::views::transform([](int n) {
  3.     return very_expensive_calculation(n);
  4. });
  5. // 具体化以避免重复计算
  6. auto cached = std::vector<result_type>(expensive_calc.begin(), expensive_calc.end());
复制代码

  • 公道组合操作顺序:某些操作顺序比其他更高效
  1. // 通常,先过滤再转换更高效
  2. auto efficient = data
  3.     | std::views::filter(pred)      // 先减少元素数量
  4.     | std::views::transform(func);  // 然后转换减少后的元素
  5. // 而不是
  6. auto less_efficient = data
  7.     | std::views::transform(func)   // 先转换所有元素
  8.     | std::views::filter(pred);     // 然后丢弃一些转换结果
复制代码
总结

C++20的范围(Ranges)库为C++带来了函数式编程的优雅和表达力。通过本文介绍的高级特性和技巧,你已经了解了如何:

  • 创建复杂的视图组合来处置惩罚嵌套数据结构
  • 设计和实现自定义视图以扩展Ranges库功能
  • 优化Ranges代码的性能,避免常见陷阱
  • 将Ranges与其他C++20特性(如概念和协程)结合利用
  • 应用Ranges库解决现实题目,如文本处置惩罚和数据分析
Ranges库不仅使代码更加简洁和声明式,还提供了强盛的组合能力,使我们能够以模块化方式构建复杂的数据处置惩罚管道。随着实践经验的积累,你会发现Ranges库能极大地进步C++编程的生产力和代码质量。
在下一篇文章中,我们将探究C++20的另一个重要特性:模块(Modules),它如何简化代码构造,加快编译,并解决头文件包含的各种题目。

这是我C++学习之旅系列的第五十二篇技能文章。查看完备系列目次了解更多内容。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表