Loading

C++ STL 系列 ——迭代器适配器

一、什么是迭代器适配器

C++ STL 标准库种的迭代器大致分为 5 种类型,输入迭代器、输出迭代器、前向迭代器、双向迭代器以及随机访问迭代器,它们是最基础的迭代器,对于很多场合,它们并不适合。

迭代器适配器是对 5 种基础迭代器进行封装,对基础迭代器的成员方法进行整合、修改,甚至添加一些新的成员方法。

迭代器适配器种类

名称 功能
反向迭代器(reverse_iterator) 又称“逆向迭代器”,其内部重新定义了递增运算符(++)和递减运算符(--),专门用来实现对容器的逆序遍历。
安插型迭代器(inserter 或者 insert_iterator) 通常用于在容器的任何位置添加新的元素,需要注意的是,此类迭代器不能被运用到元素个数固定的容器(比如 array)上。
流迭代器(istream_iterator/ostream_iterator) 输入流迭代器用于从文件或者键盘读取数据;相反,输出流迭代器用于将数据输出到文件或者屏幕上。
流缓冲区迭代器(istreambuf_iterator/ostreambuf_iterator) 输入流缓冲区迭代器用于从输入缓冲区中逐个读取数据;输出流缓冲区迭代器用于将数据逐个写入输出流缓冲区。
移动迭代器(move_iterator) 此类型迭代器是 C++ 11 标准中新添加的,可以将某个范围的类对象移动到目标范围,而不需要通过拷贝去移动。

二、反向迭代器适配器(reverse_iterator)

反向迭代器底层可以选用双向迭代器或者随机访问迭代器作为其基础迭代器。不仅如此,通过对 ++(递增)和 --(递
减)运算符进行重载,使得:

  • 当反向迭代器执行 ++ 运算时,底层的基础迭代器实则在执行 -- 操作,意味着反向迭代器在反向遍历容器;
  • 当反向迭代器执行 -- 运算时,底层的基础迭代器实则在执行 ++ 操作,意味着反向迭代器在正向遍历容器。

实现反向迭代器的模板类定义在 头文件,并位于 std 命名空间中。

#include <iterator>
using namespace std;

类模板定义:

template <class Iterator>
    class reverse_iterator;

Iterator 模板参数指的是模板类中所用的基础迭代器的类型,只能选择双向迭代器或者随机访问迭代器。

2.1 创建反向迭代器

  1. 默认构造函数
std::reverse_iterator<std::vector<int>::iterator> my_reiter;
  1. 将基础迭代器作为参数传给新建的反向迭代器
// 创建 myvector 容器
std::vector<int> myvector{1,2,3,4,5};
// 创建并初始化 my_reiter 迭代器
std::reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.end());
  1. 拷贝构造函数
// 创建并初始化一个 vector 容器
std::vector<int> myvector{1,2,3,4,5};
// 调用复制构造函数初始化反向迭代器的 2 种方式
std::reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.rbegin());
//std::reverse_iterator<std::vector<int>::iterator> my_reiter = myvector.rbegin();

2.2 类中的成员

重载运算符 功能
operator* 以引用的形式返回当前迭代器指向的元素。
operator+ 返回一个反向迭代器,其指向距离当前指向的元素之后 n 个位置的元素。此操作要求基础迭代器为随机访问迭代器。
operator++ 重载前置 ++ 和后置 ++ 运算符。
operator+= 当前反向迭代器前进 n 个位置,此操作要求基础迭代器为随机访问迭代器。
operator- 返回一个反向迭代器,其指向距离当前指向的元素之前 n 个位置的元素。此操作要求基础迭代器为随机访问迭代器。
operator-- 重载前置 -- 和后置 -- 运算符。
operator-= 当前反向迭代器后退 n 个位置,此操作要求基础迭代器为随机访问迭代器。
operator-> 返回一个指针,其指向当前迭代器指向的元素。
operator[n] 访问和当前反向迭代器相距 n 个位置处的元素。

