C++ STL复习(14)迭代器适配器

C++ 11 标准中,迭代器适配器供有 4 类,它们各自的名称和功能如下表所示。
在这里插入图片描述

实际上,前面在学习各种容器的迭代器时,我们经常会使用到反向迭代器。下面样例,演示了用反向迭代器适配器遍历 list 容器的实现过程:

#include <iostream>
#include <list>

using namespace std;

int main()
{
    
    
    std::list<int> values{
    
     1,2,3,4,5 };
    //找到遍历的起点和终点,这里无需纠结定义反向迭代器的语法,后续会详细讲解
    std::reverse_iterator<std::list<int>::iterator> begin = values.rbegin();
    std::reverse_iterator<std::list<int>::iterator> end = values.rend();
    while (begin != end) 
    {
    
    
        cout << *begin << " ";
        //注意,这里是 ++,因为反向迭代器内部互换了 ++ 和 -- 的含义
        ++begin;
    }
    return 0;
}

输出结果:

5 4 3 2 1 % 

反向迭代器适配器(reverse_iterator),可简称为反向迭代器或逆向迭代器,常用来对容器进行反向遍历,即从容器中存储的最后一个元素开始,一直遍历到第一个元素。

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

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

另外,实现反向迭代器的模板类定义在 头文件,并位于 std 命名空间中。因此,在使用反向迭代器时,需包含如下语句:

#include <iterator>
using namespace std;

反向迭代器的模板类定义如下:

template <class Iterator>
    class reverse_iterator;

1 C++ STL反向迭代器的创建

reverse_iterator 模板类中共提供了 3 种创建反向迭代器的方法,这里以 vector 容器的随机访问迭代器作为基础迭代器为例。

1

调用该类的默认构造方法,即可创建了一个不指向任何对象的反向迭代器,例如:

std::reverse_iterator<std::vector<int>::iterator> my_reiter;

由此,我们就创建好了一个没有指向任何对象的 my_reiter 反向迭代器。

2

当然,在创建反向迭代器的时候,我们可以直接将一个基础迭代器作为参数传递给新建的反向迭代器。例如:

//创建并初始化一个 myvector 容器
std::vector<int> myvector{
    
    1,2,3,4,5};
//创建并初始化 my_reiter 迭代器
std::reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.end());

我们知道,反向迭代器是通过操纵内部的基础迭代器实现逆向遍历的,但是反向迭代器的指向和底层基础迭代器的指向并不相同。以上面创建的 my_reiter 为例,其内部的基础迭代器指向的是 myvector 容器中元素 5 之后的位置,但是 my_reiter 指向的却是元素 5。

也就是说,反向迭代器的指向和其底层基础迭代器的指向具有这样的关系,即反向迭代器的指向总是距离基础迭代器偏左 1 个位置;反之,基础迭代器的指向总是距离反向迭代器偏右 1 个位置处。它们的关系如下图所示。

在这里插入图片描述

3

除了第 2 种初始化方式之外,reverse_iterator 模板类还提供了一个复制(拷贝)构造函数,可以实现直接将一个反向迭代器复制给新建的反向迭代器。比如:

//创建并初始化一个 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();

由此,my_reiter 反向迭代器指向的就是 myvector 容器中最后一个元素(也就是 5)之后的位置。

4 C++ STL reverse_iterator模板类中的成员

前面在学习每一种容器时,都提供有大量的成员函数。但迭代器模板类不同,其内部更多的是对运算符的重载。

reverse_iterator模板类中,重载了如下表所示的这些运算符。

在这里插入图片描述
下面程序演示了表中部分运算符的用法:

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

using namespace std;

int main() 
{
    
    
    //创建并初始化一个 vector 容器
    std::vector<int> myvector{
    
     1,2,3,4,5,6,7,8 };
    //创建并初始化一个反向迭代器
    std::reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.rbegin());//指向 8
    cout << *my_reiter << endl;// 8
    cout << *(my_reiter + 3) << endl;// 5
    cout << *(++my_reiter) << endl;// 7
    cout << my_reiter[4] << endl;// 3
    return 0;
}

输出结果:

8
5
7
3

可以看到,首先 my_reiter 方向迭代器指向 myvector 容器中元素 8,后续我们调用了如下几个运算符:

  • 通过重载的 * 运算符,输出其指向的元素 8;
  • 通过重载的 + 运算符,输出了距离当前指向位置为 3 的元素 5;
  • 通过重载的前置 ++ 运算符,将反向迭代器前移了 1 位,即指向了元素 7,并将其输出;
  • 通过重载的 [ ] 运算符,输出了距离当前位置为 4 的元素 3。

除此之外,reverse_iterator 模板类还提供了 base() 成员方法,该方法可以返回当前反向迭代器底层所使用的基础迭代器。举个例子:

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

using namespace std;

int main() 
{
    
    
    //创建并初始化一个 vector 容器
    std::vector<int> myvector{
    
     1,2,3,4,5,6,7,8 };
    //创建并初始化反向迭代器 begin,其指向元素 1 之前的位置
    std::reverse_iterator<std::vector<int>::iterator> begin(myvector.begin());
    //创建并初始化反向迭代器 begin,其指向元素 8
    std::reverse_iterator<std::vector<int>::iterator> end(myvector.end());
    //begin底层基础迭代器指向元素 1,end底层基础迭代器指向元素 8 之后的位置
    for (auto iter = begin.base(); iter != end.base(); ++iter) 
    {
    
    
        std::cout << *iter << ' ';
    }
    return 0;
}

输出结果:

1 2 3 4 5 6 7 8 % 

2 C++ STL插入迭代器适配器(insert_iterator)

插入迭代器适配器(insert_iterator),简称插入迭代器或者插入器,其功能就是向指定容器中插入元素。值得一提的是,根据插入位置的不同,C++ STL 标准库提供了 3 种插入迭代器,如下表所示。
在这里插入图片描述

1 C++ STL back_insert_iterator迭代器

back_insert_iterator 迭代器可用于在指定容器的末尾处添加新元素。

需要注意的是,由于此类型迭代器的底层实现需要调用指定容器的 push_back() 成员方法,这就意味着,此类型迭代器并不适用于 STL 标准库中所有的容器,它只适用于包含 push_back() 成员方法的容器。

另外,back_insert_iterator 迭代器定义在 头文件,并位于 std 命名空间中,因此在使用该类型迭代器之前,程序应包含如下语句:

#include <iterator>
using namespace std;

和反向迭代器不同,back_insert_iterator 插入迭代器的定义方式仅有一种,其语法格式如下:

std::back_insert_iterator<Container> back_it(container);

其中,Container 用于指定插入的目标容器的类型;container 用于指定具体的目标容器。

举个例子:

//创建一个 vector 容器
std::vector<int> foo;
//创建一个可向 foo 容器尾部添加新元素的迭代器
std::back_insert_iterator< std::vector<int> > back_it(foo);

在此基础上,back_insert_iterator 迭代器模板类中还对赋值运算符(=)进行了重载,借助此运算符,我们可以直接将新元素插入到目标容器的尾部。例如:

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

using namespace std;

int main() 
{
    
    
    //创建一个 vector 容器
    std::vector<int> foo;
    //创建一个可向 foo 容器尾部添加新元素的迭代器
    std::back_insert_iterator< std::vector<int> > back_it(foo);
    //将 5 插入到 foo 的末尾
    back_it = 5;
    //将 4 插入到当前 foo 的末尾
    back_it = 4;
    //将 3 插入到当前 foo 的末尾
    back_it = 3;
    //将 6 插入到当前 foo 的末尾
    back_it = 6;
    //输出 foo 容器中的元素
    for (std::vector<int>::iterator it = foo.begin(); it != foo.end(); ++it)
        std::cout << *it << ' ';
    return 0;
}

输出结果:

5 4 3 6 % 

通过借助赋值运算符,我们依次将 5、4、3、6 插入到 foo 容器中的末尾。这里需要明确的是,每次插入新元素时,该元素都会插入到当前 foo 容器的末尾。换句话说,程序中每个赋值语句,都可以分解为以下这 2 行代码:

//pos表示指向容器尾部的迭代器,value 表示要插入的元素
pos = foo.insert(pos,value);
++pos;

可以看到,每次将新元素插入到容器的末尾后,原本指向容器末尾的迭代器会后移一位,指向容器新的末尾。

除此之外,C++ STL 标准库为了方便用户创建 back_insert_iterator 类型的插入迭代器,提供了 back_inserter() 函数,其语法格式如下:

template <class Container>
    back_insert_iterator<Container> back_inserter (Container& x);

其中,Container 表示目标容器的类型。

显然在使用该函数时,只需要为其传递一个具体的容器(vector、deque 或者 list)做参数,此函数即可返回一个 back_insert_iterator 类型的插入迭代器。因此,上面程序中的反向迭代器代码,可替换成如下语句:

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

通过接收 back_inserter() 的返回值,我们也可以得到完全一样的 back_it 插入迭代器。

2 C++ STL front_insert_iterator迭代器

和 back_insert_iterator 正好相反,front_insert_iterator 迭代器的功能是向目标容器的头部插入新元素。

并且,由于此类型迭代器的底层实现需要借助目标容器的 push_front() 成员方法,这意味着,只有包含 push_front() 成员方法的容器才能使用该类型迭代器。

另外,front_insert_iterator 迭代器定义在 头文件,并位于 std 命名空间中,因此在使用该类型迭代器之前,程序应包含如下语句:

#include <iterator>
using namespace std;

值得一提的是,定义 front_insert_iterator 迭代器的方式和 back_insert_iterator 完全相同,并且 C++ STL 标准库也提供了 front_inserter() 函数来快速创建该类型迭代器。

举个例子:

#include <iostream>
#include <iterator>
#include <forward_list>

using namespace std;

int main() 
{
    
    
    //创建一个 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;
    front_it = 4;
    front_it = 3;
    front_it = 6;
    for (std::forward_list<int>::iterator it = foo.begin(); it != foo.end(); ++it)
        std::cout << *it << ' ';
    return 0;
}

输出结果:

6 3 4 5 %  

3 C++ STL insert_iterator迭代器

当需要向容器的任意位置插入元素时,就可以使用 insert_iterator 类型的迭代器。

需要说明的是,该类型迭代器的底层实现,需要调用目标容器的 insert() 成员方法。但幸运的是,STL 标准库中所有容器都提供有 insert() 成员方法,因此 insert_iterator 是唯一可用于关联式容器的插入迭代器。

和前 2 种插入迭代器一样,insert_iterator 迭代器也定义在 头文件,并位于 std 命名空间中,因此在使用该类型迭代器之前,程序应包含如下语句:

#include <iterator>
using namespace std;

不同之处在于,定义 insert_iterator 类型迭代器的语法格式如下:

std::insert_iterator<Container> insert_it (container,it);

其中,Container 表示目标迭代器的类型,参数 container 表示目标迭代器,而 it 是一个基础迭代器,表示新元素的插入位置。

和前 2 种插入迭代器相比,insert_iterator 迭代器除了定义时需要多传入一个参数,它们的用法完全相同。除此之外,C++ STL 标准库中还提供有 inserter() 函数,可以快速创建 insert_iterator 类型迭代器。

举个例子:

#include <iostream>
#include <iterator>
#include <list>

using namespace std;

int main() 
{
    
    
    //初始化为 {5,5}
    std::list<int> foo(2,5);
    //定义一个基础迭代器,用于指定要插入新元素的位置
    std::list<int>::iterator it = ++foo.begin();
    //创建一个 insert_iterator 迭代器
    //std::insert_iterator< std::list<int> > insert_it(foo, it);
    std::insert_iterator< std::list<int> > insert_it = inserter(foo, it);
    //向 foo 容器中插入元素
    insert_it = 1;
    insert_it = 2;
    insert_it = 3;
    insert_it = 4;
    //输出 foo 容器存储的元素
    for (std::list<int>::iterator it = foo.begin(); it != foo.end(); ++it)
        std::cout << *it << ' ';
    return 0;
}

输出结果:

5 1 2 3 4 5

3 流迭代器

4 move_iterator移动迭代器

5 辅助函数

C++ STL 标准库中还提供有一些辅助函数,如下表所示。
在这里插入图片描述

1 C++ STL advance()函数

advance() 函数用于将迭代器前进(或者后退)指定长度的距离,其语法格式如下:

template <class InputIterator, class Distance>
    void advance (InputIterator& it, Distance n);

其中 it 指的是目标迭代器,n 通常为一个整数。

需要注意的是,如果 it 为输入迭代器或者前向迭代器,则 n 必须为一个正数,即表示将 it 右移(前进) n 个位置;反之,如果 it 为双向迭代器或者随机访问迭代器,则 n 为正数时表示将 it 右移(前进) n 个位置,n 为负数时表示将 it 左移(后退) n 个位置。

另外,根据 it 类型是否为随机访问迭代器,advance() 函数底层采用了不同的实现机制:

  • 当 it 为随机访问迭代器时,由于该类型迭代器支持 p+n 或者 p-n(其中 p 就是一个随机访问迭代器)运算,advance()函数底层采用的就是 it+n 操作实现的;
  • 当 it 为其他类型迭代器时,它们仅支持进行 ++ 或者 – 运算,这种情况下,advance() 函数底层是通过重复执行 n 个 ++或者 – 操作实现的。

