【C++ STL应用与实现】18: 怎样使用迭代器适配器
本系列文章的文件夹在这里:文件夹. 通过文件夹里能够对STL整体有个大概了解
前言
本文介绍了STL中的迭代器适配器(iterator adapter)的概念及其用法演示样例。迭代器适配器能够和标准库中的算法配合使用,达到一些特殊的效果。
迭代器适配器分为以下几类:
reverse iterator : 反向迭代器
insert iterator : 插入型迭代器
stream iterator : 流迭代器
move iterator : 移动型迭代器
reverse iterator 反向迭代器
顾名思义。reverse就是反其道而行之。正常的迭代器是从前往后的方向递增,而反向迭代器则是从后向前递增的。
支持双向迭代的容器通常都有rbegin(), rend()这对接口。它们的返回值就是reverse iterator。使用这对反向迭代器来遍历容器就会实现从后向前的效果。
vector<int> v = { 1, 2, 3};
auto rbeg = v.rbegin();
while (rbeg != v.rend())
{
cout << *rbeg << endl;
++rbeg;
}
//----------------------- reverse iterator ----------------------
RUN_GTEST(IteratorAdapter, ReverseIterator, @);
array<int, 5> a = {1, 2, 3, 4, 5};
printContainer(a, "a: ");
auto pos1 = find(a.begin(), a.end(), 5);
// rpos1 是pos1的反向迭代器
array<int, 5>::reverse_iterator rpos1(pos1);
// 对rpos1提取值,会发现与pos1中的值不一样。这是bug吗 ?
EXPECT_EQ(4, *rpos1);
// 不,不是bug。标准库是有益这样设计的,它的设计是:
// 从一个迭代器pos1构造其反向迭代器rpos1, 两者在“物理”上是不变的。即指向同一个地方。
// 可是在取迭代器指向的值的时候(我们叫它“逻辑”地址),二者的解释是不一样的,即逻辑地址不一样。
//
// pos1: physically(logically)
// |
// |
// 1 2 3 4 5
// ^ ^
// | |
// | |
// rpos1: logically physically
//
// 对于pos1。其逻辑地址与物理地址一致。其逻辑值是5;
// 而对于rpos1, 其逻辑地址在物理地址的前一个位置,所以其逻辑值是4.
// 这样设计的原因是由于正向迭代器的半开区间特性造成的,正向迭代器的end是最后一个元素的下一个位置。
// 反向迭代器里并没有超过第一个位置的前一个位置这个概念,
// 标准库就使用了逻辑地址和物理地址独立这样的设计,实现了反向的迭代,
// 反向迭代器和正向迭代器的物理位置是一样的,仅仅只是在取值的时候往前一位来取,
// 当物理位置到达第一个位置的时候。就已经取不到值了,也就代表反向迭代器的结束。
// 这样的设计的优点是对于区间的反向操作非常easy:
array<int, 5>::reverse_iterator rEnd(a.end());
// rEnd also point to physical location: a.end(),
// but its logical location is a.end() - 1, so equal to 5.
EXPECT_EQ(5, *rEnd);
// reverse range.
auto posA = find(a.begin(), a.end(), 2);
auto posB = find(a.begin(), a.end(), 5);
pln("in normal order: ");
copy(posA, posB, ostream_iterator<int>(cout, " "));
cr;
array<int, 5>::reverse_iterator rPosA(posA);
array<int, 5>::reverse_iterator rPosB(posB);
pln("in reverse order: ");
copy(rPosB, rPosA, ostream_iterator<int>(cout, " "));
cr;
// 使用base()函数来把一个反向迭代器转为正向迭代器
auto recoverPos = rpos1.base();
EXPECT_EQ(5, *recoverPos);
END_TEST;
insert iterator 插入型迭代器
插入型迭代器能够使标准库中算法对元素进行赋值操作的语义转化为对元素的插入操作语义。由于它们会改变容器,它们须要使用一个容器来初始化,如以下的代码所看到的:
插入型迭代器分为以下三种:
back_insert_iterator
or back inserter. 在后面插入型迭代器
会对初始化它的容器调用push_back
以完毕后面插入元素的操作。
//----------------------- inserter ----------------------
RUN_GTEST(IteratorAdapter, InserterTest, @);
array<int, 5> a = { 1, 2, 3, 4, 5 };
vector<int> v = {};
//------------- 1. back inserter ----------------
// 1. back_inserter(con) : call con.push_back().
// 创建一个`back_insert_iterator`的第一种方式
back_insert_iterator<vector<int>> backInserter(v);
*backInserter = 1;
++backInserter; // do nothing, can skip this
*backInserter = 2;
++backInserter; // do nothing, can skip this
printContainer(v, "v: "); // 1 2
// 创建一个back_insert_iterator的另外一种方式
back_inserter(v) = 3;
back_inserter(v) = 4;
printContainer(v, "v: "); // 1 2 3 4
copy(a.begin(), a.end(), back_inserter(v));
printContainer(v, "v: "); // 1 2 3 4 1 2 3 4 5
END_TEST;
front_insert_iterator
or front inserter. 在前面插入型迭代器
与back_insert_iterator
相似。此迭代器调用容器的push_front
来完毕在前面插入元素的操作。
//------------- 2. front inserter ----------------
// front_inserter(con): call con.push_front().
list<int> l = {};
// 第一种创建front_insert_iterator的方式
front_insert_iterator<list<int>> frontInserter(l);
*frontInserter = 1;
++frontInserter;
*frontInserter = 2;
++frontInserter;
printContainer(l, "l: "); // 2 1
// 另外一种创建front_insert_iterator的方式
front_inserter(l) = 3;
front_inserter(l) = 4;
printContainer(l, "l: "); // 4 3 2 1
copy(a.begin(), a.end(), front_inserter(l));
printContainer(l, "l: "); // 5 4 3 2 1 4 3 2 1
insert_iterator
or general inserter. 通用型插入迭代器
最后这样的插入型迭代器是最通用的迭代器, 它对容器调用insert(value, pos)方法。使得没有push_back
, push_front
操作的容器,比方关联式容器能够使用这样的迭代器。它相对于前两种适配器,须要一个额外的參数pos以指示插入位置。
//------------- 3. general inserter ----------------
// inserter(con, pos) : call con.insert(), and return new valid pos.
set<int> s = {};
insert_iterator<set<int>> generalInserter(s, s.begin());
*generalInserter = 5;
++generalInserter;
*generalInserter = 1;
++generalInserter;
*generalInserter = 4;
printContainer(s, "s: "); // 1 4 5
inserter(s, s.end()) = 3;
inserter(s, s.end()) = 2;
printContainer(s, "s: "); // 1 2 3 4 5
list<int> copyS;
copy(s.begin(), s.end(), inserter(copyS, copyS.begin()));
printContainer(copyS, "copyS: "); // 1 2 3 4 5
stream iterator 流迭代器
分为:ostream_iterator
和istream_iterator
.
ostream_iterator
输出流迭代器
//----------------------- stream iterator ----------------------
RUN_GTEST(IteratorAdapter, StreamIterator, @);
// 输出迭代器演示样例
//------------- 1. ostream iterator ----------------
// ostream_iterator(stream, delim)
// 指定一个流类型变量和分隔符来创建一个流迭代器
ostream_iterator<int> outputInt(cout, "\n");
*outputInt = 1; // output 1 \n
++outputInt;
*outputInt = 2; // output 2 \n
++outputInt;
cr;
array<int, 5> a = {1, 2, 3, 4, 5};
copy(a.begin(), a.end(), ostream_iterator<int>(cout)); // no delim, 12345
cr;
string delim("-->");
copy(a.begin(), a.end(), ostream_iterator<int>(cout, delim.c_str()));
cr; // 1-->2-->3-->4-->5-->
istream_iterator
输入流迭代器
// 3. 输入流迭代器演示样例:
//------------- 2. istream iterator ----------------
// istream_iterator(stream)
pln("input some char, end with EOF");
// 创建一个输入流迭代器,注意创建的时候即已读取一个元素。
istream_iterator<char> charReader(cin);
// 输入流的结束位置
istream_iterator<char> charEof;
while (charReader != charEof)
{
pln(*charReader);
++charReader;
}
cin.clear();
//------------- 3. istream & ostream & advance ----------------
pln("input some string, end with EOF");
istream_iterator<string> strReader(cin);
ostream_iterator<string> strWriter(cout);
while (strReader != istream_iterator<string>())
{
advance(strReader, 1);
if (strReader != istream_iterator<string>())
{
*strWriter++ = *strReader++;
}
}
cr;
END_TEST;
move iterator
since C++11, 移动语义的提出大大提高了一些涉及到转发參数的函数调用过程之中(perfect forwarding完美转发)參数传递的效率,通过把元素内部底层的东西移动到新的元素来避免拷贝开销。
由于这个原因也提供了移动的迭代器适配器以实现须要移动语义的场合,以下是一段示意的代码:
//----------------------- move iterator ----------------------
list<string> l = {"hello", "tom", "jerry"};
vector<string> v(l.begin(), l.end()); // copy l.
vector<string> v2(make_move_iterator(l.begin()), // move l.
make_move_iterator(l.end()));
源代码与參考链接
作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!
欢迎訪问github博客,与本站同步更新
【C++ STL应用与实现】18: 怎样使用迭代器适配器的更多相关文章
- .NET设计模式(18):迭代器模式(Iterator Pattern)(转)
概述 在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据. ...
- vector源码(参考STL源码--侯捷):空间分配导致迭代器失效
vector源码1(参考STL源码--侯捷) vector源码2(参考STL源码--侯捷) vector源码(参考STL源码--侯捷)-----空间分配导致迭代器失效 vector源码3(参考STL源 ...
- STL标准库-迭代器适配器
技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性 这次主要介绍一下迭代器适配器.以reverse_iterator(反向迭代器),insert_iterator(插入迭代器),o ...
- Effective STL 学习笔记 Item 18: 慎用 vector<bool>
vector<bool> 看起来像是一个存放布尔变量的容器,但是其实本身其实并不是一个容器,它里面存放的对象也不是布尔变量,这一点在 GCC 源码中 vector<bool> ...
- STL——容器(Set & multiset)的迭代器
1.set.insert(elem); //在容器中插入元素. 2.set.begin(); //返回容器中第一个数据的迭代器. 3.set.end(); / ...
- STL 迭代器适配器(iterator adapter)
iterator adapter graph LR iterator --- reverse_iterator iterator --- Insert_iterator iterator --- io ...
- 【转】三十分钟掌握STL
转自http://net.pku.edu.cn/~yhf/UsingSTL.htm 三十分钟掌握STL 这是本小人书.原名是<using stl>,不知道是谁写的.不过我倒觉得很有趣,所以 ...
- STL学习之路
本文面向的读者:学习过C++程序设计语言(也就是说学习过Template),但是还没有接触过STL的STL的初学者.这实际上是我学习STL的一篇笔记,老鸟就不用看了. 什么是泛型程序设计 我们可以简单 ...
- STL学习小结
STL就是Standard Template Library,标准模板库.这可能是一个历史上最令人兴奋的工具的最无聊的术语.从根本上说,STL是一些"容器"的集合,这些" ...
随机推荐
- pjax
下载地址: https://github.com/defunkt/jquery-pjax/find/master 使用方法: 0.先引入jquery和jquery.pjax.js 1.父页面定义区域d ...
- (转) 淘淘商城系列——解决KindEditor上传图片浏览器兼容性问题
http://blog.csdn.net/yerenyuan_pku/article/details/72808229 上文我们已实现了图片上传功能,但是有个问题,那就是对浏览器兼容性不够,因为Map ...
- Servlet相关的几种中文乱码问题
Servlet相关的几种中文乱码问题浏览器调用jsp,html等页面中文显示乱码使得文件本身以utf-8字符集编辑保存 让浏览器浏览器以utf-8字符集解析 在浏览器中右键选择编码格式为utf-8: ...
- CAD参数绘制样条线(网页版)
在CAD设计时,需要绘制样条线,用户可以设置样条线线重及颜色等属性. 主要用到函数说明: _DMxDrawX::PathLineTo 把路径下一个点移到指定位置.详细说明如下: 参数 说明 DOUBL ...
- PHP-碎片知识 $_SERVER['argv']
1.cli模式(命令行)下,第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数 2.web网页模式下 在web页模式下必须在php.ini开启register_argc ...
- 为什么 [\000-\177]匹配任意7bit ascii码 ?
如题 41 print \000; 42 print "\n"; 43 print \177; 输出: SCALAR(0x3fce0)SCA ...
- oracle分析函数系列之sum(col1) over(partition by col2 order by col3):实现分组汇总或递增汇总
语法:sum(col1) over(partition by col2 order by col3 ) 准备数据: DEPT_ID ENAME SAL1 1000 ...
- Elasticsearch分布式机制和document分析
1. Elasticsearch对复杂分布式机制的透明隐藏特性 1.1)分片机制 1.2)集群发现机制 1.3)shard负载均衡 1.4)shard副本,请求路由,集群扩容,shard重分配 2. ...
- Nginx出现403 forbidden (13: Permission denied)报错的四种原因
一.由于php-fpm启动用户和nginx工作用户不一致所致 php-fpm启动用户配置位置 nginx工作用户配置位置 二.不存在在文件,可能是文件路径有误,可以查看nginx错误日志来判断 三.缺 ...
- 【02】HTML5与CSS3基础教程(第8版)(全)
[02]HTML5与CSS3基础教程(第8版)(全) 共392页. (魔芋:大体上扫了一遍.没有什么新东西,都是入门的一些基础知识.) 已看完. [美]elizabeth cast ...