11.2 泛型算法中的迭代器

除了常规的迭代器,C++还提供额外3种迭代器,这3种迭代器都定义在<iterator>头文件中。

  1. 插入迭代器(insert iterator): 用于和容器绑定,实现向容器写入数据
  2. iostream迭代器(iostream iterator): 和IO流绑定
  3. 反向迭代器(reverse iterator): 实现反序遍历
1.插入迭代器

插入迭代器采用的适配器模式,接收一个容器作为构造函数参数,接口定义又符合迭代器的标准,从而实现利用迭代器动态插入数据。根据插入的位置不同,插入迭代器分为3种

  1. back_inserter,使用push_back写入容器
  2. front_inserter,使用push_front写入容器
  3. inserter,使用insert插入指定位置,构造器需要一个额外的入参用于指定位置
1.back_inserter

back_inserter接收一个容器对象,实现用容器的push_back向容器里写入数据。

void test_back_inserter() {
    vector<string> v1;
    v1.push_back("1");
    v1.push_back("2");
    v1.push_back("3");

    vector<string> v2;
    std::copy(v1.begin(), v1.end(), std::back_inserter(v2));
    cout << "v2: " << to_str(v2,",") << std::endl;    // print: v2: 1,2,3

    std::copy(v1.rbegin(), v1.rend(), std::back_inserter(v2));
    cout << "v2: " << to_str(v2, ",") << std::endl;  // print: v2: 1,2,3,3,2,1
}
2.front_inserter

和back_inserter不同,front_inserter通过使用push_front实现插入。使用前面示例的代码,vector<string>不支持push_front,我们将vector<string>改为list<string>。具体代码如下

void test_front_inserter() {
    vector<string> v1;
    v1.push_back("1");
    v1.push_back("2");
    v1.push_back("3");

    list<string> v2;
    std::copy(v1.begin(), v1.end(), std::front_inserter(v2));
    cout << "v2: " << to_str(v2, ",") << std::endl;  // print v2: 3,2,1,

    std::copy(v1.rbegin(), v1.rend(), std::front_inserter(v2));
    cout << "v2: " << to_str(v2, ",") << std::endl; // v2: 1,2,3,3,2,1,
}
3.inserter

接收一个容器,以及一个位置参数用于指定位置插入。比较特别的是,插入一个元素之后,下一次插入的位置是上一次插入位置的下一个。

void test_inserter() {
    vector<string> v1;
    v1.push_back("a");
    v1.push_back("b");
    v1.push_back("c");
    v1.push_back("d");

    vector<string>::iterator it = std::find(v1.begin(), v1.end(), string("c"));  // 获取指向"c"的迭代器

    vector<string> v2;
    v2.push_back("1");
    v2.push_back("2");
    v2.push_back("3");

    std::copy(v2.begin(), v2.end(), inserter(v1, it)); // 在"c"的位置插入v2的内容,"c"后移,v2的内容按原先的迭代器输出
    cout << "v2: " << to_str(v2,",") << std::endl;  // print  v2: 1,2,3
    cout << "v1: " << to_str(v1,",") << std::endl;  // print  v1: a,b,1,2,3,c,d
}

front_inserter会导致要写入的内容倒序插入到容器前面,使用inserter + 迭代器的起始位置,我们可以做到将元素保留原有顺序写入到容器其实位置。如果要用inserter模拟front_inserter的效果,需要将插入位置选为begin,并倒序迭代要插入的元素。

void test_inserter_as_front_inserter() {
    list<string> v1;
    v1.push_back("a");
    v1.push_back("b");
    v1.push_back("c");
    v1.push_back("d");

    vector<string> v2;
    v2.push_back("1");
    v2.push_back("2");
    v2.push_back("3");

    std::copy(v2.rbegin(), v2.rend(), inserter(v1, v1.begin())); // print: v1: 3,2,1,a,b,c,d,
    //std::copy(v2.begin(), v2.end(), std::front_inserter(v1));  // print: v1: 3,2,1,a,b,c,d,
    cout << "v2: " << to_str(v2, ",") << std::endl;
    cout << "v1: " << to_str(v1, ",") << std::endl;
}
2. iostream迭代器

