前言
知识点很多,这里只记录遗忘的。从这章开始会对前面章节的内容进行一个扩充,如果以前的忘了读起来会有点吃力。总的来说,本章节难度不大。
文章目录
- 一、概述
- 二、容器库概览
- 2.1容器定义和初始化
- 2.2赋值
- 三、顺序容器操作
- 3.1添加元素
- 3.2删除元素
- 3.3forward_list
- 3.4容器操作使迭代器失效
- 四、vector对象是如何增长的
- 五、string操作
- 5.1构造string 的其他方法
- 5.2改变string的其他方法
- 5.3string搜索操作
- 5.4compare函数
- 5.5数值转换
- 六、容器适配器
一、概述
顺序容器一共有六种类型:vector、deque、list、forward_list、array、string
。下图是它们的一些特性。除了固定大小的array外,其他容器都提供高效、灵活的内存管理。vector、string和array
之所以支持快速随机访问,是因为它们的元素存储在连续的内存空间中,所以进行插入删除操作就会很慢。而forward_list和list
正好相反,它们存储元素的内存空间都是离散的,因此访问元素的开销较大。deque
相对来说访问速度和某些情况的插入删除操作都比较高效。
array和forward_list
都是C++11
增加的新特性。array
相对内置数组,效果更高、更安全。forward_list
设计是为了达到与手写单向链表的性能。新标准库的容器优于旧版本。
二、容器库概览
2.1容器定义和初始化
将一个新容器创建为另一个容器的拷贝的方法:1)直接拷贝整个容器(两个容器的类型必须相同);2)拷贝一个由迭代器指定的元素范围(array除外
)(两个迭代器的类型可以不同,只要元素能够转换)
与顺序容器大小相关的构造函数
如果元素类型是内置类型或者是具有默认构造函数的类类型,可以只为构造函数提供一个容器大小参数。如果元素没有默认构造函数,除了大小参数外,还必须指定一个显式的元素初始值。
array
array
与内置数组很相似,当定义一个array
时,除了指定元素类型,还要指定容器大小。
array<int, 42>
array<string, 10>
一个默认构造的array
是非空的,如果我们对array
进行列表初始化,初始值的数目必须等于或小于array
的大小。内置数组不能对其进行拷贝或对象赋值操作,但是array没有限制
int d[3] = {1,2,3};
int c[3] = d[3]; // 错误
array<int, 3> d2 = {1,2,3};
array<int, 10> c2 = d2;
2.2赋值
赋值就是把左边容器的元素全部换成右边容器中元素的拷贝。只有顺序容器可以使用assign,但是array不支持assign操作。
assign
赋值运算符要求等号两边的运算对象具有相同的类型,assign
允许从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。使用assign会使左边容器内部的迭代器、引用和指针失效
swap
swap
交换两个相同类型容器的内容,除string
外,指向容器的迭代器、引用和指针在swap
操作之后都不会失效。swap两个array会正在交换它们的元素
三、顺序容器操作
3.1添加元素
除了array
,所有标准库容器都提供灵活的内存管理。下表是添加元素的的操作。前面提到,像vector、string等容器添加一个元素可能会导致整个对象的存储空间重新分配,所以当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间。
当我们将一个对象插入到容器时,实际上放入的是对象的拷贝,容器的元素与提供值的对象之间没有任何关联。
emplace
C++11
引入了三个新成员:emplace_front、emplace、emplace_back
,前面说了以前的插入元素的方法是拷贝,而这些新成员会进行构造,不会拷贝。下面一段代码很容易理解它们之间的差别。加入有一个info
对象,它含有三个数据成员,且对应的构造函数。在使用emplace_back
,我们可以在A
的末尾添加一个Info
对象,但是push_back
却不行,因为它是拷贝元素,对于push_back
的函数设计里面没有接收三个参数的版本,所以只能写成最后一行的形式,先构造,再拷贝。总之,emplace_back
直接进行构造简化了操作过程。当然,如果Info对象没有对应三个参数的构造函数,那么该操作也会失效。
A.emplace_back("heyun",12,23);
A.push_back("heyun",12,23); // 错误
A.push_back(Info("heyun",12,23));
3.2删除元素
下标是删除元素的操作。
3.3forward_list
有C语言
基础的应该都记得链表,forward_list
其实就是一个单向链表。它的特殊之处在于,进行添加和删除元素时,删除或添加的元素之前的那个元素的后继会发生改变。
下表是forward_list
特有的插入和删除元素操作。
3.4容器操作使迭代器失效
添加元素
vector和string
,如果存储空间被重新分配,指向容器的迭代器、指针和引用都会失效。如果没有重新分配,则指向插入元素之间的有效,之后的全部失效。deque
,除了在首尾之外的任何位置插入元素会让它们都失效,如果在首尾添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效list和forward_list
,全部都有效
删除元素
list和forward_list
,指向容器其他位置的迭代器全部都有效deque
,在首尾之外的任何位置删除元素,指向被删除元素之外其他元素的迭代器、引用或指针也会失效。如果删除的是尾元素,则尾后迭代器也会失效,但是其他迭代器、引用和指针不受影响。如果删除首元素,这些都不受影响vector和string
,指向被删除元素之前的有效。- 只要删除元素,尾后迭代器总是失效。
四、vector对象是如何增长的
第一次接触vector
时,就很好奇这个和数组类似的存储容器居然有和链表一样的动态增加容量的功能。我们知道vector
为了支持快速随机访问,里面的元素是连续存储的,如果这时vector
的空间不足以接纳新的元素,可以再开辟一段连续的空间,就旧元素和新元素一起拷贝进去,最后删除旧的空间。这样虽然能够达到目的,但是经常进行性能会非常之慢。
C++标准库
为了避免这种代价,设法减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string
的实现通常会分配比新的空间需求更大的内存空间。
我们可以人为的去管理容量的成员函数。对于reserve
,只有当前需要的内存空间超过当前容量时才会改变vector
的容量。
五、string操作
这一小节主要对第三章的string
内容进行一个补充。
5.1构造string 的其他方法
5.2改变string的其他方法
string
类型同样支持顺序容器的赋值运算符及assign、insert和erase操作
。
s.insert(s.size(),5,'!'); // 在s末尾插入5个感叹号
s.erase(s.size() - 5,5); // 在s删除最后5个字符
const char *cp = "Stately, plump Buck";
s.assign(cp,7); // s == "Stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"
assign总是替换string中的所有内容,append总是将新字符追加到string末尾
5.3string搜索操作
string
类提供了6个不同的搜索函数,每个函数都有4个重载版本,如下表所示。每个搜索操作会返回一个string::size_type
值,表示匹配发生位置的下标,如果搜索失败,会返回一个名为string::npos
的static
成员。两个搜索操作的返回值类型都是一个unsigned类型,所以不要用带符号类型进行接收。
下面写几个书上的例子:
string name("AnnaBelle");
auto pos1 = name.find("Anna"); // pos1 = 0
// 查找给定字符串中任何一个字符匹配的位置
string numbers("0123456789"), name("r2d2");
auto pos = name.find_first_of(numbers);
string dept("1231p23");
auto pos = dept.find_first_not_of(number); // 返回p的下标
5.4compare函数
5.5数值转换
C++11
引入多个函数可以实现数值数据与标准库string
之间的转换。
六、容器适配器
stack、queue和priority_queue
是三个顺序容器适配器。一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
定义一个适配器
每个适配器都定义两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器。stack和queue基于deque实现,priority_queue在vector的基础上实现。
stack<int> stk(deq); // 从deq拷贝元素到stk
stack<string, vector<string>> str_stk; // 在vector上实现的空栈
所有适配器都要求容器具有添加和删除元素的能力,所以array
不适用于适配器。同理forward_list
也不行。
stack
只要求push_back、pop_back和back
,因此可以使用除array和forward_list
之外的任何容器类型来构造stack
。
queue
要求back、push_back、front和push_front
,因此可以构造于list或deque
之上,但不能基于vector
构造。
priority_queue
除了front、push_back和pop_back
操作之外还要求随机访问能力,可以构造于vector或deque
之上,不能基于list
构造。
栈适配器
队列适配器
priority_queue
允许为队列中的元素建立优先级,也就是每个元素含有一个权重,权重大的会优先排在前面。