C++11 引入了许多新特性,其中包括对 STL(Standard Template Library)的改进。在 STL 容器中,rbegin() 和 rend() 是两个新的成员函数,它们分别返回指向容器最后一个元素的反向迭代器(reverse iterator)和指向容器"理论上的前一个元素"的反向迭代器(该迭代器实际上并不指向任何有效元素,而是作为结束标记)。
反向迭代器介绍
反向迭代器是一种特殊的迭代器,它允许我们按照与正向迭代器相反的顺序遍历容器。也就是说,如果我们使用正向迭代器从容器的开始遍历到结束,那么使用反向迭代器就是从容器的结束遍历到开始。
使用场景
反向迭代器在需要逆序遍历容器元素的场景中非常有用。例如,你可能想要从后向前检查一个字符串是否以某个子串结尾,或者你可能想要逆序打印一个向量的所有元素。
与正向迭代器原理对比
正向迭代器和反向迭代器在原理上的主要区别在于它们如何定义"下一个"元素。对于正向迭代器,++it 将迭代器向前移动到下一个元素;而对于反向迭代器,++it 将迭代器向后移动到"上一个"元素。注意,这里的"上一个"和"下一个"是相对于容器的遍历方向而言的。
代码对比
以下是使用正向迭代器和反向迭代器遍历 std::vector 的示例代码:
cpp
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用正向迭代器遍历
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
std::cout << *it << ' ';
}
std::cout << '\n';
// 使用反向迭代器遍历
for (std::vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); ++rit) {
std::cout << *rit << ' ';
}
std::cout << '\n';
return 0;
}
在这个示例中,我们首先使用正向迭代器从前往后遍历 vector,然后使用反向迭代器从后往前遍历 vector。输出将是:
bash
1 2 3 4 5
5 4 3 2 1
注意,虽然正向迭代器和反向迭代器在语法上有所不同(特别是它们的类型),但它们的用法非常相似:都可以使用 * 运算符来解引用迭代器以访问元素,都可以使用 ++ 运算符来移动迭代器,等等。
看完你会发现反向迭代器其实也没有太多特殊的。是的,但反向迭代器本身设置了很多的限制,所以认识反向迭代器,我们一定要熟悉其限制机制;
重点-限制情况
- 依赖双向迭代器:
反向迭代器通常基于容器的双向迭代器实现。因此,如果容器仅支持单向迭代器(如某些输入迭代器),则无法使用反向迭代器。
这意味着反向迭代器不能用于如 std::istream_iterator 这样的单向迭代器类型。 - 特定操作不可用:
反向迭代器不支持随机访问迭代器的某些操作,如 operator+=、operator+、operator-=、operator- 和 operator[]。这是因为反向迭代器的内部机制并不直接支持这些操作。
例如,你不能直接通过 rit + 3 来跳过三个元素(其中 rit 是一个反向迭代器),因为这需要知道"理论上的前一个元素"的位置,这在反向迭代器的上下文中是不可知的。 - 引用和指针的语义:(这个难以理解就在下一p中熟悉-C++17新特性)
反向迭代器的 &*rit(取反向迭代器 rit 指向元素的地址)操作与正向迭代器有所不同。在反向迭代器的上下文中,&*rit 实际上引用的是迭代器在原有序列中引用的元素之外(右侧)一个位置的元素。
这意味着你不能直接通过反向迭代器来获取容器中元素的原始地址(除非你使用额外的逻辑来转换它)。 - 迭代器的有效性:
反向迭代器的有效性同样受到容器修改操作的影响。如果在遍历过程中修改了容器的大小(如添加或删除元素),则可能导致反向迭代器失效。
需要注意的是,即使正向迭代器在容器修改后仍然有效,其对应的反向迭代器也可能不再有效。 - 类型差异:
反向迭代器的类型与正向迭代器的类型不同。在 C++ 中,你需要使用如 std::vector::reverse_iterator 这样的类型来声明反向迭代器。
这意味着你不能直接将正向迭代器赋值给反向迭代器,或者期望它们在类型上兼容。 - 使用场景限制:
虽然反向迭代器在处理需要逆序遍历容器的场景时非常有用,但它们并不适用于所有情况。在某些情况下,使用正向迭代器可能更加直观和高效。