C++ 学习笔记(9)顺序容器
参考书籍:《C++ Primer 5th》
API:容器库
9.1 顺序容器概述
- string 和 vector 元素保存在连续的内存空间中。所以用下标计算地址时非常快。
- forward list 的设计目的是达到与手写单项列表数据结构相当的性能。
9.2 容器库概述
class A
{
public:
string name;
A(string str) { name = str; }
};
void main()
{
vector<A> v1(2, A("abc")); // 使用构造函数初始化所有元素。
}
9.2.1 迭代器
- 迭代器范围(iterator range):由两个迭代器表示,指向同一容器元素或元素之后的位置(begin 和 end)。
9.2.3 begin 和 end 成员
vector<A> v1;
auto it1 = v1.begin(); // it1 是 vector<A>::iterator 类型
auto it2 = v1.cbegin(); // it2 是 vector<A>::const_iterator 类型
9.2.4 容器定义和初始化
- 将容器创建为另一个容器的拷贝有两种方法:
- 直接拷贝:要求容器类型相同,其元素类型也相同。
- 由迭代器指定元素范围:不要求容器类型相同,元素类型也可以不同(但能要转换)。
list<string> str1 = { "abc","jjj","2333" };
vector<const char *> ccp = { "fuck","that","shit" };
// 直接拷贝
list<string> str2(str1); // 正确,容器类型相同,元素类型相同。
vector<string> vec(str1); // 错误,容器类型不同。
list<const char *> ccp2(ccp); // 错误,元素类型不同。
// 迭代器拷贝
forward_list<string> words(ccp.begin(), ccp.end()); // 正确,容器类型可以不同,元素类型不同但可以转换。
- array具有固定大小,初始化时需要同时类型和指定大小。
- 内置数组无法进行拷贝或对象赋值,但是array可以。
int digs[3] = { 1,2,3 };
int cpy[3] = digs; // 错误,内置数组不支持拷贝或赋值
array<int, 3> digits = {1,3,5};
array<int, 3> copy = digits; // 正确,但要求类型要完全相同
9.2.5 赋值和swap
- swap交换两个相同类型容器的内容。元素本身并没有交换,交换的是两个容器的内部数据结构(即不对元素拷贝、插入、删除)。这样指向内容迭代器、引用和指针都在swap操作后不会失效。
- swap交换两个array会真正交换元素(迭代器、指针和引用指向的位置不变,但是内容变了,所以是错误的),交换的时间和array中元素数目成正比。
9.2.6 容器大小操作
- forward_list 支持max_size 和 empty,但不支持size。
9.2.7 关系运算符
- 类似string:
- 两个容器元素对应相等时,容器相等。
- 容器大小不同,但小的都对应相等时,数目少的大于数目多的。
- 两个容器比较结果也取决于第一个不同的元素比较结果。
9.3 顺序容器操作
9.3.1 向顺序容器中添加元素
- 对于vector和string,除了尾部,其他位置添加元素都需要移动元素。还可能引起整个对象存储空间重新分配。
- 对于deque,除了首尾,都要移动元素。
- insert返回的是指向新插入元素的迭代器。
- 当用一个对象来初始化容器时,或将一个对象插入到容器(push或insert),实际上放入到容器中的对象值的拷贝,而非本身。
- 使用emplace操作是构造而非拷贝。直接在容器中构造,不会产生临时对象。
class A
{
public:
string name = "nothing";
A() = default;
A(string str) { name = str; }
};
void main()
{
vector<A> vecA;
vecA.emplace_back("shit"); // 在容器内部中直接构造元素,调用了A(string str)的构造函数。
vecA.emplace_back(); // 添加了使用默认构造函数的对象。
system("pause");
}
9.3.2 访问元素
- front、back、下标、at:返回的都是引用。
vector<string> svec;
cout << svec[0]; // 运行错误。
cout << svec.at(0); // 抛出out_of_range异常。
9.3.3 删除元素
- erase删除元素后,返回指向删除元素后位置的迭代器。
9.3.4 特殊的forward_list操作
- 在删除一个元素时,会影响到前一个元素的信息(即指向下一个元素的地址)。要删除一个元素,就要在前一个元素调用erase_after。
9.3.5 改变容器大小
9.3.6 容器操作可能使迭代器失效
- 容器添加元素:
- vector 或 string:
- 重新分配空间时:迭代器、指针引用都失效。
- 没有重新分配空间:插入之前都有效,之后的都失效。
- deque:
- 插入首尾之外:都会失效。
- 插入首位置:迭代器失效,指针引用仍有效。
- list 或 forward_list:
- 一直有效。
- vector 或 string:
- 容器删除元素(所有被删除的元素的迭代器、指针、引用都会失效):
- vector 或 string:
- 之前的都有效,之后的都失效。
- deque:
- 删除首尾之外:都失效。
- 删除尾元素:迭代器失效,指针引用有效。
- 删除首元素:都有效。
- list 或 forward_list:
- 一直有效。
- vector 或 string:
9.4 vector 对象是如何增长的
- 每次需要分配新内存空间时,将当前容量翻倍。
- 如果需求小于等于当前容量,reserve什么都不做。
- 调用shrink_to_fit只是一个请求,标准库并不保证退还内存。
- 仅在插入或调用resize 或 reserve 时,才有可能重新分配空间。即删除(或全部清除)时,不会改变容量大小。
9.5 额外的string 操作
9.5.1 构造string的其他方法
原来介绍的string:C++ 学习笔记(3)命名空间using、字符串、string、vector、迭代器、数组
- 用const char*构造创建string时,字符串必须以空字符作为结尾,因为拷贝操作在遇到空字符时才会停止。
9.5.2 改变string的其他方法
- replace是调用erase和insert的一种简写。
9.5.3 string 搜索操作
- npos:const_string::size_type类型,初始值-1(无符号型,即最大值)。
9.5.5 数值转换
// 找到字符串第一个数值,获取其数字字符串,转换成浮点数。
string s = "pi = 3.14";
float d = stod(s.substr(s.find_first_of("+-.0123456789")));
9.6 容器适配器
- 适配器(adaptor):标准库的一个通用概念。容器、迭代器和函数都适用。本质上是一种机制,能使某种事物的行为看起来像另外一种事物一样。(就像任天堂给switch加个几块纸板一样。)
- 默认情况下,stack和queue都是基于deque实现的,priority_queue是在vector。
- 适配器需要有添加和删除能力(要求是顺序容器)。
- stack要求拥有back、push_back、pop_back:
- vector 、deque 、list。
- queue要求拥有back、push_back、front、pop_front:
- deque、list。
- priority_queue要求拥有front、push_back、pop_back,同时要有随机访问的能力:
- vector 、deque。
- stack要求拥有back、push_back、pop_back: