C++学习:六个月从基础到就业——C++20:范围(Ranges)基础

#新星杯·14天创作挑战营·第11期#

C++学习:六个月从基础到就业——C++20:范围(Ranges)基础

本文是我C++学习之旅系列的第五十一篇技术文章,也是第三阶段"现代C++特性"的第十三篇,介绍C++20引入的范围(Ranges)库的基础知识。查看完整系列目录了解更多内容。

引言

STL算法和容器是C++编程中最强大的工具之一,但传统的STL算法接口存在一些使用上的不便:需要显式传递迭代器对、难以组合多个算法操作、代码可读性不佳等。C++20引入的范围(Ranges)库重新设计了算法接口,引入了视图(Views)的概念,并提供了方便的管道操作符,显著改善了这些问题。

想象一下,如果你想获取一个集合中所有偶数的平方,传统STL算法需要这样写:

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> result;

// 筛选偶数
std::copy_if(numbers.begin(), numbers.end(), 
           std::back_inserter(result),
           [](int n) { return n % 2 == 0; });

// 计算平方
std::transform(result.begin(), result.end(),
             result.begin(),
             [](int n) { return n * n; });

而使用Ranges库,代码变得简洁明了:

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

auto result = numbers
            | std::views::filter([](int n) { return n % 2 == 0; })
            | std::views::transform([](int n) { return n * n; });

// 使用结果
for (int n : result) {
    std::cout << n << " ";  // 输出: 4 16 36 64 100
}

本文将介绍C++20 Ranges库的基础知识,包括其核心概念、范围算法、视图(Views)和管道操作,帮助你理解和应用这一强大的新特性。

目录

范围库概述

传统STL算法的局限

传统STL算法虽然功能强大,但存在几个明显的使用不便:

  1. 迭代器对耦合:必须同时提供起始和结束迭代器
std::vector<int> v = {1, 2, 3, 4, 5};
std::sort(v.begin(), v.end());  // 必须提供两个迭代器
  1. 错误风险:容易传递不匹配的迭代器对
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = {4, 5, 6};
// 潜在风险:传递了不匹配的迭代器对
std::sort(v1.begin(), v2.end());  // 未定义行为
  1. 组合算法困难:算法间组合需要中间容器,代码冗长
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> temp;
std::vector<int> result;

// 筛选
std::copy_if(source.begin(), source.end(), 
           std::back_inserter(temp),
           [](int n) { return n > 2; });

// 转换
std::transform(temp.begin(), temp.end(),
             std::back_inserter(result),
             [](int n) { return n * n; });
  1. 返回值不便:许多算法返回迭代器,需要检查合法性
auto it = std::find(v.begin(), v.end(), 42);
if (it != v.end()) {  // 必须检查是否有效
    // 使用*it
} else {
    // 未找到
}

范围库的设计理念

C++20范围库基于几个核心理念设计:

  1. 范围作为统一概念:将容器或迭代器对视为一个整体的"范围"
std::vector<int> v = {1, 2, 3, 4, 5};
// 将容器作为范围直接传递
std::ranges::sort(v);  
  1. 组合操作:通过管道操作符(|)组合多个操作
auto result = container
            | std::views::filter(pred)
            | std::views::transform(func);
  1. 惰性求值:视图不立即执行操作,而是在需要结果时才计算
// 创建视图只是定义操作,不执行实际计算
auto even_squares = numbers
                  | std::views::filter([](int n) { return n % 2 == 0; })
                  | std::views::transform([](int n) { return n * n; });

// 只有遍历时才实际执行操作
for (int n : even_squares) {
    // 在这里才真正执行过滤和转换
    std::cout << n << " ";
}
  1. 更清晰的语义:代码更具声明性,意图更加明确
// 传统方式
std::vector<int> result;
std::copy_if(v.begin(), v.end(), std::back_inserter(result), 
           [](int n) { return n > 0; });

// Ranges方式
auto positive = v | std::views::filter([](int n) { return n > 0; });

核心组件

范围库包含四个主要组件:

  1. 范围概念(Range Concepts):定义何为范围的约束
template<typename T>
concept range = requires(T& t) {
    std::ranges::begin(t);
    std::ranges::end(t);
};
  1. 范围算法(Range Algorithms):接受范围作为参数的算法
// 算法直接接受范围参数
std::ranges::sort(container);
std::ranges::find(container, value);
  1. 视图(Views):轻量级、不修改原始数据的范围适配器
