stl 适配器
容器适配器
是一个封装了序列容器的类模板,它在一般序列容器的基础上提供了一些不同的功能。之所以称作适配器类,是因为它可以通过适配容器现有的接口来提供不同的功能。
本章将介绍 3 种容器适配器,分别是 stack、queue、priority_queue:
stack:是一个封装了 deque 容器的适配器类模板,默认实现的是一个后入先出(Last-In-First-Out,LIFO)的压入栈。stack 模板定义在头文件 stack 中。
queue:是一个封装了 deque 容器的适配器类模板,默认实现的是一个先入先出(First-In-First-Out,LIFO)的队列。可以为它指定一个符合确定条件的基础容器。queue 模板定义在头文件 queue 中。
priority_queue:是一个封装了 vector 容器的适配器类模板,默认实现的是一个会对元素排序,从而保证最大元素总在队列最前面的队列。priority_queue 模板定义在头文件 queue 中。
适配器类在基础序列容器的基础上实现了一些自己的操作,显然也可以添加一些自己的操作。它们提供的优势是简化了公共接口,而且提高了代码的可读性。本章我们会详细地探讨这些适配器的应用。
stack 栈适配器
stack 适配器以模板类 stack<T,Container=deque>(其中 T 为存储元素的类型,Container 表示底层容器的类型)的形式位于头文件中,并定义在 std 命名空间里。因此,在创建该容器之前,程序中应包含以下 2 行代码:
纯文本复制
#include
using namespace std;
创建
成员函数
queue
这种存储结构最大的特点是,最先进入 queue 的元素,也可以最先从 queue 中出来,即用此容器适配器存储数据具有“先进先出(简称 “FIFO” )”的特点,因此 queue 又称为队列适配器。
创建
成员函数
#include <iostream>
#include <queue>
#include <list>
using namespace std;
int main()
{
//构建 queue 容器适配器
std::deque<int> values{ 1,2,3 };
std::queue<int> my_queue(values);//{1,2,3}
//查看 my_queue 存储元素的个数
cout << "size of my_queue: " << my_queue.size() << endl;
//访问 my_queue 中的元素
while (!my_queue.empty())
{
cout << my_queue.front() << endl;
//访问过的元素出队列
my_queue.pop();
}
return 0;
}
priority_queue容器适配器
priority_queue 容器适配器模拟的也是队列这种存储结构,即使用此容器适配器存储元素只能“从一端进(称为队尾),从另一端出(称为队头)”,且每次只能访问 priority_queue 中位于队头的元素。
但是,priority_queue 容器适配器中元素的存和取,遵循的并不是 “First in,First out”(先入先出)原则,而是“First in,Largest out”原则。直白的翻译,指的就是先进队列的元素并不一定先出队列,而是优先级最大的元素最先出队列。
注意,“First in,Largest out”原则是笔者为了总结 priority_queue 存取元素的特性自创的一种称谓,仅为了方便读者理解。
那么,priority_queue 容器适配器中存储的元素,优先级是如何评定的呢?很简单,每个 priority_queue 容器适配器在创建时,都制定了一种排序规则。根据此规则,该容器适配器中存储的元素就有了优先级高低之分。
举个例子,假设当前有一个 priority_queue 容器适配器,其制定的排序规则是按照元素值从大到小进行排序。根据此规则,自然是 priority_queue 中值最大的元素的优先级最高。
priority_queue 容器适配器为了保证每次从队头移除的都是当前优先级最高的元素,每当有新元素进入,它都会根据既定的排序规则找到优先级最高的元素,并将其移动到队列的队头;同样,当 priority_queue 从队头移除出一个元素之后,它也会再找到当前优先级最高的元素,并将其移动到队头。
基于 priority_queue 的这种特性,因此该容器适配器有被称为优先级队列。
创建
成员函数
#include <iostream>
#include <queue>
#include <array>
#include <functional>
using namespace std;
int main()
{
//创建一个空的priority_queue容器适配器
std::priority_queue<int>values;
//使用 push() 成员函数向适配器中添加元素
values.push(3);//{3}
values.push(1);//{3,1}
values.push(4);//{4,1,3}
values.push(2);//{4,2,3,1}
//遍历整个容器适配器
while (!values.empty())
{
//输出第一个元素并移除。
std::cout << values.top()<<" ";
values.pop();//移除队头元素的同时,将剩余元素中优先级最大的移至队头
}
return 0;
}
迭代器适配器
5 种迭代器适配器,分别是反向迭代器适配器、插入型迭代器适配器、流迭代器适配器、流缓冲区迭代器适配器、移动迭代器适配器。
初学者完全可以将迭代器适配器视为普通迭代器。之所以称为迭代器适配器,是因为这些迭代器是在输入迭代器、输出迭代器、正向迭代器、双向迭代器或者随机访问迭代器这些基础迭代器的基础上实现的。也就是说,使用迭代器适配器的过程中,其本质就是在操作某种基础迭代器。
反向迭代器
反向迭代器适配器(reverse_iterator),可简称为反向迭代器或逆向迭代器,常用来对容器进行反向遍历,即从容器中存储的最后一个元素开始,一直遍历到第一个元素。
值得一提的是,反向迭代器底层可以选用双向迭代器或者随机访问迭代器作为其基础迭代器。不仅如此,通过对 ++(递增)和 --(递减)运算符进行重载,使得:
当反向迭代器执行 ++ 运算时,底层的基础迭代器实则在执行 – 操作,意味着反向迭代器在反向遍历容器;
当反向迭代器执行 – 运算时,底层的基础迭代器实则在执行 ++ 操作,意味着反向迭代器在正向遍历容器。
#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
运算符
#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。
插入迭代器
插入迭代器适配器(insert_iterator),简称插入迭代器或者插入器,其功能就是向指定容器中插入元素。值得一提的是,根据插入位置的不同,C++ STL 标准库提供了 3 种插入迭代器
back insert
#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
front insert
#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
insert iterator
当需要向容器的任意位置插入元素时,就可以使用 insert_iterator 类型的迭代器。
需要说明的是,该类型迭代器的底层实现,需要调用目标容器的 insert() 成员方法。但幸运的是,STL 标准库中所有容器都提供有 insert() 成员方法,因此 insert_iterator 是唯一可用于关联式容器的插入迭代器。
定义 insert_iterator 类型迭代器的语法格式如下:
std::insert_iterator insert_it (container,it);
其中,Container 表示目标容器的类型,参数 container 表示目标容器,而 it 是一个基础迭代器,表示新元素的插入位置。
#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;
}
begin()和end()函数用法
#include <iostream> // std::cout
#include <vector> // std::vector, std::begin, std::end
using namespace std;
int main() {
//创建并初始化 vector 容器
std::vector<int> myvector{ 1,2,3,4,5 };
//调用 begin() 和 end() 函数遍历 myvector 容器
for (auto it = begin(myvector); it != end(myvector); ++it)
cout << *it << ' ';
return 0;
}
程序执行结果为:
1 2 3 4 5
程序第 8 行中,begin(myvector) 等同于执行 myvector.begin(),而 end(myvector) 也等同于执行 myvector.end()。
#include <iostream> // std::cout
#include <vector> // std::vector, std::begin, std::end
using namespace std;
int main() {
//定义一个普通数组
int arr[] = { 1,2,3,4,5 };
//创建一个空 vector 容器
vector<int> myvector;
//将数组中的元素添加到 myvector 容器中存储
for (int *it = begin(arr); it != end(arr); ++it)
myvector.push_back(*it);
//输出 myvector 容器中存储的元素
for (auto it = myvector.begin(); it != myvector.end(); ++it)
cout << *it << ' ';
return 0;
}
程序执行结果为:
1 2 3 4 5
注意程序中第 10 行,这里用整数指针 it 接收 begin(arr) 的返回值,同时该循环会一直循环到 it 指向 arr 数组中最后一个元素之后的位置。