【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是一些"容器"的集合,这些" ...
随机推荐
- ALTER TABLE - 修改表的定义
SYNOPSIS ALTER TABLE [ ONLY ] name [ * ] ADD [ COLUMN ] column type [ column_constraint [ ... ] ] AL ...
- JAVA编程不得不看的几本经典书籍
为了帮助对java编程感兴趣的同学更好.更快的提高编程技术,武汉北大青鸟光谷校区专业老师在此推荐几本学习编程非常有用的书籍,以供大家参考. 入门类 1.<java从入门到精通>(第3版) ...
- ArrayList集合的特点和几种遍历方法
public class temp { public static void main(String[] args)throws Exception { ArrayList 在定义时长度为空 ,在新增 ...
- CAD交互绘制圆弧(com接口)
在CAD设计时,需要绘制圆弧,用户可以在图面点圆弧起点,圆弧上的一点和圆弧的终点,这样就绘制出圆弧. 主要用到函数说明: _DMxDrawX::DrawArc2 由圆弧上的三点绘制一个圆弧.详细说明如 ...
- 使用layer时控制台出现: Failed to load resource: the server responded with a status of 404 (Not Found)
问题:layer文件路径放置出错 解决:layer文件如图:都放在创建的JS文件里,而不是单独的layer.js文件.
- 洛谷——P1602 Sramoc问题
P1602 Sramoc问题 $bfs$搜索 保证第一个搜到的符合条件的就是最小的 #include<bits/stdc++.h> #define N 110000 using names ...
- [SCOI2011]棘手的操作(可并堆/并查集/线段树)
我懒死了 过于棘手 但这题真的很水的说 毕竟写啥都能过 常见思路: ①:由于不强制在线,所以重新编号之后线段树维护 ②:用各种可以高速合并的数据结构,比如可并堆,可并平衡树啥的 讲一种无脑算法: 对于 ...
- win7 x64安装glpk
下载glpk,下载地址:http://ftp.gnu.org/gnu/glpk/
- Django之Ajax提交
Ajax 提交数据,页面不刷新 Ajax要引入jQuery Django之Ajax提交 Js实现页面的跳转: location.href = "/url/" $ajax({ url ...
- 商业研究(21):活力蛙,足疗O2O,曾经的“中国上门足疗领先品牌”
友情提示:商业研究系列文章,只探讨项目和相关项目的商业本身,不针对任何人和任何组织!!! 2015年,在京东-东家 股权众筹平台,参与投资了足疗O2O项目,活力蛙. 后来由于,股市大跌和资本寒 ...