// 基本视图
std::views::all      // 整个范围的视图
std::views::filter   // 根据谓词筛选元素
std::views::transform // 转换元素
std::views::take     // 取前N个元素
std::views::drop     // 跳过前N个元素
  1. 范围适配器(Range Adaptors):修改范围属性的工具
// 范围适配器
std::views::reverse  // 反转范围
std::views::join     // 连接嵌套范围
std::views::split    // 分割范围

范围概念

什么是范围

在C++20中,范围(Range)是一个抽象概念,表示元素序列。更具体地说,任何可以通过调用std::ranges::begin()std::ranges::end()得到有效迭代器对的类型都是范围。

#include <ranges>
#include <vector>
#include <list>
#include <string>
#include <iostream>

void demonstrate_ranges() {
    // 这些都是范围
    std::vector<int> vec = {1, 2, 3, 4, 5};        // 向量是范围
    std::list<double> lst = {1.1, 2.2, 3.3};       // 列表是范围
    std::string str = "Hello";                     // 字符串是范围
    int arr[] = {10, 20, 30, 40, 50};              // 数组是范围
    
    // 普通指针对也可以是范围
    const char* cstr = "C-string";
    auto ptr_range = std::ranges::subrange(cstr, cstr + 8);
    
    // 视图也是范围
    auto even = vec | std::views::filter([](int n) { return n % 2 == 0; });
    
    // 使用范围变量的示例
    std::cout << "Vector elements:";
    for (int n : vec) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "Even numbers:";
    for (int n : even) std::cout << " " << n;
    std::cout << std::endl;
}

范围的关键特性:

  1. 提供了表示元素序列的统一抽象
  2. 支持对整个序列而非迭代器对进行操作
  3. 可以是有限的(如容器)或无限的(如生成器)
  4. 可以是普通容器、视图或迭代器对

范围类别和需求

C++20定义了多种范围概念,根据底层迭代器的能力形成层次结构:

#include <ranges>
#include <vector>
#include <list>
#include <forward_list>
#include <iostream>

template<typename R>
void print_range_capabilities() {
    std::cout << "- range: " << std::ranges::range<R> << std::endl;
    std::cout << "- input_range: " << std::ranges::input_range<R> << std::endl;
    std::cout << "- forward_range: " << std::ranges::forward_range<R> << std::endl;
    std::cout << "- bidirectional_range: " << std::ranges::bidirectional_range<R> << std::endl;
    std::cout << "- random_access_range: " << std::ranges::random_access_range<R> << std::endl;
    std::cout << "- contiguous_range: " << std::ranges::contiguous_range<R> << std::endl;
    std::cout << "- sized_range: " << std::ranges::sized_range<R> << std::endl;
    std::cout << "- view: " << std::ranges::view<R> << std::endl;
}

int main() {
    std::cout << "std::vector capabilities:" << std::endl;
    print_range_capabilities<std::vector<int>>();
    
    std::cout << "\nstd::list capabilities:" << std::endl;
    print_range_capabilities<std::list<int>>();
    
    std::cout << "\nstd::forward_list capabilities:" << std::endl;
    print_range_capabilities<std::forward_list<int>>();
    
    std::cout << "\nFilter view capabilities:" << std::endl;
    print_range_capabilities<decltype(std::vector<int>{} | 
                                    std::views::filter([](int) { return true; }))>();
    
    return 0;
}

主要范围类别:

  1. std::ranges::range:基本范围概念,支持begin()end()
  2. std::ranges::input_range:可以读取元素的范围
  3. std::ranges::forward_range:可以多次遍历的范围
  4. std::ranges::bidirectional_range:可以双向遍历的范围
  5. std::ranges::random_access_range:支持随机访问的范围
  6. std::ranges::contiguous_range:内存连续存储的范围
  7. std::ranges::sized_range:可以常数时间获取大小的范围
  8. std::ranges::view:轻量级、非拥有元素的范围

