《C++Primer》第九章-顺序容器-学习笔记(3)

《C++Primer》第九章-顺序容器-学习笔记(3)

日志:
1,2020-03-12 笔者提交文章的初版V1.0

作者按:
最近在学习C++ primer,初步打算把所学的记录下来。

传送门/推广
《C++Primer》第二章-变量和基本类型-学习笔记(1)
《C++Primer》第三章-标准库类型-学习笔记(1)
《C++Primer》第八章-标准 IO 库-学习笔记(1)
《C++Primer》第十二章-类-学习笔记(1)

容器适配器

除了顺序容器,标准库还提供了三种顺序容器适配器队列(queue)优先级队列(priority_queue)栈(stack)
适配器(adaptor)是标准库中通用的概念,包括容器适配器迭代器适配器函数适配器
适配器本质上是使一事物的行为类似于另一事物的行为的一种机制。 容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。例如,stack(栈)适配器可使任何一种顺序
容器以栈的方式工作
。表 9.22 列出了所有容器适配器通用的操作和类型。

表 1. 适配器通用的操作和类型:

操作和类型 作用
size_type 一种类型,足以存储此适配器类型最大对象的长度
value_type 元素类型
container_type 基础容器的类型,适配器在此容器类型上实现
A a; 创建一个新空适配器,命名为 a
A a( c ); 创建一个名为 a 的新适配器,初始化为容器 c 的副本
关系操作符 所有适配器都支持全部关系操作符:==、 !=、 <、 <=、 >、>=

使用适配器时,必须包含相关的头文件:

#include <stack> // stack adaptor
#include <queue> // both queue and priority_queue adaptors

适配器的初始化

所有适配器都定义了两个构造函数默认构造函数用于创建空对象,而带一个容器参数的构造函数将参数容器的副本作为其基础值。例如,假设 deq deque 类型的容器,则可用 deq 初始化一个新的栈,如下所示:

stack<int> stk(deq); // copies elements from deq into stk
//带一个容器参数的构造函数`将参数容器的副本作为其基础值

覆盖基础容器类型

默认的stack 和 queue基于 deque 容器实现priority_queue基于 vector 容器实现。在创建适配器时,通过将一个顺序容器指定为适配器的第二个类型实参,可覆盖其关联的基础容器类型:

// empty stack implemented on top of vector
stack< string, vector<string> > str_stk;  //将一个顺序容器指定为适配器的第二个类型实参,可覆盖stack关联的基础容器类型deque
// str_stk2 is implemented on top of vector and holds a copy of svec
stack<string, vector<string> > str_stk2(svec);

对于给定的适配器,其关联的容器必须满足一定的约束条件。

  • stack 适配器所关联的基础容器可以是任意一种顺序容器类型。因此,stack 栈可以建立在vector、list 或者 deque 容器之上。
  • queue 适配器要求其关联的基础容器必须提供 push_front 运算,因此只能建立在 list 或deque容器上,而不能建立在vector 容器上。
  • priority_queue 适配器要求提供随机访问功能,因此可建立在vector 或 deque 容器上,但不能建立在 list 容器上。

适配器的关系运算

两个相同类型的适配器可以做相等、不等、小于、大于、小于等于以及等于关系比较,只要基础元素类型支持等于和小于操作符既可。这些关系运算由元素依次比较来实现。第一对不相等的元素将决定两者之间的小于或大于关系。

栈适配器

表 2列出了栈提供的所有操作。
表 2. 栈容器适配器支持的操作:

栈容器适配器操作 作用
s.empty() 如果栈为空,则返回 true,否则返回 stack
s.size() 返回栈中元素的个数
s.pop() 删除栈顶元素的值,但不返回其值
s.top() 返回栈顶元素的值,但不删除该元素
s.push(item) 在栈顶压入新元素

栈容器适配器支持的操作程序实例:

// number of elements we'll put in our stack
const stack<int>::size_type stk_size = 10;  //打算放入10个元素到栈中
stack<int> intStack; // empty stack  初始化一个栈
// fill up the stack
int ix = 0;
while (intStack.size() != stk_size) 
// use postfix increment; want to push old value onto intStack
	intStack.push(ix++); // intStack holds 0...9 inclusive  
int error_cnt = 0;  //用来检查元素错误的数量
// look at each value and pop it off the stack
while (intStack.empty() == false)
 {
	int value = intStack.top();
// read the top element of the stack
	if (value != --ix) 
	{
		cerr << "oops! expected " << ix<< " received " << value << endl;
		++error_cnt; 
	}
	intStack.pop(); // pop the top element, and repeat
}
cout << "Our program ran with "<< error_cnt << " errors!" << endl;

声明语句:

stack<int> intStack; // empty stack

将 intStack 定义为一个存储整型元素的空栈。第一个 while 循环在该栈中添加了 stk_size 个元素,元素初值是从 0 开始依次递增 1 的整数。第二个while 循环迭代遍历整个栈,检查其栈顶(top)的元素值,然后栈顶元素出栈,直到栈变空为止。
所有容器适配器都根据其基础容器类型所支持的操作来定义自己的操作。默认情况下,栈适配器建立在 deque 容器上,因此采用 deque 提供的操作来实现栈功能。例如,执行下面的语句:

// use postfix increment; want to push old value onto intStack
intStack.push(ix++); // intStack holds 0...9 inclusive

这个操作通过调用 push_back 操作实现,而该 intStack 所基于的 deque对象提供。尽管栈是以 deque 容器为基础实现的,但是程序员不能直接访问deque 所提供的操作。例如,不能在栈上调用push_back 函数,而是必须使用栈所提供的名为 push 的操作。

队列和优先级队列

标准库队列使用了先进先出(FIFO)的存储和检索策略。进入队列的对象被放置在尾部,下一个被取出的元素则取自队列的首部。标准库提供了两种风格的队列:FIFO 队列(FIFO queue,简称 queue),以及优先级队列(priority queue)
priority_queue允许用户为队列中存储的元素设置优先级。这种队列不是直接将新元素放置在队列尾部,而是放在比它优先级低的元素前面。标准库默认使用元素类型的 <操作符来确定它们之间的优先级关系。
优先级队列的一个实例机场行李检查队列。30 分钟后即将离港的航班的乘客通常会被移到队列前面,以便他们能在飞机起飞前完成检查过程。使用优先级队列的程序示例是操作系统的调试表,它决定在大量等待进程中下一个要执行的进程。
要使用FIFO 队列与优先级队列,必须包含 queue 头文件。表 3列出了队列和优先级队列所提供的所有操作。
表 3. 队列和优先级队列支持的操作:

队列和优先级队列操作 作用
q.empty() 如果队列为空,则返回 true,否则返回 false
q.size() 返回队列中元素的个数
q.pop() 删除队首元素,但不返回其值
q.front() 返回队首元素的值,但不删除该元素。(该操作只适用于队列)
q.back() 返回队尾元素的值,但不删除该元素。(该操作只适用于队列)
q.top() 返回具有最高优先级的元素值,但不删除该元素。(该操作只适用于优先级队列)
q.push(item) 对于 queue,在队尾压入一个新元素,对于 priority_quue,在基于优先级的适当位置插入新元素

小结

C++ 标准库定义了一系列顺序容器类型。容器是用于存储某种给定类型对象的模板类型。在顺序容器中,所有元素根据其位置排列和访问。顺序容器共享一组通用的已标准化的接口:如果两种顺序容器都提供某一操作,那么该操作具有相同的接口和含义。

所有容器都提供(有效的)动态内存管理。程序员在容器中添加元素时,不必操心元素存放在哪里。容器自己实现其存储管理。

最经常使用的容器类型是vector,它支持对元素的快速随机访问。可高效地在 vector 容器尾部添加和删除元素,而在其他任何位置上的插入或删除运算则要付出比较昂贵的代价。
deque 类与 vector 相似,但它还支持在 deque 首部的快速插入和删除运算
list 类只支持元素的顺序访问,但在 list 内部任何位置插入和删除元素都非常快速。

容器定义的操作非常少,只定义了构造函数、添加或删除元素的操作、设置容器长度的操作以及返回指向特殊元素的迭代器的操作。其他一些有用的操作,如排序、查找,则不是由容器类型定义,而是由第十一章介绍的标准算法定义。
在容器中添加或删除元素可能会使已存在的迭代器失效。当混合使用迭代器操作和容器操作时,必须时刻留意给定的容器操作是否会使迭代器失效。许多使一个迭代器失效的操作,例如 insert 或 erase,将返回一个新的迭代器,让程序员保留容器中的一个位置。使用改变容器长度的容器操作的循环必须非常小心其迭代器的使用。

参考资料

【1】C++ Primer 中文版(第四版·特别版)

注解

发布了52 篇原创文章 · 获赞 72 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/engineerxin/article/details/104818657