标准库提供了istream_iterator用于读取输入流,ostream_iterator用于写输出流。适配iostream成迭代器,从而支持泛型算法的一些功能。

构造函数

说明

istream_iterator<T> in(stream)

从输入流stream创建一个读取T类型对象的istream_iterator

istream_iterator<T> in

指向超出末端的istream_iterator

ostream_iterator<T> in(stream)

将T对象写入到stream的ostream_iterator

ostream_iterator<T> in(stream, delim)

将T对象写入到stream的ostream_iterator,多次输出用delim作为分隔符

流迭代只提供了最基本的功能,赋值、自增、解引用,对于istream_iterator额外提供了一个相等(==)比较的能力。

1.istream_iterator

istream_iterator通过默认构造函数的返回值提示流已经迭代完成。我们来可以一个简单的例子,用istream_iterator从istringstream读取字符串保存到vector中

#include <iostream>
#include <iterator>
#include <sstream>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;

string to_str(const vector<string>& ivec, const char* deli) {
    std::ostringstream os;
    std::copy(ivec.begin(), ivec.end() - 1, std::ostream_iterator<string>(os, deli));
    os << ivec.back();
    return os.str();
}

void test_istream_iterator() {
    std::istringstream iss("the quick red fox jumps over the slow red turtle");

    std::istream_iterator<string> it(iss);
    std::istream_iterator<string> eof;

    vector<string> v1;
    std::copy(it, eof, std::back_inserter(v1));  // 通过eof限定迭代器结束位置
    cout << "v1: " << to_str(v1, ",") << endl;
}

这个例子中用it、eof指定的范围插入到vector中,其实更高效的写法是直接传递给vector的构造函数

void test_vector() {
    std::istringstream iss("the quick red fox jumps over the slow red turtle");
    string s1;
    std::istream_iterator<string> it(iss);
    std::istream_iterator<string> eof;

    vector<string> v1(it, eof);
    cout << "v1: " << to_str(v1, ",") << endl;
}
2. ostream_iterator

ostream_iterator将赋值给迭代器的内容,输出到绑定的输出流中。我们用cout初始化ostream_iterator,每次输出后接一个分隔符"\n"。

void test_ostream_iterator() {
    std::istringstream iss("the quick red fox jumps over the slow red turtle");
    std::istream_iterator<string> it(iss);
    std::istream_iterator<string> eof;    
    std::ostream_iterator<string> it_out(cout, "\n");
    while (it != eof) {
        *it_out++ = *it++;
    }  
}
3.反向迭代器

类似于容器提供的begin、end函数,很多容器还提供rbegin、rend函数,rbegin之前容器的最后一个元素,rend指向容器第一个元素的前一个位置。

我们来看一个简单的示例,通过反向迭代器倒序输出vector中的数据

void test_reverse_iterator() {
    std::vector<int> v1;
    for (int i = 0; i < 10; i++) {
        v1.push_back(i);
    }
    cout << "v1: " << to_str(v1, ",") << endl;  // print  v1: 0,1,2,3,4,5,6,7,8,9
    vector<int>::reverse_iterator rit;
    for (rit = v1.rbegin(); rit != v1.rend(); rit++) {
        cout << *rit << endl;  // print 9 8 7 ...
    }
}

之前我们学过sort函数,用于按字典顺序排列元素,如果我们想倒序排列,一种办法是提供自定义的谓词函数,另外一种就是传入反向迭代器。

bool compare(string i1, string i2) {
    return i2 < i1;
}

void test_reverse_sort() {
    vector<string> v1;
    v1.push_back("1");
    v1.push_back("3");
    v1.push_back("2");

    //std::sort(v1.begin(), v1.end()); // print:  v1: 1,2,3
    std::sort(v1.rbegin(), v1.rend()); // print:  v1: 3,2,1
    //std::sort(v1.begin(), v1.end(), compare); // print: v1: 3,2,1
    cout << "v1: " << to_str(v1,",") << endl;
}

