【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_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博客,与本站同步更新

原文链接: https://www.cnblogs.com/clnchanpin/p/7138174.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    【C++ STL应用与实现】18: 怎样使用迭代器适配器

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/256507

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月14日 上午10:08
下一篇 2023年2月14日 上午10:10

相关推荐