不同容器支持不同级别的范围能力:

  • std::vector 支持所有范围能力(除了view
  • std::list 是双向范围但不支持随机访问
  • std::forward_list 是前向范围但不支持双向遍历
  • 视图(如filter view)通常继承底层范围的能力,但始终是view

迭代器与哨兵

C++20范围库引入了"哨兵"(Sentinel)的概念,允许迭代器和终止条件类型不同:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>

int main() {
    // 传统方式:begin和end类型相同
    std::vector<int> v = {1, 2, 3, 4, 5};
    auto it_begin = v.begin();  // std::vector<int>::iterator
    auto it_end = v.end();      // 同样类型
    
    // 范围库:允许不同类型的终止条件
    std::string str = "Hello, world!";
    
    // 自定义视图:到null字符为止
    auto null_terminated = std::ranges::subrange(
        str.c_str(),  // const char*
        std::unreachable_sentinel  // 特殊哨兵类型
    );
    
    // 计算长度(不包括null终止符)
    std::cout << "String length: " 
              << std::ranges::distance(null_terminated) << std::endl;
    
    // 查找特定字符的视图
    auto until_comma = std::ranges::subrange(
        str.begin(),
        std::ranges::find(str, ',')  // 迭代器指向逗号
    );
    
    std::cout << "Text before comma: ";
    for (char c : until_comma) {
        std::cout << c;
    }
    std::cout << std::endl;
    
    return 0;
}

哨兵概念的优势:

  1. 更灵活的范围表示:终止条件可以是谓词而非具体位置
  2. 无限序列支持:可以表示无限序列,只在需要时检查终止条件
  3. 懒惰计算:哨兵可以推迟终止条件的计算
  4. 优化机会:编译器可以针对特定哨兵类型优化代码

范围算法

算法改进

C++20为标准库算法提供了对应的范围版本,带来几个主要改进:

  1. 直接接受范围参数,而非迭代器对
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {5, 3, 1, 4, 2};
    
    // 传统STL算法
    std::sort(numbers.begin(), numbers.end());
    
    // 范围版本算法
    std::ranges::sort(numbers);  // 更简洁
    
    return 0;
}
  1. 返回更有意义的结果
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    
    // 传统STL查找
    auto it = std::find(numbers.begin(), numbers.end(), 3);
    if (it != numbers.end()) {
        std::cout << "Found: " << *it << std::endl;
    }
    
    // 范围版本查找
    auto it_ranges = std::ranges::find(numbers, 3);
    if (it_ranges != numbers.end()) {
        std::cout << "Found with ranges: " << *it_ranges << std::endl;
    }
    
    // 更复杂的例子:找出最大和最小值
    auto [min_it, max_it] = std::ranges::minmax_element(numbers);
    std::cout << "Min: " << *min_it << ", Max: " << *max_it << std::endl;
    
    return 0;
}
  1. 支持投影(Projections):在应用算法前变换元素
#include <ranges>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

struct Person {
    std::string name;
    int age;
};

int main() {
    std::vector<Person> people = {
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35}
    };
    
    // 使用投影按年龄排序
    std::ranges::sort(people, {}, &Person::age);
    
    // 输出排序后的结果
    for (const auto& person : people) {
        std::cout << person.name << ": " << person.age << std::endl;
    }
    
    // 查找年龄最大的人
    auto oldest = std::ranges::max_element(people, {}, &Person::age);
    if (oldest != people.end()) {
        std::cout << "Oldest person: " << oldest->name 
                  << " (" << oldest->age << ")" << std::endl;
    }
    
    return 0;
}
  1. 概念约束:算法明确指定了对输入类型的要求
// std::ranges::sort的简化定义
template<std::random_access_range R, 
         typename Comp = std::ranges::less, 
         typename Proj = std::identity>
requires std::sortable<std::ranges::iterator_t<R>, Comp, Proj>
constexpr auto sort(R&& r, Comp comp = {}, Proj proj = {}) -> ...;

常用范围算法

C++20范围库包含了标准库中大部分算法的范围版本:

#include <ranges>
#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>

