C++知识点29——使用C++标准库(迭代器适配器)

在上一篇文章https://blog.csdn.net/Master_Cui/article/details/108512730谈到的迭代器是基本的五种类型的迭代器

但是随着C++标准库的扩展,又实现了一些迭代器,这些迭代器的操作和之前的五中操作类似,所以成为迭代器适配器,类似容器适配器

 

常用的容器适配器有三种:

1.插入迭代器:该迭代器能向容器中添加元素

2.流迭代器:该迭代器能操作C++标准库中的IO对象

3.反向迭代器:从容器尾元素向首元素反向移动的迭代器

 

一、插入迭代器

插入迭代器分为三种:back inserter,front insertter和inserter

1.back inserter的相关声明

template <class Container> 
class back_insert_iterator;

template <class Container>
back_insert_iterator<Container> back_inserter (Container& x);

explicit back_insert_iterator (Container& x) : container(&x) {}//back_insert_iterator的构造函数

其中back_insert_iterator是back inserter的类名,back_inserter是back_insert_iterator的生成函数,返回back_insert_iterator的对象,该插入迭代器指向被插入的元素

该迭代器向容器的末尾插入新的元素,内部实现调用了push_back,所以,back inserter插入元素的容器必须支持push_back操作,所以,只有vector,list,deque和string支持back inserter

示例

void backinsertertest()
{
	list<int> l;
	back_insert_iterator<list<int>> lit(l);
	*lit=1;
	++lit;
	*lit=2;
	++lit;
	for (list<int>::iterator it=l.begin();it!=l.end();++it) {
		cout<<*it<<endl;
	}
	cout<<"---------------"<<endl;
	back_inserter(l)=3;
	back_inserter(l)=4;
	for (list<int>::iterator it=l.begin();it!=l.end();++it) {
		cout<<*it<<endl;
	}
	cout<<"---------------"<<endl;
	list<int> l2;
	copy(l.begin(), l.end(), back_inserter(l2));
	cout<<"---------------"<<endl;
	for (list<int>::iterator it=l2.begin();it!=l2.end();++it) {
		cout<<*it<<endl;
	}
}

 

注意:list虽然支持push_back,但是copy时,back_inserter中的参数不能是原来的容器,必须得是另外一个list对象

当把上面的第20行代码替换为copy(l.begin(), l.end(), back_inserter(l));时,程序会卡在第20行,原因暂时未知!!!!

总之,当使用copy拷贝元素时,目标容器最好和源容器不同

如果不在copy中使用back_inserter,那么插入的容器要事先分配元素数量,因为copy不负责分配内存

具体使用方法见博客https://blog.csdn.net/Master_Cui/article/details/108404257

 

2.front inserter的相关声明

template <class Container>
class front_insert_iterator;

template <class Container>
front_insert_iterator<Container> front_inserter (Container& x);

explicit front_insert_iterator (Container& x) : container(&x) {}

意义参考back inserter

该迭代器向容器的头部插入新的元素,内部实现调用了push_front,所以,front inserter插入元素的容器必须支持push_front操作,所以,list和deque支持back inserter

 

示例

void frontinsertertest()
{
	list<int> l;
	front_insert_iterator<list<int>> lit(l);
	*lit=1;
	++lit;
	*lit=2;
	++lit;
	for (list<int>::iterator it=l.begin();it!=l.end();++it) {
		cout<<*it<<endl;
	}
	cout<<"---------------"<<endl;
	front_inserter(l)=3;
	front_inserter(l)=4;
	for (list<int>::iterator it=l.begin();it!=l.end();++it) {
		cout<<*it<<endl;
	}
	cout<<"---------------"<<endl;
	copy(l.begin(), l.end(), front_inserter(l));
	cout<<"---------------"<<endl;
	for (list<int>::iterator it=l.begin();it!=l.end();++it) {
		cout<<*it<<endl;
	}
}

由于push_front会把元素插入到容器的头部,所以,容器里的数据是反序的

在拷贝时将4321逐个拷贝到容器的头部,所以最终结果是12344321

 

非常的怪异,front_insert就没有上面卡住的问题,C++标准库真是个神奇的东西

 

3.inserter的相关声明

template <class Container> 
class insert_iterator;

template <class Container>
insert_iterator<Container> inserter (Container& x, typename Container::iterator it);

explicit insert_iterator (Container& x, typename Container::iterator i) : container(&x), iter(i) {}

意义和back inserter类似

该迭代器向容器中的指定位置插入元素,内部调用的是成员函数insert(pos, val)

 

示例