值得一提的是,advance() 函数定义在头文件,并位于 std 命名空间中。因此,程序在使用该函数之前,应包含如下 2 行代码:

#include <iterator>
using namespace std;

为了让读者更好地知晓 advance() 函数的功能,首先以 forward_list 容器(仅支持使用前向迭代器)为例,下面程序演示了 advance() 函数的功能:

#include <iostream>     // std::cout
#include <iterator>     // std::advance
#include <forward_list>

using namespace std;

int main() 
{
    
    
    //创建一个 forward_list 容器
    forward_list<int> mylist{
    
    1,2,3,4};
    //it为前向迭代器,其指向 mylist 容器中第一个元素
    forward_list<int>::iterator it = mylist.begin();
    //借助 advance() 函数将 it 迭代器前进 2 个位置
    advance(it, 2);
    cout << "*it = " << *it;
    return 0;
}

输出结果:

*it = 3%  

此程序中,由于 it 为前向迭代器,其只能进行 ++ 操作,即只能前进(右移),所以 advance() 函数的第 2 个参数只能为正数。

下面程序以 vector 容器为例,演示了 advance() 函数的功能:

#include <iostream>     // std::cout
#include <iterator>     // std::advance
#include <vector>

using namespace std;

int main() 
{
    
    
    //创建一个 vector 容器
    vector<int> myvector{
    
    1,2,3,4};
    //it为随机访问迭代器,其指向 myvector 容器中第一个元素
    vector<int>::iterator it = myvector.begin();
    //借助 advance() 函数将 it 迭代器前进 2 个位置
    advance(it, 2);
    cout << "1、*it = " << *it << endl;
    //继续使用it,其指向 myvector 容器中最后一个元素之后的位置
    it = myvector.end();
    //借助 advance() 函数将 it 迭代器后退 3 个位置
    advance(it, -3);
    cout << "2、*it = " << *it;
    return 0;
}

输出结果:

1、*it = 3
2、*it = 2%  

2 distance()函数

作用于同一容器的 2 个同类型迭代器可以有效指定一个区间范围。在此基础上,如果想获取该指定范围内包含元素的个数,就可以借助本节要讲的 distance() 函数。

distance() 函数用于计算两个迭代器表示的范围内包含元素的个数,其语法格式如下:

template<class InputIterator>
  typename iterator_traits<InputIterator>::difference_type distance (InputIterator first, InputIterator last);

其中,first 和 last 都为迭代器,其类型可以是输入迭代器、前向迭代器、双向迭代器以及随机访问迭代器;该函数会返回[first, last)范围内包含的元素的个数。

注意,first 和 last 的迭代器类型,直接决定了 distance() 函数底层的实现机制:

  • 当 first 和 last 为随机访问迭代器时,distance() 底层直接采用 last - first 求得 [first,
    last) 范围内包含元素的个数,其时间复杂度为O(1)常数阶;
  • 当 first 和 last 为非随机访问迭代器时,distance() 底层通过不断执行 ++first(或者 first++)直到
    first==last,由此来获取 [first, last) 范围内包含元素的个数,其时间复杂度为O(n)线性阶。

另外,distance() 函数定义在头文件,并位于 std 命名空间中。因此在使用此函数前,程序中应包含如下代码:

#include <iterator>
using namespace std;

下面程序以 list 容器(其迭代器类型为双向迭代器)为例,演示了 distance() 函数的用法:

#include <iostream>     // std::cout
#include <iterator>     // std::distance
#include <list>         // std::list

using namespace std;

int main() 
{
    
    
    //创建一个空 list 容器
    list<int> mylist;
    //向空 list 容器中添加元素 0~9
    for (int i = 0; i < 10; i++) 
    {
    
    
        mylist.push_back(i);
    }
    //指定 2 个双向迭代器,用于执行某个区间
    list<int>::iterator first = mylist.begin();//指向元素 0
    list<int>::iterator last = mylist.end();//指向元素 9 之后的位置
    //获取 [first,last) 范围内包含元素的个数
    cout << "distance() = " << distance(first, last);
    return 0;
}

输出结果:

distance() = 10% 

猜你喜欢

转载自blog.csdn.net/qq_24649627/article/details/108136976