void demonstrate_range_algorithms() {
    std::vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};
    std::vector<int> dest(numbers.size());
    
    // 排序算法
    std::ranges::sort(numbers);
    std::cout << "排序后: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    // 查找算法
    auto it = std::ranges::find(numbers, 7);
    if (it != numbers.end()) {
        std::cout << "找到7,位置: " << std::distance(numbers.begin(), it) << std::endl;
    }
    
    // 计数算法
    int count = std::ranges::count_if(numbers, [](int n) { return n % 2 == 0; });
    std::cout << "偶数个数: " << count << std::endl;
    
    // 复制算法
    std::ranges::copy_if(numbers, dest.begin(), [](int n) { return n > 5; });
    
    std::cout << "大于5的数: ";
    for (int i = 0; i < std::ranges::count(numbers, true, [](int n) { return n > 5; }); ++i) {
        std::cout << dest[i] << " ";
    }
    std::cout << std::endl;
    
    // 变换算法
    std::ranges::transform(numbers, dest.begin(), [](int n) { return n * n; });
    
    std::cout << "平方值: ";
    for (int i = 0; i < numbers.size(); ++i) {
        std::cout << dest[i] << " ";
    }
    std::cout << std::endl;
    
    // 其他常用算法
    auto [min, max] = std::ranges::minmax(numbers);
    std::cout << "最小值: " << min << ", 最大值: " << max << std::endl;
    
    std::ranges::reverse(numbers);
    std::cout << "反转后: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    bool all_positive = std::ranges::all_of(numbers, [](int n) { return n > 0; });
    std::cout << "全部为正: " << (all_positive ? "是" : "否") << std::endl;
}

常见范围算法分类:

  1. 非修改序列算法ranges::find, ranges::count, ranges::all_of
  2. 修改序列算法ranges::copy, ranges::transform, ranges::replace
  3. 排序和相关算法ranges::sort, ranges::partial_sort, ranges::nth_element
  4. 数值算法ranges::accumulate(注意:部分数值算法尚未有范围版本)
  5. 集合算法ranges::set_union, ranges::set_intersection

投影参数

范围算法的一个重要特性是支持投影(Projections),允许在实际应用算法前转换元素:

#include <ranges>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

struct Student {
    std::string name;
    std::vector<int> scores;
    
    // 计算平均分
    double average() const {
        if (scores.empty()) return 0;
        int sum = 0;
        for (int score : scores) sum += score;
        return static_cast<double>(sum) / scores.size();
    }
};

int main() {
    std::vector<Student> students = {
        {"Alice", {85, 90, 82}},
        {"Bob", {76, 88, 95}},
        {"Charlie", {90, 92, 98}},
        {"David", {65, 75, 80}}
    };
    
    // 使用投影基于平均分排序
    std::ranges::sort(students, {}, [](const Student& s) { return s.average(); });
    
    // 显示结果
    std::cout << "学生按平均分排序:" << std::endl;
    for (const auto& student : students) {
        std::cout << student.name << ": " << student.average() << std::endl;
    }
    
    // 找出平均分最高的学生
    auto top_student = std::ranges::max_element(students, {}, 
                                              [](const Student& s) { return s.average(); });
    
    std::cout << "\n平均分最高的学生: " << top_student->name 
              << " (" << top_student->average() << ")" << std::endl;
    
    // 找出有满分(100)的学生
    auto perfect_student = std::ranges::find_if(students, 
        [](const std::vector<int>& scores) {
            return std::ranges::any_of(scores, [](int score) { return score == 100; });
        },
        &Student::scores  // 投影:获取学生的分数向量
    );
    
    if (perfect_student != students.end()) {
        std::cout << "\n有满分的学生: " << perfect_student->name << std::endl;
    } else {
        std::cout << "\n没有学生获得满分" << std::endl;
    }
    
    return 0;
}

投影的优势:

  1. 代码简洁性:无需创建单独的比较器函数
  2. 语义清晰:明确表达对哪些属性进行操作
  3. 可组合性:可以与其他算法参数(如比较器)组合
  4. 易于维护:当数据结构变化时,只需更改投影函数

视图(Views)基础

视图的概念

视图(View)是范围库中的核心概念,表示一个轻量级、非拥有元素的范围:

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 创建视图
    auto even_numbers = numbers | std::views::filter([](int n) { return n % 2 == 0; });
    
    // 视图不修改原始数据
    std::cout << "原始数据:";
    for (int n : numbers) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "视图内容:";
    for (int n : even_numbers) std::cout << " " << n;
    std::cout << std::endl;
    
    // 修改原始数据会影响视图
    numbers[0] = 0;  // 将1改为0
    
    std::cout << "修改后视图:";
    for (int n : even_numbers) std::cout << " " << n;
    std::cout << std::endl;
    
    return 0;
}

视图的关键特性:

  1. 轻量级:创建视图通常是O(1)操作,不涉及元素复制
  2. 惰性求值:只在遍历时才执行操作
  3. 非拥有:视图不拥有元素,只引用原始范围
  4. 可组合:可以通过管道操作符组合多个视图

基本视图类型

