您的位置:首页 > 编程语言 > C语言/C++

C++ Primer 第二遍阅读笔记(第九章)

2013-01-27 20:27 183 查看
本章将对第三章的内容进行扩充和完善,继续讨论标准库提供的顺序容器类型。

标准库定义了三种顺序容器类型:vector、list 和 deque(是双端队列“double-ended queue”的简写,发音为“deck”)。它们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。

标准库还提供了三种容器适配器(adaptors)。顺序容器适配器包括stack、queue 和 priority_queue 类型。



容器只定义了少量操作。大多数额外操作则由算法库提供,我们将在第十一章学习算法库。

为了定义一个容器类型的对象,必须先包含相关的头文件,即下列头文件之一:



所有的容器都是类模板。要定义某种特殊的容器,必须在容器名后加一对尖括号,尖括号里面提供容器中存放的元素的类型:



为了使程序更清晰、简短,容器类型最常用的构造函数是默认构造函数

除了默认构造函数,容器类型还提供其他的构造函数,使程序员可以指定元素初值,见表 9.2。





将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同

尽管不能直接将一种容器内的元素复制给另一种容器,但系统允许通过传递一对迭代器间接实现该实现该功能。使用迭代器时,不要求容器类型相同。容器内的元素类型也可以不相同,只要它们相互兼容,能够将要复制的元素转换为所构建的新容器的元素类型,即可实现复制。



允许通过使用内置数组中的一对指针初始化容:



除了引用类型外,所有内置或复合类型都可用做元素类型。引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器。

因为容器受容器元素类型的约束,所以可定义元素是容器类型的容器。例如,可以定义 vector 类型的容器 lines,其元素为 string 类型的 vector 对象:



必须用空格隔开两个相邻的 > 符号,以示这是两个分开的符号,否则,系统会认为 >> 是单个符号,为右移操作符,并导致编译时错误。

表 9.3 列出迭代器为所有标准库容器类型所提供的运算。





C++ 定义的容器类型中,只有 vector 和 deque 容器提供下面两种重要的运算集合:







list 容器的迭代器既不支持算术运算(加法或减法),也不支持关系运算(<=, <, >=, >),它只提供前置和后置的自增、自减运算以及相等(不等)运算。

迭代器 first 和 last 如果满足以下条件,则可形成一个迭代器范围:

*它们指向同一个容器中的元素或超出末端的下一位置。

*如果这两个迭代器不相等,则对 first 反复做自增运算必须能够到达 last。换句话说,在容器中,last 绝对不能位于 first 之前。

使用迭代器编写程序时,必须留意哪些操作会使迭代器失效。使用无效迭代器将会导致严重的运行时错误。

表 9.5. 容器定义的类型别名





表 9.6. 容器的 begin 和 end 操作



除了 push_back 运算,list 和 deque 容器类型还提供了类似的操作:push_front。这个操作实现在容器首部插入新元素的功能。

在容器中添加元素时,系统是将元素值复制到容器里。类似地,使用一段元素初始化新容器时,新容器存放的是原始元素的副本

表 9.7 在顺序容器中添加元素的操作





任何 insert 或 push 操作都可能导致迭代器失效。当编写循环将元素插入到 vector 或 deque 容器中时,程序必须确保迭代器在每次循环后都得到更新

表 9.8. 顺序容器的大小操作



表 9.9. 访问顺序容器内元素的操作



使用越界的下标,或调用空容器的 front 或 back 函数,都会导致程序出现严重的错误。

使用下标运算的另一个可选方案是 at 成员函数。这个函数的行为和下标运算相似,但是如果给出的下标无效,at 函数将会抛出out_of_range 异常(第 6.13 节):



表 9.10. 删除顺序容器内元素的操作





vector 容器类型不支持 pop_front 操作。

pop_front 和 pop_back 函数的返回值并不是删除的元素值,而是 void。要获取删除的元素值,则必须在删除元素之前调用 front 或 back 函数。

删除一个或一段元素更通用的方法是
erase 操作。该操作有两个版本:删除由一个迭代器指向的单个元素,或删除由一对迭代器标记的一段元素。erase 的这两种形式都返回一个迭代器,它指向被删除元素或元素段后面的元素。也就是说,如果元素 j 恰好紧跟在元素 i 后面,则将元素 i 从容器中删除后,删除操作返回指向
j 的迭代器。

