Effective STL:04迭代器

26:iterator优先于const_iterator、reverse_iterator 以及 const_reverse_iterator(before C++11)

STL中的所有标准容器都提供了4种迭代器类型。iterator的作用相当于T*,而const_iterator则相当于const T*;对一个iterator或者const_iterator进行递增则可以移动到容器中的下一个元素,通过这种方式可以从容器的头部一直遍历到尾部;reverse_iterator与const_reverse_iterator同样分别对应于T*和const T*,所不同的是,对这两个迭代器进行递增的效果是由容器的尾部反向遍历到容器头部。

在C++11之前,容器的成员函数如insert、erase等仅接受iterator类型的参数,而不是const_iterator、reverse_iterator 以及 const_reverse_iterator,而C++11之后,这些函数就只接受const_iterator了。下面是这几种迭代器之间的转换关系:

 

从iterator到const_iterator之间,或者从iterator到reverse_iterator之间,或者从 reverse_iterator到const_reverse_iterator之间都存在隐式转换。并且,通过调用 reverse_iterator的 base 成员函数,可以将reverse_iterator转换为 iterator。类似地,const_reverse_iterator也可以通过base成员函数被转换为const_iterator。然而,通过base得到的迭代器也许并非你所期待的迭代器,条款28中详细讨论这一点。

从图中还可以看出,没有办法从const_iterator转换得到iterator,也无法从const_reverse_iterator得到reverse_iterator。

为什么应该尽可能使用iterator,而避免使用const或者reverse型的迭代器,主要有下面的原因:

有些版本的insert和erase函数要求使用iterator,const和reverse型的迭代器不能满足这些函数的要求。

要想隐式地将一个const_iterator转换成iterator是不可能的;

从reverse_iterator转换而来的iterator在使用之前可能需要相应的调整,条款28讨论为什么需要调整以及何时进行调整;

由此得出结论,应尽量使用iterator而不是const或reverse型的迭代器,可以使容器的使用更为简单而有效,并且可以避免潜在的问题。但是该条款在C++11之后就不合适了。

27:使用 distance 和 advance 将容器的 const_iterator 转换成iterator

下面的代码试图把一个 const_iterator 强制转换成iterator:

typedef deque<int> IntDeque; 
typedef lntDeque::iterator Iter;
typedef lntDeque::const_iterator Constlter;
Constlter ci; // ci is a const_iterator
…
Iter i(ci); // error! no implicit conversion from const_iterator to iterator
Iter i(const_cast<lter>(ci)); // still an error! can't cast a const_iterator to an iterator

 这里只是以deque为例,但是用其他容器类(list、set、multiset、map、multimap)得到的结果也是一样的。也许在vector或string类的情形下,强制转换的代码行能够通过编译,但这是非常特殊的情形,我们稍后再考虑。

包含显式类型转换的代码不能通过编译的原因在于,对于这些容器类型,iterator和 const_iterator是完全不同的类。试图将一种类型转换为另一种类型是毫无意义的,这就是const_cast转换被拒绝的原因。reinterpret_cast、static_cast甚至C语言风格的类型转换也不能胜任。

不过,对于vector和string容器来说,以上包含const_cast的代码也许能够通过编译。 因为通常情况下,大多数STL实现都会利用指针作为vector和string容器的迭代器。对于这样的实现而言,vector<T>::iterator和vector<T>::const_iterator分别被定义为T*和const T*, string::iterator和string::const_iterator则分别被定义为 char*和 const char*。因此,对于这样的实现,从const_iterator到iterator的const_cast转换被最终解释成从const T*到T* 的转换,因而可以通过编译。然而,即便在这样的STL实现中,reverse_iterator和 const_reverse_iterator仍然是真正的类,所以不能直接将const_reverse_iterator通过const_cast强制转换成reverse_iterator。

如果你有一个const_iterator并且可以访问它所在的容器,那么这里有一条安全的、可移植的途径能得到对应的iterator,而且用不着涉及类型系统的强制转换。下面是这种方案的本质:

typedef deque<int> IntDeque; //as before
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator Constlter;

IntDeque d;
Constlter ci;
… // make ci point into d
Iter i(d.begin()); // initialize i to d.begin()
advance(i, distance<ConstIter>(i, ci)) //move i up to where ci is

 首先创建一个新的iterator,将它指向容器的起始位置,然后取得const_iterator 距离容器起始位置的偏移量,并将iterator向前移动相同的偏移量即可。这项任务是通过<iterator>中声明的两个函数模板来实现的:distance用以取得两个迭代器之间的距离;advance则用于将一个迭代器移动指定的距离。如果i和ci指向同一个容器,则表达式advance(i, distance<ConstIter>(i, ci))会使i和ci指向容器中相同的位置。

调用distance时之所以要指定<ConstIter>,是因为i和ci的类型不相同,如果不指定,会导致模板实参推导失败,下面是distance的声明:

template<typename InputIterator〉
typename iterator_traits<InputIterator>::difference_type distance(Inputlterator first, Inputlterator last);

 这种实现的效率取决于你所使用的迭代器。对于随机访问的迭代器(如vector、string和deque产生的迭代器)而言,它是一个常数时间的操作;对于双向迭代器(所有其他标准容器的迭代器,以及某些哈希容器实现的迭代器)而言,它是一个线性时间的操作。

实际上,你应该重新审视你的设计,是否真的需要从const_iterator到iterator的转换呢?

28:正确理解由reverse_iterator的base()成员函数所产生的iterator的用法

调用reverse_iterator的base成员函数可以得到与之相对应的iterator,但是该iterator和reverse_iterator实际上指向不同的位置,在reverse_iterator与对应的base()产生的iterator之间存在偏移,这段偏移也正好勾画出了rbegin和rend()与对应的begin()和end()之间的偏移。

如果希望在reverse_iterator指定的位置上插入或删除元素,因为insert或erase函数不接受reverse_iterator作为参数,只能转换为iterator才行。这就需要根据实际情况,考虑到插入和删除的具体位置,在对reverse_iterator.base得到的iterator做调整。比如如果要删除reverse_iterator指向的位置,则将其转换为iterator之后,应该是删除该iterator前面的元素,而非iterator指向的元素。

29:对于逐个字符的输入请考虑使用istreambuf_iterator

假如想把一个文本文件的内容拷贝到一个string对象中,以下的代码看上去是一种合理的解决方案: 

ifstream inputFile("interestingData•txt");
string fileData((istream_iterator<char>(inputFile)), istream_iterator<char>()); 

 但是这段代码并没有把文件中的空白字符拷贝到string对象中。因为istream_iterator使用operator>>函数来完成实际的读操作,而默认情况下operator>>函数会跳过空白字符。

如果要保留空白字符,那么所需要做的工作是改写这种默认行为,只要清除输入流的skipws标志即可:

ifstream inputFile("interestingData.txt");
inputFile.unsetf(ios::skipws); 
string fileData((istream_iterator<char>(inputFile)), istream_iterator<char>());

 现在,inputFile中的所有字符都会被拷贝到fileData中。然而,你可能会发现整个拷贝过程远不及你希望的那般快。istream_iterator内部使用的 operator>>函数实际上执行了格式化的输入,这意味着你每调用一次operator>>操作符,它都要执行许多附加的操作。有一种更为有效的途径,那就是使用istreambuf_iterator。

istreambuf_iterator的使用方法与istream_iterator大致相同,但是istream_iterator对象使用 operator>>从输入流中读取单个字符,而istreambuf_iterator<char>则直接从流的缓冲区中读取下一个字符。

猜你喜欢

转载自www.cnblogs.com/gqtcgq/p/10125072.html
今日推荐