标准库提供的容器都支持正序、反序迭代,iostream迭代器不支持倒序迭代。

1. 正反序迭代器转换

当我们使用反向迭代器的时候,迭代器将元素逆向输出。我们举个例子来说明这个问题

void test_reverse() {
   string s1("123,456,789");
   string::iterator it = find(s1.begin(), s1.end(), ',');
   cout << "正序,第1个,之前的内容: " << string(s1.begin(), it) << endl; // print 123

   string::reverse_iterator rit = find(s1.rbegin(), s1.rend(), ',');
   cout << "倒序,第1个,之后的内容: " << string(s1.rbegin(), rit) << endl; // print 987
   cout << "倒序,第1个,之后的内容正序输出: " << string(rit.base(), s1.end()) << endl; // print 789
}

第一个输出,用于显示正序第一个","之前的内容,输出123并没有什么问题。第二个输出,显示最后一个","之后的内容,并没有按s1里原先的顺序显示789,而是显示为987。反序迭代器提供了一个base函数,用于将反序迭代器转换为正序迭代器。第3个输出,我们将rit通过base函数转换,输出到end位置的内容,显示内容是我们期望的789。

4.迭代器分类

不同的迭代器支持的操作不同,比如ostream_iterator只支持赋值、自增、解引用,而vector的迭代器还支持自减、关系和算术运算。根据泛型算法对迭代器的要求,可以将迭代器分为5类

迭代器类型

支持的操作

输入迭代器

只读;只支持自增运算

输出迭代器

只写;只支持自增运算

前向迭代器

读写;只支持自增运算

双向迭代器

读写;支持自增和自减运算

随机访问迭代器

读写;支持完整的算术运算

1.输入迭代器

输入迭代器可以用于读取容器元素,至少需要支持以下操作

  1. 相等和不相等(==、!=),比较两个迭代器
  2. 前置、后置自增运算(++),将迭代器移到下一个元素
  3. 读取元素的解引用操作
  4. 箭头操作(->),调用元素的成员函数、数据

支持这个层次的泛型算法有find、accumulate等,标准库的istream_iterator就是输入迭代器的一种。

2.输出迭代器

用于向容器写入元素,至少需要支持以下操作

  1. 前置、后置自增运算(++),将迭代指向下一个元素
  2. 解引用操作,做为左操作数

输出迭代器一般作为泛型算法的第3个参数,作为输出的目标位置。标准库ostream_iterator就是输出迭代器的一种。

3.前向迭代器

前向迭代器只能向一个方向遍历,支持输入迭代器和输出迭代器的所有操作。同时支持对一个元素的多次读写。标准库replace函数就至少需要这个层次的迭代器。

4.双向迭代器

支持前向迭代器的所有操作,额外支持自减(--)的能力。标准库reverse函数至少需要这个层次的迭代器。标准库容器提供的迭代器至少都达到双向迭代器的要求。

5.随机访问迭代器

提供常量时间内访问任意位置的功能,支持双向迭代器的功能为,额外提供以下操作

  1. 关系操作符(<、<=、>、>=),比较两个迭代器的相对位置
  2. 支持算术运算,支持迭代器和整型数值n的加减法操作
  3. 支持两个迭代器的减法操作,得到之间的距离
  4. 下标操作符iter[n],是*(iter+n)的同义词

泛型算法里的sort就需要这个层次的迭代器。vector、deque、string、数组的指针的迭代器都是随机访问迭代器。map、set、list都提供双向迭代器。istream_iterator是输入迭代器,而ostream_iterator是输出迭代器。5种迭代器,出来输出迭代器,剩下的4种形成一组继承结构,在要求较低层级的迭代器的算法中,可以传入更高层级的迭代器,比如find要求输入迭代器,我们可以传入一个输入迭代器,也可以传入一个前向迭代器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值