C++20提供了多种预定义视图,位于std::views命名空间:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>

void demonstrate_basic_views() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // all:整个范围的视图
    auto all_view = std::views::all(numbers);
    
    // filter:过滤元素
    auto even = numbers | std::views::filter([](int n) { return n % 2 == 0; });
    
    // transform:变换元素
    auto squares = numbers | std::views::transform([](int n) { return n * n; });
    
    // take:取前N个元素
    auto first_five = numbers | std::views::take(5);
    
    // drop:丢弃前N个元素
    auto skip_five = numbers | std::views::drop(5);
    
    // reverse:反转范围
    auto reversed = numbers | std::views::reverse;
    
    // 输出各种视图
    std::cout << "原始数据:";
    for (int n : all_view) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "偶数:";
    for (int n : even) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "平方值:";
    for (int n : squares) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "前5个:";
    for (int n : first_five) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "跳过前5个:";
    for (int n : skip_five) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "反转:";
    for (int n : reversed) std::cout << " " << n;
    std::cout << std::endl;
    
    // iota:生成整数序列
    auto sequence = std::views::iota(1, 6);  // 1到5
    std::cout << "整数序列:";
    for (int n : sequence) std::cout << " " << n;
    std::cout << std::endl;
    
    // 字符串相关视图
    std::string text = "Hello,World,C++,Ranges";
    auto words = text | std::views::split(',');
    
    std::cout << "分割字符串:" << std::endl;
    for (auto word : words) {
        for (char c : word) std::cout << c;
        std::cout << std::endl;
    }
}

常用预定义视图:

  1. std::views::all:引用整个范围
  2. std::views::filter:根据谓词筛选元素
  3. std::views::transform:变换每个元素
  4. std::views::take:取前N个元素
  5. std::views::take_while:取满足条件的前缀元素
  6. std::views::drop:丢弃前N个元素
  7. std::views::drop_while:丢弃满足条件的前缀元素
  8. std::views::reverse:反转范围
  9. std::views::elements:获取元组元素
  10. std::views::keys/std::views::values:获取键值对的键或值
  11. std::views::iota:生成连续递增的整数序列
  12. std::views::split:按分隔符分割范围
  13. std::views::join:连接嵌套范围

视图与容器的区别

视图和容器有几个关键区别:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <chrono>

void compare_view_and_container() {
    std::vector<int> numbers(1'000'000);
    
    // 填充数据
    for (int i = 0; i < numbers.size(); ++i) {
        numbers[i] = i;
    }
    
    // 计时创建副本
    auto start = std::chrono::high_resolution_clock::now();
    
    std::vector<int> filtered_container;
    for (int n : numbers) {
        if (n % 2 == 0) {
            filtered_container.push_back(n);
        }
    }
    
    auto mid = std::chrono::high_resolution_clock::now();
    
    // 创建视图
    auto filtered_view = numbers | std::views::filter([](int n) { return n % 2 == 0; });
    
    auto end = std::chrono::high_resolution_clock::now();
    
    // 计算时间
    auto container_time = std::chrono::duration_cast<std::chrono::milliseconds>
                          (mid - start).count();
    auto view_time = std::chrono::duration_cast<std::chrono::milliseconds>
                     (end - mid).count();
    
    std::cout << "创建容器副本时间: " << container_time << " ms" << std::endl;
    std::cout << "创建视图时间: " << view_time << " ms" << std::endl;
    
    // 内存使用对比
    std::cout << "容器元素数量: " << filtered_container.size() << std::endl;
    std::cout << "视图元素数量: " << std::ranges::distance(filtered_view) << std::endl;
    
    std::cout << "容器内存占用: " << filtered_container.size() * sizeof(int) << " 字节" << std::endl;
    std::cout << "视图理论内存占用: < 100 字节" << std::endl;
    
    // 修改原始数据影响
    numbers[0] = 1;  // 改为奇数
    
    // 容器不受影响
    std::cout << "修改原始数据后:" << std::endl;
    std::cout << "容器首元素: " << filtered_container[0] << std::endl;
    std::cout << "视图首元素: " << *filtered_view.begin() << std::endl;
}