如同其他操作一样,erase 操作也不会检查它的参数。程序员必须确保用作参数的迭代器或迭代器范围是有效的

赋值assign 操作使左操作数容器的所有迭代器失效,swap 操作则不会使迭代器失效。完成 swap 操作后,尽管被交换的元素已经存放在另一容器中,但迭代器仍然指向相同的元素。

表 9.11. 顺序容器的赋值操作



带有一对迭代器参数的 assign 操作允许我们将一个容器的元素赋给另一个不同类型的容器

为了支持快速的随机访问,vector 容器的元素以连续的方式存放——每一个元素都紧挨着前一个元素存储。

vector 类提供了两个成员函数:capacity 和 reserve 使程序员可与 vector 容器内存分配的实现部分交互工作。capacity操作获取在容器需要分配更多的存储空间之前能够存储的元素总数,而reserve
操作则告诉 vector 容器应该预留多少个元素的存储空间。

弄清楚容器的 capacity(容量)与 size(长度)的区别非常重要。size 指容器当前拥有的元素个数;而 capacity 则指容器在必须分配新存储空间之前可以存储的元素总数。

为了说明 size 和 capacity 的交互作用,考虑下面的程序:



由此可见,空 vector 容器的 size 是 0,而标准库显然将其 capacity 也设置为 0。当程序员在 vector 中插入元素时,容器的 size 就是所添加的元素个数,而其 capacity 则必须至少等于 size,但通常比 size 值更大

现在,可如下预留额外的存储空间



每当 vector 容器不得不分配新的存储空间时,以加倍当前容量的分配策略实现重新分配。

list 容器表示不连续的内存区域,允许向前和向后逐个遍历元素。在任何位置都可高效地 insert 或 erase一个元素。插入或删除
list 容器中的一个元素不需要移动任何其他元素。另一方面,list 容器不支持随机访问,访问某个元素要求遍历涉及的其他元素。

对于 vector 容器,除了容器尾部外,其他任何位置上的插入(或删除)操作都要求移动被插入(或删除)元素右边所有的元素。

deque 容器拥有更加复杂的数据结构。从 deque 队列的两端插入和删除元素都非常快。在容器中间插入或删除付出的代价将更高。
deque 容器同时提供了 list 和 vector 的一些性质:

与 vector 容器一样,在 deque 容器的中间 insert 或 erase元素效率比较低

不同于 vector 容器,deque 容器提供高效地在其首部实现 insert 和 erase的操作,就像在容器尾部的一样。

与 vector 容器一样而不同于 list 容器的是, deque 容器支持对所有元素的随机访问

在 deque 容器首部或尾部插入元素不会使任何迭代器失效,而首部或尾部删除元素则只会使指向被删除元素的迭代器失效。在 deque 容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器都失效


vector 和 deque 容器都支持对其元素实现高效的随机访问

通常来说,除非找到选择使用其他容器的更好理由,否则 vector 容器都是最佳选择。

下面列举了一些选择容器类型的法则:

如果程序要求随机访问元素,则应使用vector 或 deque 容器。

如果程序必须在容器的中间位置插入或删除元素,则应采用
list 容器。

如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用 deque容器。

如果只需在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑在输入时将元素读入到一个 list 容器,接着对此容器重新排序,使其适合顺序访问,然后将排序后的 list 容器复制到一个 vector 容器。


再谈 string 类型

表 9.12 第 3.2 节介绍的 string 操作





在某些方面,可将 string 类型视为字符容器。除了一些特殊操作,string 类型提供与 vector 容器相同的操作。string 类型与 vector 容器不同的是,它不支持以栈方式操纵容器:在 string 类型中不能使用 front、back 和 pop_back操作。

string 不支持带有单个容器长度作为参数的构造函数。创建 string 对象时:不提供任何参数,则得到空的 string 对象;也可将新对象初始化为另一个 string 对象的副本;或用一对迭代器初始化;或者使用一个计数器和一个字符初始化:



除了上述构造函数之外,string 类型还提供了三种其他的方式创建类对象(表 9.13)。在前面的章节中,已经使用过只有一个指针参数的构造函数,该指针指向以空字符结束的字符数组中的第一个元素。另一种构造函数需要一个指向字符数组元素的指针和一个标记要复制多少个字符的计数器作参数。由于该构造函数带有一个计数器,因此数组不必以空字符结束:



9.13. 构造 string 对象的其他方法



表 9.14 与容器共有的 string 操作



表 9.15 string 类型特有的版本





在 string 对象中 insert 或 assign 的字符可来自于字符数组或另一个 string 对象。例如,以空字符结束的字符数组可以用作 insert 或 assign 到 string 对象的内容:



类似地,可如下所示将一个 string 对象的副本插入到另一个 string 对象中:



表 9.16 子串操作



表 9.17 修改 string 对象的操作(args 在表 9.18 中定义)



表 9.18 append 和 replace 操作的参数:args



replace 操作用于删除一段指定范围的字符,然后在删除位置插入一组新字符,等效于调用 erase 和 insert 函数

string 类提供了 6 种查找函数(表 9.19),每种函数以不同形式的 find 命名。这些操作全都返回 string::size_type 类型的值,以下标形式标记查找匹配所发生的位置;或者返回一个名为 string::npos的特殊值,说明查找没有匹配。

string 类型的查找操作



string 类型提供的 find 操作的参数



默认情况下,find 操作(以及其他处理字符的 string 操作)使用内置操作符比较 string 字符串中的字符。因此,这些操作(以及其他 string 操作)都区分字母的大小写

find 操作的返回类型是 string::size_type,请使用该类型的对象存储 find 的返回值。

如果在查找字符串时希望匹配任意指定的字符,则实现起来稍微复杂一点。例如,下面的程序要在 name 中寻找并定位第一个数字:



程序员可以给 find 操作传递一个可选的起点位置实参,用于指定开始查找的下标位置,该位置实参的默认值为 0。通常的编程模式是使用这个可选的实参循环查找 string 对象中所有的匹配。下面的程序重写了查找“r2d2”的程序,以便找出 name 字符串中出现的所有数字:



除了寻找匹配的位置外,还可以调用 find_first_not_of 函数查找第一个与实参不匹配的位置。例如,如果要在 string 对象中寻找第一个非数字字符,可以如下编写程序:



迄今为止,我们使用的所有 find 操作都是从左向右查找的。除此之外,标准库还提供了一组类似的从右向左查找 string 对象的操作。rfind 成员函数用于寻找最后一个——也就是是最右边的——指定子串出现的位置:



除了关系操作符,string 类型还提供了一组 compare 操作,用于实现字典顺序的比较。这些操作的结果类似于 C 语言中的库函数 strcmp

compare 函数返回下面列出的三种可能值之一:

正数,此时 s1 大于 args 所代表的 string 对象。

负数,此时 s1 小于 args 所代表的 string 对象。

0,此时 s1 恰好等于 args 所代表的 string 对象。


表 9.21 string 类型 compare 操作



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

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



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



所有适配器都定义了两个构造函数:默认构造函数用于创建空对象,而带一个容器参数的构造函数将参数容器的副本作为其基础值。

例如,假设 deq 是 deque<int> 类型的容器,则可用 deq 初始化一个新的栈,如下所示:



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



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

表 9.23. 容器适配器支持的操作





将 intStack 定义为一个存储整型元素的空栈。第一个 while 循环在该栈中添加了 stk_size 个元素,元素初值是从 0 开始依次递增 1 的整数。第二个 while 循环迭代遍历整个栈,检查其栈顶(top)的元素值,然后栈顶元素出栈,直到栈变空为止。

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

标准库队列使用了先进先出(FIFO)的存储和检索策略。进入队列的对象被放置在尾部,下一个被取出的元素则取自队列的首部。标准库提供了两种风格的队列:FIFO 队列(FIFO queue,简称 queue),以及优先级队列(priority queue)。

priority_queue允许用户为队列中存储的元素设置优先级。这种队列不是直接将新元素放置在队列尾部,而是放在比它优先级低的元素前面。标准库默认使用元素类型的
< 操作符
来确定它们之间的优先级关系。

表 9.24. 队列和优先级队列支持的操作



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: