正向迭代器和反向迭代器之间的关系
1.1 为何使用迭代器
谈到迭代器,我们不可避免地会想到STL和泛型编程。在《C++Primer》一书中这样描述到,STL是一种泛型编程(generic programming)。面向对象编程关注的是编程的数据方面,而泛型编程关注的是算法。他们之间的共同点是抽象和创建可重用性代码,但两者的理念绝然不同:泛型编程旨在编写独立于数据类型的代码。这一点最近在学习侯捷老师的《STL源码剖析》一书中深有感触。
总的来说,模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。因而在vector, set, list中,我们都能见到迭代器的身影。
1.2 迭代器的类型
STL定义了5种迭代器的类型:输入迭代器,输出迭代器,正向迭代器,双向迭代器和随机访问迭代器。此外,还有一些专用的预定义迭代器类型:reverse_iterator, back_insert_iterator, front_insert_iterator和insert_iterator。
1.3 正向迭代器
正向迭代器只使用++运算符来遍历容器,所以它每次沿容器向前移动一个元素。正向迭代器可以修改和读取数据。
1.4 反向迭代器
对于反向迭代器执行递增操作将导致它被递减。最常见的一种使用方式如下:
std::set<int> container;
for (auot iter = container.rbegin(); iter != container.rend(); iter++ )
在这样的场景下,我们可以从容器尾部进行一个遍历。在大数据量且容器内部元素有序的情况下显得尤为有用。
1.5 具体场景
迭代器在具体的应用场景下可能会和std::find_if()搭配使用,通过条件匹配返回一个置顶元素的迭代器,然后通过erase将其删除。但在反向迭代器中,这种场景将会失效,因为erase并不支持反向迭代器。因而,我们需要一种合理的转换方式。
关于reverse_iterator的源码:
private:
#ifndef _LIBCPP_ABI_NO_ITERATOR_BASES
_Iter __t; // no longer used as of LWG #2360, not removed due to ABI break
#endif
#if _LIBCPP_STD_VER > 17
static_assert(__is_cpp17_bidirectional_iterator<_Iter>::value || bidirectional_iterator<_Iter>,
"reverse_iterator<It> requires It to be a bidirectional iterator.");
#endif // _LIBCPP_STD_VER > 17
protected:
_Iter current;
我们可以观察到存在一个current的变量,同样也是一个迭代器类型。它的应用场景是什么呢?我们继续观察
reference operator*() const {_Iter __tmp = current; return *--__tmp;}
reverse_iterator& operator++() {--current; return *this;}
reverse_iterator& operator--() {++current; return *this;}
*是解引用的一种方式,我们可以看到,将current赋给了一个临时变量__tmp, 然后该变量先执行自减操作,再取出存储的值。前面我们提到,反向迭代器其实是从后往前,从rbegin到rend,在直观上是一种"+“, 实际上,从正向的角度来看,其实是”-", 这这好对应代码块的下两行,也就是说current和正向迭代器是存在关联的。
rbegin指向容器的最后一个元素,存在超尾现象,因此,在取最后一个元素时,首先需要将目前指向位置自减,然后再解引用。其余元素依次类推。
因而将反向转为正向时,我们可以调用base()函数,得到current。但实际上current目前实际指向是目标元素的后一位,再去解引用并不是预期行为。所以我们需要首先对反向迭代器执行一个自增,current会自减,进而指向目标元素,通过base()返回正确的迭代器。