主要区别:

  1. 内存所有权

    • 容器拥有并管理其元素的内存
    • 视图不拥有元素,只引用其他范围
  2. 创建成本

    • 创建容器副本需要复制元素,时间和空间成本与元素数成正比
    • 创建视图是O(1)操作,几乎没有开销
  3. 修改传播

    • 修改容器副本不影响原始数据
    • 修改原始数据会反映在引用它的视图中
  4. 惰性求值

    • 容器中的元素是预先计算的
    • 视图元素是按需计算的,可能多次计算
  5. 适用场景

    • 容器适用于需要存储和拥有数据的场景
    • 视图适用于临时转换和处理数据的场景

管道操作

管道语法

Ranges库引入了管道操作符(|),使数据处理更加直观:

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 使用管道语法
    auto result = numbers
                | std::views::filter([](int n) { return n % 2 == 0; })  // 偶数
                | std::views::transform([](int n) { return n * n; });   // 平方
    
    // 输出结果
    std::cout << "偶数的平方:";
    for (int n : result) std::cout << " " << n;
    std::cout << std::endl;
    
    // 管道操作符使用规则:
    
    // 1. 传统函数调用风格
    auto even_func = std::views::filter(numbers, [](int n) { return n % 2 == 0; });
    
    // 2. 管道风格 (通常更易读)
    auto even_pipe = numbers | std::views::filter([](int n) { return n % 2 == 0; });
    
    // 两种方式等效
    std::cout << "函数调用风格:";
    for (int n : even_func) std::cout << " " << n;
    std::cout << std::endl;
    
    std::cout << "管道风格:";
    for (int n : even_pipe) std::cout << " " << n;
    std::cout << std::endl;
    
    return 0;
}

管道语法的优势:

  1. 可读性:从左到右的数据流更符合人类思维习惯
  2. 组合性:轻松组合多个操作,无需中间变量
  3. 简洁性:减少样板代码
  4. 表达力:清晰表达数据转换意图

视图组合

Ranges库的强大之处在于可以轻松组合多个视图:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <cctype>  // for toupper

int main() {
    std::vector<std::string> words = {
        "apple", "banana", "cherry", "date", "elderberry", 
        "fig", "grape", "honeydew"
    };
    
    // 组合多个视图
    auto processed = words
                   | std::views::filter([](const std::string& s) { 
                         return s.length() > 5;  // 只要长词
                     })
                   | std::views::transform([](std::string s) {
                         // 转换为大写
                         for (char& c : s) c = std::toupper(c);
                         return s;
                     })
                   | std::views::take(3);  // 只取前3个
    
    // 输出结果
    std::cout << "处理结果:" << std::endl;
    for (const auto& word : processed) {
        std::cout << word << std::endl;
    }
    
    // 更复杂的组合示例
    std::vector<std::vector<int>> nested = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    auto flattened = nested
                   | std::views::join  // 展平嵌套容器
                   | std::views::filter([](int n) { return n % 2 != 0; })  // 奇数
                   | std::views::transform([](int n) { return n * n; })    // 平方
                   | std::views::reverse;  // 反转顺序
    
    std::cout << "\n展平处理:";
    for (int n : flattened) std::cout << " " << n;
    std::cout << std::endl;
    
    return 0;
}

视图组合的关键点:

  1. 顺序重要性:操作按照管道中指定的顺序执行
  2. 效率考虑:某些组合可能比其他组合更高效
  3. 视图延迟特性:无论多少操作组合,都只在遍历时执行
  4. 表达能力:复杂的数据转换可以简洁地表达

惰性求值

视图的一个核心特征是惰性求值,只在需要结果时才执行操作:

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 创建一个包含多个操作的视图
    std::cout << "创建视图..." << std::endl;
    auto result = numbers
                | std::views::filter([](int n) {
                    std::cout << "过滤: " << n << std::endl;
                    return n % 2 == 0;
                  })
                | std::views::transform([](int n) {
                    std::cout << "变换: " << n << std::endl;
                    return n * n;
                  });
    
    std::cout << "视图创建完成,尚未执行任何操作" << std::endl;
    
    // 获取首个元素
    std::cout << "\n获取第一个元素..." << std::endl;
    auto it = result.begin();
    std::cout << "第一个元素: " << *it << std::endl;
    
    // 获取下一个元素
    std::cout << "\n获取下一个元素..." << std::endl;
    ++it;
    std::cout << "下一个元素: " << *it << std::endl;
    
    // 遍历剩余元素
    std::cout << "\n遍历剩余元素..." << std::endl;
    for (; it != result.end(); ++it) {
        std::cout << "元素: " << *it << std::endl;
    }
    
    // 重新遍历整个视图
    std::cout << "\n再次遍历(注意操作会重新执行)..." << std::endl;
    for (int n : result) {
        std::cout << "结果: " << n << std::endl;
    }
    
    return 0;
}

