C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++20 Range类型

C++20 Range类型的具体使用

作者:Lion 莱恩呀

本文主要介绍了C++20引入的Ranges库,重点解析了四种核心Range类型:input_range(单次遍历)、forward_range(多次前向遍历)、bidirectional_range(双向遍历)和random_access_range(随机访问),感兴趣的可以了解一下

一、引言

在 C++ 编程中,经常需要处理序列化的数据,例如数组、列表或其他容器中的元素。为了高效且简洁地操作这些序列,C++20 引入了 Ranges 库,它提供了一种更强大、更灵活的方式来表达和处理数据序列。本篇将深入探讨 Range 的各种概念,例如 input_range, forward_range, bidirectional_range, random_access_range 等,以及它们之间的区别和应用场景,更好地理解和运用 Ranges 库。

Range 的定义和核心思想:Range 是一种对序列的抽象,它代表一个可以迭代访问其元素的集合。不同于传统的迭代器对(begin()end()),Range 将序列视为一个单一的实体,简化了序列处理。一个 Range 通常由一个起始迭代器和一个结束迭代器(或哨兵)定义,用于标记序列的边界。

Ranges 库的引入极大地提高了代码的可读性和表达能力。它允许以更声明式的方式编写代码,专注于操作的逻辑而不是底层的迭代细节。

为什么需要深入理解 Range 的类型?

二、Range 概念基础

在深入探讨各种 Range 类型之前,需要先了解 Range 的一些基本概念和特征。虽然前面文章都介绍了很多次,但这里还是要简单回顾一下,有助于更好地理解不同 Range 类型之间的区别和联系。

迭代器与 Range 的关系:Range 的核心在于迭代器。每个 Range 都可以通过一对迭代器(或一个迭代器和一个哨兵)来表示。起始迭代器指向 Range 的第一个元素,结束迭代器(或哨兵)指向 Range 结尾的下一个位置。

Range 的能力由其迭代器的能力决定。例如,如果一个 Range 的迭代器支持双向移动,那么这个 Range 就支持双向遍历。

Range 的视图 (View) 提供了一种对 Range 进行非破坏性操作的方式。视图并不会复制底层的 Range 数据,而是提供了一个新的视角来观察和操作数据。

视图通常基于另一个 Range 创建,并提供不同的观察方式,例如转换、过滤或排序。视图是惰性求值的,只有在需要时才会计算结果。这可以提高代码的效率,尤其是在处理大型数据集。多个视图可以组合在一起,形成一个数据处理管道,而不会产生额外的性能开销。

三、深入理解 Range 的各种类型

这里详细介绍几种 Range 类型,包括 input_rangeforward_rangebidirectional_rangerandom_access_range,并解释它们之间的区别、特性以及应用场景。

3.1、input_range (输入范围)

input_range 是最基本的 Range 类型。它的迭代器只能单次向前移动,即只能遍历 input_range 一次;每次递增迭代器后,之前迭代器所指向的元素可能会失效。

无法保证不同迭代器指向相同的元素时,它们的值是否相等。

input_range 适用于处理只能读取一次的数据流,例如从网络套接字或传感器读取数据。

典型操作:

应用场景:

示例:

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <list>
#include <sstream>
#include <iterator>

void demonstrate_input_range() 
{
    std::istringstream input_stream("1 2 3 4 5 6");
    std::ranges::input_range auto int_range = std::ranges::istream_view<int>(input_stream);

    std::cout << "从 input stream 读取数据: " << std::endl;
    for (int value : int_range) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    // 尝试再次遍历 input_range (这将输出未定义的内容,因为 input_range 只能遍历一次)
    std::cout << "再次遍历 input stream: "<< std::endl;
    for (int value : int_range) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    std::cout << "使用 std::ranges::for_each 消费 input_range" << std::endl;
    std::istringstream input_stream2("6 7 8 9 10");
    std::ranges::input_range auto int_range2 = std::ranges::istream_view<int>(input_stream2);

    std::ranges::for_each(int_range2, [](int value) { std::cout << value * 2 << " "; });
    std::cout << std::endl;
}

int main() 
{
    demonstrate_input_range();
    return 0;
}

输出内容:

从 input stream 读取数据: 
1 2 3 4 5 6 
再次遍历 input stream: 

使用 std::ranges::for_each 消费 input_range
12 14 16 18 20 

3.2、forward_range (前向范围)

forward_range 扩展了 input_range 的功能,它支持多次向前遍历 Range 中的元素。即可以多次遍历同一个 forward_range,并且每次遍历的结果都是一致的。

forward_range 的迭代器是多遍的,可以保存迭代器的副本,并在稍后使用它重新访问相同的元素。递增迭代器不会使其他迭代器失效。

input_range 的区别:

典型操作:

需要多次遍历同一序列的应用场景:

许多标准库算法都需要 forward_range,例如 std::ranges::sort (虽然它需要更强的 random_access_range), std::ranges::findstd::ranges::count 等。

示例:

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <list>
#include <sstream>
#include <iterator>

// 演示 forward_range
void demonstrate_forward_range() 
{
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 多次遍历 forward_range
    std::cout << "第一次遍历 forward_range: ";
    for (int number : numbers) {
        std::cout << number << " ";
    }
    std::cout << std::endl;

    std::cout << "第二次遍历 forward_range: ";
    for (int number : numbers) {
        std::cout << number << " ";
    }
    std::cout << std::endl;

    // 使用 std::ranges::find 查找元素
    auto it = std::ranges::find(numbers, 3);
    if (it != numbers.end()) {
        std::cout << "找到元素 3" << std::endl;
    }
}

int main() 
{
    demonstrate_forward_range();
    return 0;
}

结果输出:

第一次遍历 forward_range: 1 2 3 4 5 
第二次遍历 forward_range: 1 2 3 4 5 
找到元素 3

3.3、bidirectional_range

定义和特性:

forward_range 的区别:

典型操作:

应用场景:

示例:

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <list>
#include <sstream>
#include <iterator>

void demonstrate_bidirectional_range() 
{
    std::list<int> numbers = {1, 2, 3, 4, 5};

    // 正向遍历 bidirectional_range
    std::cout << "正向遍历 bidirectional_range: ";
    for (int number : numbers) {
        std::cout << number << " ";
    }
    std::cout << std::endl;

    // 反向遍历 bidirectional_range
    std::cout << "反向遍历 bidirectional_range: ";
    for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    // 使用 std::ranges::find 查找元素 (正向)
    auto it = std::ranges::find(numbers, 3);
    if (it != numbers.end()) {
        std::cout << "正向找到元素 3" << std::endl;
    }

    // 使用 std::ranges::find 查找元素 (反向) - 需要使用 std::ranges::reverse_view
    auto reversed_numbers = std::ranges::reverse_view{numbers};
    auto it_reverse = std::ranges::find(reversed_numbers, 3);
    if (it_reverse != reversed_numbers.end()) {
        std::cout << "反向找到元素 3" << std::endl;
    }
}

int main() 
{
    demonstrate_bidirectional_range();
    return 0;
}

3.4、random_access_range

定义和特性:

bidirectional_range 的主要区别在于random_access_range支持高效的随机访问,而 bidirectional_range 需要通过多次递增或递减迭代器来访问特定元素。

典型操作:

应用场景举例:

示例:

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <list>
#include <array>
#include <sstream>
#include <iterator>

void demonstrate_random_access_range() 
{
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 随机访问 random_access_range
    std::cout << "随机访问 random_access_range: ";
    std::cout << numbers[0] << " " << numbers[2] << std::endl;

    // 使用 std::ranges::sort 排序
    std::ranges::sort(numbers); // 注意:示例简单,vector已排序

    std::cout << "排序后的 random_access_range: ";
    for (int number : numbers) {
        std::cout << number << " ";
    }
    std::cout << std::endl;


    // 使用 std::ranges::sort 反向排序
    std::ranges::sort(numbers, std::greater<int>());

    std::cout << "反向排序后的 random_access_range: ";
    for (int number : numbers) {
        std::cout << number << " ";
    }
    std::cout << std::endl;
}

int main() 
{
    demonstrate_random_access_range();
    return 0;
}

输出:

随机访问 random_access_range: 1 3
排序后的 random_access_range: 1 2 3 4 5 
反向排序后的 random_access_range: 5 4 3 2 1 

3.5、contiguous_range

contiguous_rangerandom_access_range 的一个特例,其元素在内存中是连续存储的。可以像数组一样,通过指针算术直接访问元素。

contiguous_rangedata() 成员函数返回一个指向底层连续内存块的指针。高效的内存访问和缓存利用率是其主要优势。

contiguous_range 的主要区别在于它的元素保证在内存中是连续的,而 random_access_range 不一定保证连续性 (例如 std::deque 在存储大量元素时,底层可能不是连续的).

典型操作:

应用场景举例:

示例:

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <list>
#include <array>
#include <string>
#include <sstream>
#include <iterator>

void demonstrate_contiguous_range() 
{
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    std::string str = "hello";

    // 使用 data() 获取指向底层数据的指针
    int* vec_ptr = vec.data();
    int* arr_ptr = arr.data();
    char* str_ptr = str.data();

    // 使用指针算术遍历 contiguous_range
    std::cout << "使用指针遍历 vector: ";
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << *(vec_ptr + i) << " ";
    }
    std::cout << std::endl;

    std::cout << "使用指针遍历 array: ";
    for (size_t i = 0; i < arr.size(); ++i) {
        std::cout << *(arr_ptr + i) << " ";
    }
    std::cout << std::endl;

    std::cout << "使用指针遍历 string: ";
    for (size_t i = 0; i < str.size(); ++i) {
        std::cout << *(str_ptr + i);
    }
    std::cout << std::endl;
}

int main() 
{
    demonstrate_contiguous_range();
    return 0;
}

输出:

使用指针遍历 vector: 1 2 3 4 5 
使用指针遍历 array: 1 2 3 4 5 
使用指针遍历 string: hello

四、类型之间的层次结构与选择

C++20 中的 Range 概念建立在迭代器类别之上,形成了一个明确的层次结构,旨在精确描述可迭代序列的能力。这种层次结构意味着更高级别的 Range 概念包含了低级别 Range 的所有能力,从而允许算法根据 Range 的能力自动选择最有效的实现。

这种层次结构的关键在于,如果一个 Range 满足了某个更高级别的概念,那么它也自动满足了所有比它低级别的概念,但 input_range 不包含 output_range 的写入能力。

选择合适的 Range 类型对于编写高效、正确且可读的 C++ 代码至关重要。C++20 Ranges 库通过概念在编译时强制执行这些要求,从而提高了代码的健壮性。

性能考量:

五、总结

C++20 引入的 Ranges 库提供了一种强大且灵活的机制来处理序列数据。通过定义 input_rangeforward_rangebidirectional_rangerandom_access_rangecontiguous_range 等不同类型的 Range,Ranges 库能够更精确地表达序列的能力,从而实现更高效的算法选择和执行

C++20 Ranges 库的设计理念是根据 Range 的能力自动选择最优的算法实现。因此,选择合适的 Range 类型不仅可以提高代码的可读性和可维护性,还能在编译时优化性能,避免不必要的开销。

到此这篇关于C++20 Range类型的具体使用的文章就介绍到这了,更多相关C++20 Range类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文