void insertertest()
{
	vector<int> v;
	insert_iterator<vector<int>> it(v,v.begin());//创建一个插入迭代器对象,并指定插入的容器以及插入的位置
	*it=1;
	++it;
	*it=2;
	++it;
	for (vector<int>::iterator it=v.begin();it!=v.end();++it) {
		cout<<*it<<endl;
	}
	cout<<"---------------"<<endl;
	inserter(v, v.begin())=3;
	inserter(v, v.begin())=4;//分别在头部插入3和4
	for (vector<int>::iterator it=v.begin();it!=v.end();++it) {
		cout<<*it<<endl;
	}
	cout<<"---------------"<<endl;
	cout<<v.capacity()<<endl;
	v.reserve(v.size()*2);
	copy(v.begin(),v.end(),inserter(v, v.end()));
	for (vector<int>::iterator it=v.begin();it!=v.end();++it) {
		cout<<*it<<endl;
	}
}

因为copy不负责扩大容器的空间,所以上述代码中,当要使用copy重新向vector中添加元素时,需要扩大vector对象所能容纳元素的个数,否则会产生迭代器失效的问题

copy之前,vector在不重新分配内存的情况下最多能容纳四个元素,所以要想容纳八个元素,需要调用reserve方法重新分配内存

关于vector的增长与迭代器失效见博客https://blog.csdn.net/Master_Cui/article/details/107503634

上述所说用一个表总结如下

二、流迭代器

常用的流迭代器有两个:istream_iterator(输入流迭代器)、ostream_iterator(输出流迭代器),这两个迭代器一般和cin与cout一起使用

 

1.istream_iterator以及相关操作

其中,T是流中数据的类型

 

示例

void istream_iteratortest()
{
	istream_iterator<int> cinit(cin);
	istream_iterator<int> eofit;//eof迭代器对象
	while(cinit != eofit) {
		cout<<*cinit<<endl;//将输入迭代器的数据解引用并写入输出流
		++cinit;//读取下一个输入数据
	}
}

通过输入流迭代器,可以像操作其他常用对象一样操作输入流

 

2.ostream_iterator以及相关操作

其中,T是写入流数据的类型

 

示例

void ostream_iteratortest()
{
	ostream_iterator<int> coutit(cout, ",");
	*coutit=1;
	++coutit;
	*coutit=2;
	++coutit;

	deque<int> dq={3,4,5,6,7};
	copy(dq.begin(),dq.end(),coutit);
	cout<<endl;
}

上面的代码通过copy将deque中的数据写入到输出流,然后依次打印出来,一行实现了循环打印的功能

 

三、反向迭代器

反向迭代器重新实现了递增递减操作,使其和一般迭代器的行为正好相反,一般用来反向操作容器中的元素

 

示例

void reverseiteratortest()
{
	vector<int> v={1,2,3,4,5,6,7,8,9};
	for (vector<int>::reverse_iterator it=v.rbegin();
		it!=v.rend();++it) {
		cout<<*it<<endl;
	}
}

上述代码实现了反序打印容器的功能,使用方法begin与end相同

 

C++标准库中使用rbegin()和rend()来获取反向迭代器,其中,rbegin()指向的元素是最后一个元素,迭代器位置最后一个元素的后一个位置,rend()指向的是首元素的前一个元素(不确定的值),迭代器位置首元素的位置

也就是说,反向迭代器的实际位置和值所在的位置是错开的,用下图来表示

上面这种反人类的特性就造成了下面的这个bug,

示例

void reverseiteratortest2()
{
	vector<int> v={1,2,3,4,5,6,7,8,9};
	vector<int>::iterator pos=find(v.begin(),v.end(),5);
	if (pos!=v.end()) {
		cout<<*pos<<endl;
	}

	vector<int>::reverse_iterator rpos(pos);//注意,不能使用等号进行初始化
	if (rpos!=v.rend()) {
		cout<<*rpos<<endl;
	}
}

正因为反向迭代器的这个bug,所以相同位置的迭代器解引用出来的值确实不一样的

 

用图来表示就是下面这样的

如何避免这个bug呢,那就是使用一般迭代器,如果代码里有反向迭代器,通过base函数将其转化为一般迭代器

代码如下

void reverseiteratortest2()
{
	vector<int> v={1,2,3,4,5,6,7,8,9};
	vector<int>::iterator pos=find(v.begin(),v.end(),5);
	if (pos!=v.end()) {
		cout<<*pos<<endl;
	}

	vector<int>::reverse_iterator rpos(pos);
	if (rpos!=v.rend()) {
		cout<<*rpos.base()<<endl;//这里不同,调用反向迭代器的base方法将反向迭代器转化为一般迭代器
	}
}

所以,不要混用反向迭代器和一般迭代器,在使用迭代器的时候,优先使用一般迭代器,尽量不使用反向迭代器,如果使用了,在解引用的时候,最好通过base成员函数将反向迭代器转化为一般迭代器,真不明白C++的反向迭代器为啥会设计的这么脑抽

 

参考

《C++ Primer》

《C++标准库》

http://www.cplusplus.com/reference/iterator/

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值