惰性求值的特点和优势:

  1. 按需计算:只计算实际需要的元素
  2. 节省内存:不需要存储中间结果
  3. 支持无限序列:可以处理理论上无限的数据流
  4. 避免不必要的工作:如果只需要前几个结果,不会处理所有元素
  5. 潜在缺点:多次遍历会重复计算,有时需要具体化(materialize)结果

实际应用示例

数据过滤与转换

Ranges库特别适合数据过滤和转换操作:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>

struct Product {
    std::string name;
    double price;
    int stock;
    bool discontinued;
};

void product_processing() {
    std::vector<Product> products = {
        {"Laptop", 1200.0, 5, false},
        {"Smartphone", 800.0, 12, false},
        {"Tablet", 400.0, 8, false},
        {"MP3 Player", 50.0, 0, true},
        {"Headphones", 120.0, 20, false},
        {"Camera", 600.0, 3, false},
        {"Printer", 250.0, 0, false},
        {"DVD Player", 80.0, 1, true}
    };
    
    // 查找可购买的产品(有库存且未停产)
    auto available = products 
                   | std::views::filter([](const Product& p) {
                         return p.stock > 0 && !p.discontinued;
                     });
    
    std::cout << "可购买的产品:" << std::endl;
    for (const auto& product : available) {
        std::cout << product.name << " - $" << product.price
                  << " (库存: " << product.stock << ")" << std::endl;
    }
    
    // 找出价格在一定范围内的产品
    auto mid_range = products
                   | std::views::filter([](const Product& p) {
                         return p.price >= 100 && p.price <= 500;
                     })
                   | std::views::transform([](const Product& p) {
                         // 返回产品名称和价格
                         return std::make_pair(p.name, p.price);
                     });
    
    std::cout << "\n中等价位产品:" << std::endl;
    for (const auto& [name, price] : mid_range) {
        std::cout << name << " - $" << price << std::endl;
    }
    
    // 计算所有可用产品的总库存价值
    double total_value = 0.0;
    for (const auto& p : available) {
        total_value += p.price * p.stock;
    }
    
    std::cout << "\n总库存价值: $" << total_value << std::endl;
    
    // 按价格排序并显示前3名最贵的产品
    auto top_priced = products
                    | std::views::filter([](const Product& p) { 
                          return p.stock > 0; 
                      })
                    | std::ranges::to<std::vector>()  // 具体化为向量
                    | std::views::transform([](const Product& p) {
                          return std::make_pair(p.name, p.price);
                      });
    
    // 注意:视图不保证保留原始顺序,需要排序
    std::vector<std::pair<std::string, double>> sorted_prices(top_priced.begin(), top_priced.end());
    std::ranges::sort(sorted_prices, std::ranges::greater{}, &std::pair<std::string, double>::second);
    
    std::cout << "\n价格最高的3个有库存产品:" << std::endl;
    for (const auto& [name, price] : sorted_prices | std::views::take(3)) {
        std::cout << name << " - $" << price << std::endl;
    }
}

字符串处理

Ranges库使字符串处理更加简洁优雅:

#include <ranges>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <cctype>

void string_processing() {
    std::string text = "The quick brown fox jumps over the lazy dog";
    
    // 将字符串拆分为单词
    auto words = text | std::views::split(' ');
    
    std::cout << "单词列表:" << std::endl;
    for (auto word : words) {
        // 将视图转换为string以便打印
        std::string w(word.begin(), word.end());
        std::cout << w << std::endl;
    }
    
    // 找出所有长度大于3的单词
    auto long_words = text 
                    | std::views::split(' ')
                    | std::views::filter([](auto word) {
                          return std::ranges::distance(word) > 3;
                      });
    
    std::cout << "\n长度大于3的单词:" << std::endl;
    for (auto word : long_words) {
        std::string w(word.begin(), word.end());
        std::cout << w << std::endl;
    }
    
    // 将每个单词的首字母大写
    std::string capitalized;
    bool new_word = true;
    
    for (char c : text) {
        if (new_word && std::isalpha(c)) {
            capitalized += std::toupper(c);
            new_word = false;
        } else {
            capitalized += c;
            if (c == ' ') new_word = true;
        }
    }
    
    std::cout << "\n首字母大写: " << capitalized << std::endl;
    
    // 计算文本中不同字母的出现频率(不区分大小写)
    std::array<int, 26> letter_counts = {0};
    
    for (char c : text | std::views::filter(::isalpha) | std::views::transform(::tolower)) {
        letter_counts[c - 'a']++;
    }
    
    std::cout << "\n字母频率:" << std::endl;
    for (int i = 0; i < 26; ++i) {
        if (letter_counts[i] > 0) {
            std::cout << static_cast<char>('a' + i) << ": " << letter_counts[i] << std::endl;
        }
    }
}

数值计算

Ranges库可以简化数值计算和数据分析:

#include <ranges>
#include <vector>
#include <iostream>
#include <numeric>
#include <cmath>
#include <iomanip>

void numerical_calculations() {
    // 生成1到100的序列
    auto numbers = std::views::iota(1, 101);
    
    // 计算平均值
    double sum = std::accumulate(numbers.begin(), numbers.end(), 0.0);
    double mean = sum / std::ranges::distance(numbers);
    
    std::cout << "平均值: " << mean << std::endl;
    
    // 计算平方和
    double sum_squares = 0.0;
    for (int n : numbers) {
        sum_squares += n * n;
    }
    std::cout << "平方和: " << sum_squares << std::endl;
    
    // 生成斐波那契数列的前20个数
    std::vector<int> fibonacci;
    fibonacci.reserve(20);
    fibonacci.push_back(0);
    fibonacci.push_back(1);
    
    for (int i = 2; i < 20; ++i) {
        fibonacci.push_back(fibonacci[i-1] + fibonacci[i-2]);
    }
    
    std::cout << "\n斐波那契数列:";
    for (int n : fibonacci) std::cout << " " << n;
    std::cout << std::endl;
    
    // 查找小于1000的所有斐波那契数中的偶数
    auto even_fibs = fibonacci 
                   | std::views::filter([](int n) { return n < 1000 && n % 2 == 0; });
    
    std::cout << "小于1000的偶数斐波那契数:";
    for (int n : even_fibs) std::cout << " " << n;
    std::cout << std::endl;
    
    // 生成前10个质数
    std::vector<int> primes;
    for (int n = 2; primes.size() < 10; ++n) {
        bool is_prime = true;
        for (int i = 2; i <= std::sqrt(n); ++i) {
            if (n % i == 0) {
                is_prime = false;
                break;
            }
        }
        if (is_prime) primes.push_back(n);
    }
    
    std::cout << "\n前10个质数:";
    for (int p : primes) std::cout << " " << p;
    std::cout << std::endl;
    
    // 计算每个质数与下一个质数的差
    auto prime_gaps = primes
                    | std::views::slide(2)  // C++23功能,在某些编译器可能不可用
                    | std::views::transform([](auto pair) {
                          return *std::next(pair.begin()) - *pair.begin();
                      });
    
    // 如果slide不可用,可以使用替代方法
    std::vector<int> gaps;
    for (size_t i = 0; i < primes.size() - 1; ++i) {
        gaps.push_back(primes[i+1] - primes[i]);
    }
    
    std::cout << "质数间隔:";
    for (int gap : gaps) std::cout << " " << gap;
    std::cout << std::endl;
}

总结

C++20的范围(Ranges)库彻底改变了我们处理集合和算法的方式,为C++带来了更现代、更函数式的编程风格。主要优势包括:

  1. 更简洁的语法:直接对容器操作,无需显式传递迭代器对
  2. 更好的可读性:代码表达数据流向,更符合人类思维模式
  3. 组合能力:通过管道操作符轻松组合多个操作
  4. 惰性求值:按需执行操作,提高效率
  5. 视图抽象:轻量级引用原始数据,避免不必要的复制
  6. 投影参数:简化复杂数据类型的处理

范围库的基础概念、范围算法和基本视图类型为处理各种数据集合提供了强大的工具。通过管道操作和惰性求值,我们可以构建高效的数据处理流程,使代码更加简洁明了。

在下一篇文章中,我们将探讨Ranges库的更多高级特性,包括复杂视图组合、自定义视图、性能优化技巧以及与其他C++20特性的结合使用。


这是我C++学习之旅系列的第五十一篇技术文章。查看完整系列目录了解更多内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superior tigre

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值