三、插入迭代器适配器(insert_iterator)

根据插入位置的不同,C++ STL 标准库提供了 3 种插入迭代器

迭代器适配器 功能
back_insert_iterator 在指定容器的尾部插入新元素,但前提必须是提供有 push_back() 成员方法的容器(包括 vector、deque 和 list)。
front_insert_iterator 在指定容器的头部插入新元素,但前提必须是提供有 push_front() 成员方法的容器(包括 list、deque 和 forward_list)。
insert_iterator 在容器的指定位置之前插入新元素,前提是该容器必须提供有 insert() 成员方法。

3.1 back_insert_iterator 迭代器

back_insert_iterator 迭代器可用于在指定容器的末尾处添加新元素。其底层使用的是 push_back() 成员方法。

C++ STL 标准库中,提供有 push_back() 成员方法的容器包括 vector、deque 和 list。

#include <iterator>
using namespace std;

定义 back_insert_iterator 插入迭代器的方式仅有一种:

std::back_insert_iterator back_it(container);

  • Container : 指定插入的目标容器的类型
  • container : 指定目标容器
std::vector<int> foo;
// 创建一个可向 foo 容器尾部添加新元素的迭代器
std::back_insert_itertor<std::vector<int>> back_it(foo);

// back_insert_iterator 迭代器模板类对赋值运算符进行了重载
back_it = 5;           // 将 5 插入到 foo 的末尾
// 相当于
pos = foo.insert(pos,value);
++pos;

C++ STL 标准库为了方便用户创建 back_insert_iterator 类型的插入迭代器,提供了 back_inserter() 函数

template
back_insert_iterator back_inserter(Container& x);

std::back_insert_iterator<std::vector<int>> back_it = back_inserter(foo);

3.2 front_insert_iterator 迭代器

功能:向目标容器的头部插入新元素。底层借助目标容器的 push_front() 方法,C++ STL 标准库中提供 push_front() 方法的容器仅有 deque、list 和 forward_list。

#include <iterator>
using namespace std;
//创建一个 forward_list 容器
std::forward_list<int> foo;

//创建一个前插入迭代器
//std::front_insert_iterator< std::forward_list<int> > front_it(foo);
std::front_insert_iterator< std::forward_list<int> > front_it = front_inserter(foo);

//向 foo 容器的头部插入元素
front_it = 5;

3.3 insert_iterator 迭代器

功能:向容器任意位置插入元素。底层借助 insert() 方法实现,STL 标准库中所有容器都提供 insert() 成员方法。insert_iterator 是唯一可用于关联式容器的插入迭代器。

#include <iterator>
using namespace std;

语法格式:

std::insert_iterator insert_it(container,it);

  • Container : 目标迭代器的类型
  • container : 目标迭代器
  • it : 基础迭代器

C++ STL 标准库中还提供有 inserter() 函数,可以快速创建 insert_iterator 类型迭代器。

std::list<int> foo(2,5);

// 定义一个基础迭代器,用于指定要插入新元素的位置
std::list<int>::iterator it = ++foo.begin();

// 创建一个 insert_iterator 迭代器
// std::insert_iterator<std::list<int>> insert_it = inserter(foo, it);

// 向 foo 容器中插入元素
insert_it = 1;

四、流迭代器

  • 将绑定到输入流对象的迭代器称为输入流迭代器(istream_iterator),其可以用来读取输入流中的数据;
  • 将绑定到输出流对象的迭代器称为输出流迭代器(ostream_iterator),其用来将数据写入到输出流中。

4.1 输入流迭代器(istream_iterator)

输入流迭代器用于直接从指定的输入流中读取元素,其本质就是一个输入迭代器,其只能进行 ++p、p++、*p 操作,迭代器之间只能使用 == 和 != 运算符。

输入迭代器底层是通过重载 ++ 运算符实现的,该运算符内部会调用 operator >> 读取数据。

#include <iterator>
using namespace std;

创建输入流迭代器的方式:

  1. 默认构造函数,创建一个具有结束标志的输入流迭代器
std::istream_iterator<double> eos;
  1. 创建一个可用来读取数据的输入流迭代器
std::istream_iterator<double> iit(std::cin);  // 此方式调用的构造函数会自行尝试去指定流种读取一个指定类型的元素
  1. 通过已创建好的 istream_iterator 迭代器为新建 istream_iterator 迭代器初始化
std::istream_iterator<double> iit2(iit1);
// 用于接受输入流中的数据
double value1, value2;
cout << "请输入 2 个小数";
// 创建表示结束的输入流迭代器
istream_iterator<double> eos;
// 创建一个可逐个读取输入流种数据的迭代器,同时会让用户输入数据
istream_iterator<double> iit(cin);

// 判断输入流中是否有数据 
if(iit != eos)
{
    // 读取一个数据,并赋值给 value1
    value1 = *iit;
}

// 如果输入流中此时没有数据,则用户要输入一个;反之,如果流中有数据,iit 迭代器后移一位,做读取下一个元素做准备
iit++;
if (iit != eos) {
    //读取第二个元素,赋值给 value2
    value2 = *iit;
}

只有读取到 EOF 流结束符时,程序中的 iit 才会和 eos 相等。

4.2 输出流迭代器(ostream_iterator)

输出流迭代器用于将数据写到指定的输出流(如 cout)中,本质上属于输出迭代器,能执行 ++p、p++、*p=t 以及 *p++ = t 等类似操作。

输出迭代器底层是通过重载赋值运算符实现,即每个赋值给输出流迭代器的元素都会被写入到指定的输出流中。

#include <iterator>
using namespace std;

创建 ostream_iterator 迭代器的方法

  1. 默认构造函数,创建一个指定输出流的迭代器
std::ostream_iterator<int> out_it(std::cout);
  1. 创建为写入的元素之间指定分隔符
std::ostream_iterator<int> out_it(std::cout, ",");  // 在向输出流写入int元素时,还会附带逗号,
  1. 用已有同类型的迭代器初始化新建迭代器
std::ostream_iterator<int> out_it1(out_it);
// 创建一个输出流迭代器
ostream_iterator<string> out_it<cout>;
// 向 cout 输出流写入 string 字符串
*out_it = "12341234";

// 创建一个输出流迭代器,设置分隔符
ostream_iterator<int> out_it1(cout, ",");
// 向 cout 输出流依次写入 1、2、3
*out_it1 = 1;
*out_it2 = 2;
*out_it3 = 3;

在实际场景中,输出流迭代器常和 copy() 函数连用,即作为该函数第 3 个参数。

#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>    // std::copy

using namespace std;

int main()
{
    // 创建一个 vector 容器
    vector<int> myvector;
    // 初始化 myvector 容器
    for(int i=0; i<10; i++)
        myvector.push_back(i);
    
    // 创建输出流迭代器
    std::ostream_iterator<int> out_it(std::cout, ",");
    // 将 myvector 容器中存储的元素写入到 cout 输出流中
    std::copy(myvector.begin(), myvector.end(), out_it);
    return 0;
}

五、流缓冲区迭代器(streambuf_iterator)

流缓冲区迭代器分为输入流缓冲区迭代器和输出流缓冲区迭代器

  • 输入流缓冲区迭代器(istreambuf_iterator):从输入流缓冲区中读取字符元素;
  • 输出流缓冲区迭代器(ostreambuf_iterator):将连续的字符元素写入到输出缓冲区中。

流缓冲区迭代器和流迭代器最大的区别:

  • 流缓冲区迭代器会将元素以字符的形式(包括 char、wchar_t、char16_t 及 char32_t 等)读或者写到流缓冲区中,由于不会涉及数据类型的转换,读写数据的速度比流迭代器要快。

