本系列文章的文件夹在这里:文件夹. 通过文件夹里能够对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_iteratoristream_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: 怎样使用迭代器适配器的更多相关文章

  1. .NET设计模式(18):迭代器模式(Iterator Pattern)(转)

    概述 在面向对象的软件设计中,我们经常会遇到一类集合对象,这类集合对象的内部结构可能有着各种各样的实现,但是归结起来,无非有两点是需要我们去关心的:一是集合内部的数据存储结构,二是遍历集合内部的数据. ...

  2. vector源码(参考STL源码--侯捷):空间分配导致迭代器失效

    vector源码1(参考STL源码--侯捷) vector源码2(参考STL源码--侯捷) vector源码(参考STL源码--侯捷)-----空间分配导致迭代器失效 vector源码3(参考STL源 ...

  3. STL标准库-迭代器适配器

    技术在于交流.沟通,本文为博主原创文章转载请注明出处并保持作品的完整性 这次主要介绍一下迭代器适配器.以reverse_iterator(反向迭代器),insert_iterator(插入迭代器),o ...

  4. Effective STL 学习笔记 Item 18: 慎用 vector<bool>

    vector<bool> 看起来像是一个存放布尔变量的容器,但是其实本身其实并不是一个容器,它里面存放的对象也不是布尔变量,这一点在 GCC 源码中 vector<bool> ...

  5. STL——容器(Set & multiset)的迭代器

    1.set.insert(elem);     //在容器中插入元素. 2.set.begin();         //返回容器中第一个数据的迭代器. 3.set.end();          / ...

  6. STL 迭代器适配器(iterator adapter)

    iterator adapter graph LR iterator --- reverse_iterator iterator --- Insert_iterator iterator --- io ...

  7. 【转】三十分钟掌握STL

    转自http://net.pku.edu.cn/~yhf/UsingSTL.htm 三十分钟掌握STL 这是本小人书.原名是<using stl>,不知道是谁写的.不过我倒觉得很有趣,所以 ...

  8. STL学习之路

    本文面向的读者:学习过C++程序设计语言(也就是说学习过Template),但是还没有接触过STL的STL的初学者.这实际上是我学习STL的一篇笔记,老鸟就不用看了. 什么是泛型程序设计 我们可以简单 ...

  9. STL学习小结

    STL就是Standard Template Library,标准模板库.这可能是一个历史上最令人兴奋的工具的最无聊的术语.从根本上说,STL是一些"容器"的集合,这些" ...

随机推荐

  1. TensorFlow低阶API(一)—— 简介

    简介 本文旨在知道您使用低级别TensorFlow API(TensorFlow Core)开始编程.您可以学习执行以下操作: 管理自己的TensorFlow程序(tf.Graph)和TensorFl ...

  2. python常见问题一(安装报错)

    常见问题一:我在安装python2.7时,提示错误:'An error occurred during the installation of assembly 'Microsoft.VC90.CRT ...

  3. XML和JSON

    XML XML(EXtensible Markup Language),可扩展标记语言 特点 XML与操作系统.编程语言的开发平台无关 实现不同系统之间的数据交换 作用: 数据交互 配置应用程序和网站 ...

  4. 对比props

    1.在组件中data返回数组对象 2.在父级作用域中写入 (1)prop传值 <btn-grp :buttons="buttons"></btn-grp> ...

  5. JS_类数组

    [目录] 什么是数组 非类数组 类数组对象转化为数组 [类数组] 什么是类数组? 定义: 不具有数组的所具有的方法 拥有length属性,其属性(索引)为非负整数 类数组 var obj = { 0 ...

  6. iOS sandbox

    iOS的沙盒机制,应用只能访问自己应用目录下的文件.iOS不像android,没有SD卡概念,不能直接访问图像.视频等内容.iOS应用产生的内容,如图像.文件.缓存内容等都必须存储在自己的沙盒内.默认 ...

  7. vue hash模式和404页面的配置

    1.设置我们的路由配置文件(/src/router/index.js): { path:'*', component:Error } 这里的path:’*’就是找不到页面时的配置,component是 ...

  8. 笔试算法题(47):简介 - B树 & B+树 & B*树

    B树(B-Tree) 1970年由R. Bayer和E. Mccreight提出的一种适用于外查找的树,一种由BST推广到多叉查找的平衡查找树,由于磁盘的操作速度远小于存储器的读写速度,所以要求在尽量 ...

  9. ELK6.3.2+filebeat部署过程

    ELK安装部署 elk作为公司的日志收集检索的方案的首选,是必要的工具,下面介绍一下elk的安装部署方法,以及一些报错的解决方法:(使用的是ubuntu16.04,jdk使用1.8,ELK的版本为6. ...

  10. select into outfile 与 load data infile

    select into outfile用法 MySQL中,可以使用SELECT...INTO OUTFILE语句将表的内容导出为一个文本文件. SELECT [列名] FROM table [WHER ...