顺序容器
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之上实现的。