第九章-顺序容器

顺序容器

1 顺序容器概述

顺序容器类型

vector可变大小数组。支持快速随机访问。在尾部之外插入/删除元素可能很慢
deque双端队列。支持快速随机访问。在头尾位置插入/删除很快
list双向链表。只支持双向顺序访问。在list中任何位置插入/删除操作速度都很快
forward_list单向链表。只支持单向顺序访问。在任何位置插入/删除操作速度都很快
array固定大小数组。支持快速随机访问。不能添加/删除元素
string与vector相似,但专门用来保存字符。随机访问快,在尾部插入/删除速度快
  • forward_list和array是C++11新标准增加的内省。和内置数组相比,array是一种更安全、更容易使用的数组类型。forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作(保存/计算其大小就会比手写链表多出额外的开销)。
  • 选择容器的基本原则:
  • 除非有很好的理由选择其他容器,否则应使用vector;
  • 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list;
  • 如果程序要求随机访问元素,应使用vector或deque;
  • 如果程序需要在容器的中间插入/删除元素,应使用list或forward_list;
  • 如果程序需要在头尾位置插入/删除元素,但不会在中间进行插入/删除操作,则使用deque;
  • 如果程序只有在读取输入时才需要在容器中间插入元素,随后需要随机访问元素,则:
  • 首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常一容易地向vector追加数据,然后调用sort,从而避免在中间位置添加元素。
  • 如果必须要在中间位置插入元素,考虑在输入阶段用list,一旦输入完成,将list的内容拷贝到一个vector中。
  • 如果程序既需要随机访问元素,又需要在容器中间位置插入元素。这时候应该取决于list或forward_list中访问元素与vector或deque中插入/删除元素的相对性能。一般来说,应用中占主导地位的操作决定了容器类型的选择。

2 容器库概览

2-1 迭代器

  • 一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置,[begin,end) 左闭合区间。

2-2 容器类型成员

  • 反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各种操作的含义也都发生了颠倒。

2-3 begin和end成员

  • begin()和end()都是被重载过的。一个是const成员,返回容器的const_iterator类型;另外一个是非常量成员,返回容器的iterator类型。
  • C++11新标准引入了cbegin()和cend(),可以获得const_iterator,而不管容器的类型是什么。

2-4 容器定义和初始化

  • 将一个新容器创建为另一个容器的拷贝的方法有两种:直接拷贝整个容器;(array除外)拷贝由一个迭代器对指定的元素范围。
  • 为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。不过当传递迭代器参数来拷贝一个范围时,不要求元素类型相同,只需能将要拷贝的元素转换为初始化的容器的元素类型即可。
  • 除了与关联容器相同的构造函数外,顺序容器(array除外)还提供另外一个构造函数,它接受一个容器大小和一个(可选)元素初始值。如果我们不提供元素初始值,则标准库会创建一个值初始器。
  • 一个默认构造的array是非空的:它包含了与其一样大小一样多的元素,这些元素都是被默认初始化的。
  • 虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但array无此限制。

2-5 赋值和swap

  • 由于邮编运算的大小可能与左边运算对象的大小不同,因此array类型不支持assign,也不允许花括号包围的值进行赋值。
  • 赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器、引用和指针失效(array和string除外)。
  • swap两个array会真正交换它们的元素。
  • 统一使用非成员版本的swap是一个好习惯。

2-6 容器大小操作

2-7 关系运算符

  • 比较两个容器的过程:
  • 如果两个容器具有相同的大小而且所有所有元素都两两对应相等,则这两个容器相等;否则不等。
  • 如果两个容器大小不同,但较小容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
  • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。

3 顺序容器操作

3-1 向顺序容器添加元素

  • 当我们用一个对象来初始化容器时,或将一个对象插入到容器时,实际上放入到容器的是对象的一个拷贝。
  • emplace是通过使用构造对象的参数在容器管理的内存空间中直接构造元素。

3-2 访问元素

  • at和下表操作只适用于string、vector、deque和array。

3-3 删除元素

3-4 特殊的forward_list操作

  • forward_list由于没有办法很方便获得某元素的前驱,所以插入和删除操作都是在某个元素之后进行的(而不像其他容器在某个元素之前进行添加/删除)。

3-5 改变容器的大小

3-6 容器操作可能使迭代器失效

  • 由于向迭代器添加/删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确的重定位迭代器。

4 vector对象如何增长

shrink_to_fit只适用于vector、string和deque。 capacity和reserve只适用于vector和string。

容器大小管理操作

c.shrink_to_fit()请求将capacity减少为size相同的大小
c.capacity()不重新分配内存的话,c可以保存多少元素
c.reserve()分配至少容纳n个元素的内存空间
  • 只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间(可能更大)。如果需求大小小于或等于当前容量,reserve什么也不做。
  • 只有执行insert操作时size与capacity相等,或者调用resize或reserve时给定的大小超过当前capacity,vector才可能重新分配内存空间。

5 额外的string操作

5-1 构造string的其他方法

  • 通常当我们从一个const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符停止。如果我们还传递给构造函数一个计数值,数组就不必以空字符结尾。如果我们未传递计数值且也未以空字符结尾,或者给定计数值大于数组大小,则构造函数的行为是未定义的。

5-2 改变string的其他方法

5-3 string搜索操作

5-4 compare函数

5-5 数值转换

6 容器适配器

  • 适配器是标准库中一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
  • 标准库定义了三种顺序容器适配器:stack、queue和priority_queue。默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。
点赞