5.1 输入流缓冲区迭代器(istreambuf_iterator)

istreambuf_iterator 输入流缓冲区迭代器的功能是从指定的流缓冲区中读取字符元素。

该类型迭代器本质是一个输入迭代器,只能进行 ++p、p++、*p 操作,同时迭代器之间也只能使用 == 和 != 运算符。

#include <iterator>
using namespace std;

创建输入流缓冲区迭代器的常用方式:

  1. 默认构造函数,创建一个表示结尾的输入流缓冲区迭代器,即从流缓冲区读完数据。
std::istreambuf_iterator<char> end_in;
  1. 指定要读取的流缓冲区
std::istreambuf_iterator<char> in{std::cin};
// 传入流缓冲区的地址,rdbuf 函数是获取指定缓冲区的地址
std::istreambuf_iterator<char> in{std::cin.rdbuf()};
#include <iostream>             // std::cin, std::cout
#include <iterator>             // std::istreambuf_iterator
#include <string>              // std::string

using namespace std;

int main() {
    // 创建结束流缓冲区迭代器
    istreambuf_iterator<char> eos;
    
    // 创建一个从输入缓冲区读取字符元素的迭代器
    istreambuf_iterator<char> iit(cin);
    
    string mystring;
    cout << "向缓冲区输入元素:\n";
    
    //不断从缓冲区读取数据,直到读取到 EOF 流结束符
    while (iit != eos) {
        mystring += *iit++;
    }
    
    cout << "string:" << mystring;

    return 0;
}

5.2 输出流缓冲区迭代器(ostreambuf_iterator)

ostreambuf_iterator 输出流缓冲区迭代器用于将字符元素写入到指定的流缓冲区中。

该类型迭代器本质上是一个输出迭代器,它仅能执行 ++p、p++、*p=t 以及 *p++=t 等操作。

和 ostream_iterator 输出流迭代器一样,istreambuf_iterator 迭代器底层也是通过重载赋值(=)运算符实现的。

#include <iterator>
using namespace std;

常见创建输出流缓冲区迭代器的方式:

  1. 通过传递流缓冲区对象,创建输出流缓冲区迭代器
std::ostreambuf_iterator<char> out_it(std::cout);
> 尖括号 <> 中用于指定要写入字符的类型,可以是 char、wchar_t、char16_t 以及 char32_t 等。
  1. 借助 rdbug() 传递流缓冲区的地址,创建输出流缓冲区迭代器
std::ostreambuf_iterator<char> out_it(std::cout.rdbuf());
#include <iostream>             // std::cin, std::cout
#include <iterator>             // std::ostreambuf_iterator
#include <string>               // std::string
#include <algorithm>            // std::copy

int main() {
    // 创建一个和输出流缓冲区相关联的迭代器
    std::ostreambuf_iterator<char> out_it(std::cout); // stdout iterator
    // 向输出流缓冲区中写入字符元素
    *out_it = 'S';
    *out_it = 'T';
    *out_it = 'L';

    // 和 copy() 函数连用
    std::string mystring("\nhttp://c.biancheng.net/stl/");
    // 将 mystring 中的字符串全部写入到输出流缓冲区中
    std::copy(mystring.begin(), mystring.end(), out_it);
    
    return 0;
}

六、move_iterator 移动迭代器用法详解

C++11 新增。move_iterator 迭代器适配器,又可简称为移动迭代器,其可以实现以移动而非复制的方式,将某个区域空间中的元素移动至另一个指定的空间。

先看一段代码

#include <iostream>
#include <vector>
#include <list>
#include <string>

using namespace std;

int main()
{
  // 创建并初始化一个 vector 容器
  vector<string> myvec{ "STL","Python","Java" };
  // 再次创建一个 vector 容器,利用 myvec 为其初始化
  vector<string>othvec(myvec.begin(), myvec.end()); // 复制

  cout << "myvec:" << endl;
  // 输出 myvec 容器中的元素
  for (auto ch : myvec)
      cout << ch << " ";

  cout << endl << "othvec:" << endl;
  // 输出 othvec 容器中的元素
  for (auto ch : othvec)
      cout << ch << " ";

  return 0;
}

上述代码在初始化 othvec 容器时是通过复制 myvec 容器中的元素实现的,如果想把 myvec 容器中的元素全部移动到 othvec 容器中,可以采用移动迭代器。

move_iterator 虽然没有明确要求基础迭代器,但该模板类中某些成员方法的底层实现,需要此基础迭代器为双向迭代器或者随机访问迭代器。

常见 move_iterator 移动迭代器:

  • 默认构造函数,创建一个不指向任何对象的移动迭代器
    // 将 vector 容器的随机访问迭代器作为新建移动迭代器底层使用的基础迭代器
    typedef std::vector<std::string>::iterator Iter;
    // 调用默认构造函数,创建移动迭代器
    std::move_iterator<Iter>mIter;
    
  • 创建 move_iterator 迭代器的同时初始化
    // 创建一个 vector 容器
    std::vector<std::string> myvec{ "one","two","three" };
    // 将 vector 容器的随机访问迭代器作为新建移动迭代器底层使用的基础迭代器
    typedef std::vector<std::string>::iterator Iter;
    // 创建并初始化移动迭代器
    std::move_iterator<Iter>mIter(myvec.begin());
    
  • 通过已有移动迭代器初始化新建同类型迭代器
    std::move_iterator<Iter> mIter2(mIter);
    // 还可以使用 = 运算符,它们等价
    std::move_iterator<Iter> mIter2 = mIter;
    
  • make_move_iterator() 函数
    typedef std::vector<std::string>::iterator Iter;
    std::vector<std::string> myvec{ "one","two","three" };
    // 将 make_move_iterator() 的返回值赋值给同类型的 mIter 迭代器
    std::move_iterator<Iter>mIter = make_move_iterator(myvec.begin());
    

对一开始的程序进行修改,使用移动迭代器

#include <iostream>
#include <vector>
#include <list>
#include <string>

using namespace std;

int main()
{
  // 创建并初始化一个 vector 容器
  vector<string> myvec{ "STL","Python","Java" };
  // 再次创建一个 vector 容器,利用 myvec 为其初始化
  vector<string>othvec(make_move_iterator(myvec.begin()), make_move_iterator(myvec.end()));
  cout << "myvec:" << endl;
  
  // 输出 myvec 容器中的元素

  for (auto ch : myvec)
    cout << ch << " ";        // 没有元素输出

  cout << endl << "othvec:" << endl;
  
  // 输出 othvec 容器中的元素
  for (auto ch : othvec)
    cout << ch << " ";

  return 0;
}

七、辅助函数

为了方便用户操作这些迭代器,C++ STL 标准库中还提供有一些辅助函数

迭代器辅助函数 功能
advance(it, n) it 表示某个迭代器,n 为整数。该函数的功能是将 it 迭代器前进或后退 n 个位置。
distance(first, last) first 和 last 都是迭代器,该函数的功能是计算 first 和 last 之间的距离。
begin(cont) cont 表示某个容器,该函数可以返回一个指向 cont 容器中第一个元素的迭代器。
end(cont) cont 表示某个容器,该函数可以返回一个指向 cont 容器中最后一个元素之后位置的迭代器。
prev(it) it 为指定的迭代器,该函数默认可以返回一个指向上一个位置处的迭代器。注意,it 至少为双向迭代器。
next(it) it 为指定的迭代器,该函数默认可以返回一个指向下一个位置处的迭代器。注意,it 最少为前向迭代器。
posted @ 2021-12-17 11:01  锦瑟,无端  阅读(896)  评论(0编辑  收藏  举报