C++_Primer09.container
顺序容器
sequential container
概述
顺序容器中的元素顺序与加入容器的顺序是相同的。
| 容器类型 | 说明 |
|---|---|
| vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 |
| deque | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快 |
| list | 双向链表。只支持双向顺序访问。在list中任何位置插入/删除速度都很快 |
| forward_list | 单向链表。只支持单向顺序访问。在链表任何位置插入/删除操作速度都很快 |
| array | 固定大小数组。支持快速随机访问。不能添加或删除元素 |
| string | 与vector相似的容器,但专门用于保存字符。随即访问快。在尾部插入/删除速度快 |
string 和 vector 将元素保存在连续的内存空间中。由于元素是连续存储的,由元素下表来计算地址是非常快速的。但是,这两种容器的中间位置增删元素会非常耗时:在一次插入或删除操作后,需要移动其后所有元素。并且在添加一个元素时,有时需要分配额外的存储空间,此时所有元素都必须移动到新的存储空间中。
list 和 forward_list 两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这俩容器不支持元素的随机访问:为了访问一个元素,必须遍历整个容器。而且,与 vector, deque 和 array 相比,这两个容器的额外内存开销也很大。
deque 是一个更复杂的数据结构。与 string 和 vector 类似,deque 支持快速随机访问,在 deque 的中间位置添加删除元素的代价(可能)很高。但是,在 deque 的两端添加或删除元素都是很快的,与 list 和 forward_list 添加删除元素的速度相当。
forward_list 和 array 是新 C++ 标准增加的类型。与内置数组相比,array是一种更安全,更容易使用的数组类型。与内置数组类似,array 大小是固定的。forward_list 的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此 forward_list 没有 size 操作,因为保存或计算其大小会比手写链表多出额外的开销。对其他容器而言,size 保证是一个快速的常量时间的操作。
新标准库的容器比旧版本快得多。新标准库容器的性能几乎与最精心优化过的同类数据结构一样好
现代C++程序应该使用标准库容器,而不是更原始的数据结构,如内置数组
通常,使用 vector 是最好的选择
确定使用哪种容器
- 除非有更好的理由,否则应使用 vector
- 如果程序中有很多小元素,且空间的额外开销很重要,则不要使用 list 或 forward_list
- 如果程序要求随机访问元素,应使用 vector 或 deque
- 如果程序要求在容器的中间插入或删除元素,应使用 list 或 forward_list
- 如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用 deque
- 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
- 首先确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易的向 vector 追加数据,然后再调用标准库的 sort 函数重排,避免在中间位置添加元素。
- 如果必须在中间位置添加元素,考虑在输入阶段使用 list,然后再将 list 中的内容拷贝到 vector 中。
如果程序既需要随机访问元素,又需要在容器中间位置插入元素,则取决于 list或 forward_list 中访问元素和 vector 或 deque 中插入删除元素的相对性能。一般来说,程序中占主导地位的操作决定了容器类型的选择。
如果不确定应该使用哪种容器,那么可以在程序中只使用 vector 和 list 公共的操作:使用迭代器,不使用下标操作,避免随机访问。
容器库概览
迭代器范围(iterator range):由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置,这个范围是一个左闭合区间 [begin, end)
- 如果 begin 与 end 相等,则范围为空
- 如果不相等,则范围至少包含一个元素
- 可以对 begin 递增若干次,使得 begin==end
while (begin != end) {
* begin = val;
++begin;
}
容器的操作
| 容器操作 | 说明 |
|---|---|
| 类型别名 | |
| iterator | 迭代器 |
| const_iterator | 只读迭代器 |
| size_type | 无符号整数,足够保存此种容器类型最大可能容器的大小 |
| difference_type | 带符号整数类型,足够保存两个迭代器之间的距离 |
| value_type | 元素类型 |
| reference | 元素的左值类型,与 value_type& 含义相同 |
| const_reference | 元素的 const 左值类型 |
| 构造函数 | |
| C c; | 默认构造器 |
| Cc1(c2); | 构造 c2 的拷贝 c1 |
| C c(b, e); | 将迭代器 b 和 e 指定的范围内的元素拷贝到 c 中(array不支持) |
| C c(a, b, c...); | 列表初始化 c |
| 赋值与 swap | |
| c1 = c2; | c1 中的元素替换为 c2 中的元素 |
| c1 = {a, b, c...}; | c1 中的元素替换为列表中元素(不适用 array) |
| a.swap(b) | 交换 a 和 b 的元素 |
| swap(a, b) | 同上 |
| 大小 | |
| c.size() | c 的元素数(不适用forward_list) |
| c.max_size() | 最大元素数目 |
| c.empty() | 判空 |
| 添加/删除(不适用array) | |
| c.insert(args) | 将 args 中的元素拷贝进 c |
| c.emplace(inits) | 使用 inits 构造 c 中的一个元素 |
| c.erase(args) | 删除 args 指定的元素 |
| c.clear() | 清空 |
| 关系运算符 | |
| ==, != | 相等运算符 |
| <, <=, >, >= | 关系运算符(无序关联容器不支持) |
| 获取迭代器 | |
| c.begin(), c.end() | 返回指向 c 的首元素和尾元素之后位置的迭代器 |
| c.cbegin(), c.cend() | 返回 const_iterator |
| 反向容器的额外成员(不支持forward_list) | |
| reverse_iteraotr | 按逆序寻址元素的迭代器 |
| const_reverse_iterator | 不能修改元素的逆序迭代器 |
| c.rbegin(), c.rend() | 返回指向 c 的尾元素和首元素之前位置的迭代器 |
| c.crbegin(), c.crend() | const 类型 |
begin 和 end 成员
list<string> a = {"Milton", "Tom", "July"};
auto it1 = a.begin(); // list<string>::iterator
auto it2 = a.rbegin(); // list<string>::reverse_iterator
auto it3 = a.cbegin(); // list<string>::const_iterator
auto it4 = z.crbegin(); // list<string>::const_reverse_iterator
其中 a 为 const 类型时 it1 类型为 list<string>::const_iterator
当不需要写操作时,应使用 cbegin 和 cend
容器定义和初始化
| 初始化 | 说明 |
|---|---|
| C c; | 默认构造函数。如果C是一个array,则c中元素按默认方式初始化,否则c为空 |
| C c1(c2); | c1,c2 类型必须相同(如果C为array,这两者大小还必须相同) |
| C c1 = c2; | 同上 |
| C c | 列表初始化。列表中元素类型必须与C的元素类型相容 |
| C c = | 同上 |
| C c(b,e) | c 初始化为迭代器指定范围中元素的拷贝。其中元素类型必须与c元素类型相容(array不适用) |
| 只有顺序容器(不包括array)的构造函数才能接受大小参数 | |
| C seq(n) | seq 包含n个元素,这些元素都进行了值初始化;此构造函数是 explicit 的 |
| C seq(n, t) | seq 包含n个初始化为值t的元素 |
只有顺序容器(不包括 array)的构造器才能接受大小参数:
C seq(n); // seq 包含 n 个元素,这些元素进行了初始化
C seq(n,t); // seq 包含 n 个初始化为值 t 的元素
使用拷贝构造器时,两个容器的类型及元素类型必须匹配。不过当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且新容器与原容器中的元素类型也可以不同,只要能将要拷贝的元素转换为新容器的元素类型即可。
list<string> authors = {"Milton", "Tom", "July"};
vector<const char*> articles = {"a", "an", "the"};
list<string> list2(author); // 正确
deque<string> authList(authors); // 错误,容器类型不匹配
vector<string> words(articles); // 错误,容器类型不匹配
forward_list<string> words(articles.begin(), articles.end()); // 正确
除了和关联容器相同的构造函数外,顺序容器(array除外)还提供了另一个构造函数,它接受一个容器大小和一个元素初始值(可选):
vector<int> ivec(10, -1); // 10个元素,每个都初始化为-1
list<string> svec(10, "hi!"); // 10个string,每个都初始化为 "hi!"
forward_list<int> ivec(10); // 10个元素,每个都初始化为0
deque<string> svec(10); // 10个元素,每个都是空string
对于没有默认构造函数的类型,在指定大小参数外,还必须指定一个显式的元素初始值
标准库 array 具有固定大小
大小是array类型的一部分
当定义一个 array 时,除了指定元素类型,还要指定容器大小
array<int, 42>
array<string, 10>
// 为了使用array类型,必须同时指定元素类型大小
array<int, 10>::size_type i; // 正确
array<int>::size_type j; // 错误
array的初始化:
array<int, 10> ia1; // 10个默认初始化的 int
array<int, 10> ia2 = {1,2,3,4,5,6,7,8,9,10};
array<int, 10> a3 = {42}; // 剩余元素都为0
与内置数组类型不同,array可以进行拷贝或对象值操作:
int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs; // 错误
array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};
array<int, 10> copy = digits; // 正确
习题 9.11
9.11 对6种创建和初始化vector对象的方法,每一种都给出一个实例。解释每个vector包含什么值。
vector<int> ivec1; // 空
vector<int> ivec2{1, 2, 3, 4, 5}; // 1, 2, 3, 4, 5
vector<int> ivec3 = {6, 7, 8, 9, 10}; // 6, 7, 8, 9, 10
vector<int> ivec4(ivec2); // 1, 2, 3, 4, 5
vector<int> ivec5 = ivec3; // 6, 7, 8, 9, 10
vector<int> ivec6(10, 4); // 10个4
习题 9.12
9.12 对于接受一个容器创建其拷贝的构造函数,和接受两个迭代器创建拷贝的构造函数,解释它们的不同。
接受一个容器创建其拷贝的构造函数,要求两个容器类型及元素类型必须匹配。
接受两个迭代器创建拷贝的构造函数,不要求容器类型匹配,而且元素类型也可以不同,只要拷贝的元素能转换就可以。
习题 9.13
9.13 如何用一个list初始化一个vector?从一个vector又该如何创建?编写代码验证你的答案。
list<int> ilist = {1, 2, 3, 4, 5};
vector<double> dvec1(ilist.cbegin(), ilist.cend());
vector<int> ivec = {5, 6, 7, 8};
vector<double> dvec2(ivec.cbegin(), ivec.cend());
swap
swap(c1,c2); // 交换两个容器的元素,类型必须相同。swap通常比拷贝元素快得多
c1.swap(c2); // 同上
除array外,swap 不对任何元素进行拷贝,删除或插入操作,因此可以保证在常数时间内完成。
元素不会被移动,这意味着,除 string 外,指向容器的迭代器,引用和指针在 swap 操作之后都不会失效。他们仍指向 swap 操作之前所指向的那些元素。但是在 swap 之后,这些元素已经属于不同的容器了。
比如,iter 在 swap 前指向 svec1[3] 的 string,那么在 swap 后它指向 svec2[3]的元素。
对一个 string 调用 swap 会导致迭代器,引用和指针失效。
与其他容器不同,swap 两个 array 会真正交换他们的元素,因此交换两个 array 所需的时间与 array 中元素的数目成正比。
对于 array,swap 后的指针,引用和迭代器所绑定的元素保持不变,但元素值已经与另一个 array 中对应的元素值进行了交换。
早期版本只提供成员函数版本的 swap,而统一使用非成员版本的 swap 是一个好习惯。
使用 assign
assgin 操作允许从一个不同但相容的类型赋值,或从一个子序列赋值
仅顺序容器,array除外
list<string> names;
vector<const char*> oldstyle;
names = oldstyle; // 错误,容器类型不匹配
names.assign(oldstyle.cbegin(), oldstyle.cend()); // 正确
由于就元素被替换,因此传递给 assign 的迭代器不能指向调用 assign 的容器
使用assign替换为相同的值:
list<string> slist1(1);
slist1.assign(10, "Hiya!");
// 等价于:
// slist1.clear();
// slist.insert(slist1.begin(), 10, "Hiya!");
容器大小
每个容器都提供一个成员函数 size 返回容器中元素的数目。
成员函数 empty 当 size 为0时返回 true。
max_size 返回一个大于或等于容器能容纳最大元素数的值。
forward_list 支持 max_size 和 empty,但不支持 size
关系运算符
每个容器类型都支持相等运算符(==,!=)
除了无序关联容器外的所有容器都支持关系运算符(<,>,>=,<=),关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。即我们只能比较 vector
两个容器的比较实际上是逐对比较,工作方式与 string 类似。
容器的相等运算符实际上使用的是元素的 == 运算符实现比较的;其他关系运算符使用元素的 < 运算符。
顺序容器操作
list, vector, deque
string
forward_list
添加元素
添加元素:
| 操作 | 说明 |
|---|---|
| c.push_back(t) c.emplace_back(args) |
在c的尾部创建一个值为t或由args创建的元素,返回 void |
| c.push_front(t) c.emplace_front(args) |
在c的头部创建一个值为t或由args创建的元素,返回 void |
| c.insert(p, t) c.emplace(p, args) |
在迭代器 p 指向的元素前创建一个值为 t 或由 args 创建的元素。返回指向新添加元素的迭代器 |
| c.insert(p, n, t) | 在迭代器 p 指向的元素前插入 n 个 t。返回指向第一个新添加元素的迭代器,若 n 为0则返回 p |
| c.insert(p, b, e) | 在迭代器 p 前插入由迭代器 b 和 e 指定的范围的元素,b 和 e 不能指向 c 中的元素。返回指向添加的第一个元素的迭代器,若范围为空,则返回 p |
| c.inert(p, il) | il 是一个花括号包围的元素值列表,意思与上类似 |
forward_list 有自己转悠版本的 insert 和 emplace;并且它不支持 push_back 和 emplace_back
vector 和 string 不支持 push_front 和 emplace_back
向一个 vector, string 或 deque 插入元素会使所有指向容器的迭代器,引用和指针失效
向一个 vector, deque 或 string 添加元素可能会引起整个对象存储空间的重新分配,并将元素从旧空间移动到新的空间中。
当用一个对象初始化容器时,或将一个对象插入到容器时,实际放入到容器中的是对象值的一个拷贝,而不是对象本身
push_back
除 array 和 forward_list 外,每个顺序容器都支持 push_back (包括 string)
push_front
list, forward_list 和 deque 容器支持 push_front
vector 不支持 push_front 操作,但可以使用 insert 将元素插入到首位
在 vector 和 deque 的非首尾位置插入元素会很耗时
insert
vector, deque, list 和 string 都支持 insert. forward_list 提供了特殊版本的 insert 成员。
新标准下,insert 返回第一个新加入元素的迭代器(旧标准返回 void)。如果返回为空,insert 会返回第一个参数。
利用 insert 的返回值,可以在容器中的一个特定位置反复插入元素:
list<string> lst;
auto iter = lst.begin();
while(cin >> word){
iter = lst.insert(iter, word); // 等价于 push_front
}
emplace
emplace_front, emplace, emplace_back
当调用 push 或 insert 成员函数时,我们将元素类型的对象传递给他们,这些对象被拷贝到容器中。而当我们调用一个 emplace 成员函数时,则是将参数传递给元素类型的构造函数,emplace 成员会在容器管理的内存空间中直接构造元素:
c.emplace_back("1234567", 25, 15.99);
c.push_back("1234567", 25, 15.99); // 错误
c.push_back(Sales_data("1234567", 25, 15.99)); // 正确
c.emplace_back(); // c.push_back(Sales_data());
c.emplace(iter, "1234567"); // c.insert(iter, Sales_data("1234567"));
// 以下等价于 c.push_front(Sales_data("1234567", 25, 15.99));
c.emplace_front("1234567", 25, 15.99);
传递给 emplace 函数的参数必须与元素类型的构造函数相匹配
习题9.18
编写程序,从标准输入读取string序列,存入一个deque中。编写一个循环,用迭代器打印deque中的元素。
#include <string>
#include <iostream>
#include <deque>
using namespace std;
int main(int argc, char** argv){
string tmp;
deque<string> q;
while(cin >> tmp){
if (tmp == "exit"){
break;
}
q.push_back(tmp);
}
// 或者用 auto iter = q.cbegin()
for (deque<string>::const_iterator iter = q.cbegin(); iter != q.cend(); ++iter){
cout << * iter << endl;
}
return 0;
}
访问元素
若容器中没有元素, 访问操作的结果是未定义的
| 操作 | 说明 |
|---|---|
| c.back() | 返回 c 的尾元素的引用,等价于 *(c.end()-1) |
| c.front() | 首元素的引用,等价于 *c.begin() |
| c[n] | 返回 c 下表为 n 的元素的引用。如果 n>=c.size(), 则函数行为未定义 |
| c.at(n) | 同上。如果下标越界则抛出 out_of_range 异常 |
at 和下标操作只适用于 string, vector, deque 和 array
back 不适用于 forwrad_list
在调用 front 和 back 之前,要确保 c 非空。对空容器调用 front 和 back,就像使用越界下标一样。
返回的是引用
if (!c.empty()){
c.front() = 42;
auto& v = c.back();
v = 1024;
auto v1 = c.back(); // v1 不是引用,它是 c.back() 的一个拷贝
v1 = 0; // 未改变 c 中的元素
}
删除元素
| 操作 | 说明 |
|---|---|
| c.pop_back() | 删除 c 的尾元素,若 c 为空,则函数行为未定义。函数返回 void |
| c.pop_front() | 删除 c 的首元素,若 c 为空,则函数行为未定义。函数返回 void |
| c.erase(p) | 删除迭代器 p 所指定的元素,返回被删元素的下一个元素的迭代器。 若p是尾后迭代器则函数行为未定义 |
| c.erase(b, e) | 删除迭代器 b 和 e 所指定范围内的元素。返回最后被删元素之后的迭代器。若 e 是尾后迭代器,则返回尾后迭代器 |
| c.clear() | 删除 c 中所有元素。返回 void |
这些操作会改变容器大小,所以不适用于 array
forward_list 有特殊版本的 erase
forward_list 不支持 pop_back
vector 和 string 不支持 pop_front
删除 deque 中除首尾位置之外的任何元素都会使所有迭代器,引用和指针失效。指向 vector 或 string 中删除点之后位置的迭代器,引用和指针都会失效
删除元素的成员函数并不检查参数,所以在删除前程序员必须确保它们是存在的。
pop_front 和 pop_back
while (!ilist.empty()) {
process(ilist.front());
ilist.pop_front();
}
删除指定位置的元素
容器中删除所有的奇数:
list<int> lst = {0,1,2,3,4,5,6,7,8};
auto iter = lst.begin();
while (iter != lst.end()){
if (*iter % 2 == 1){
it = lst.erase(iter);
} else {
++iter;
}
}
特殊的 forward_list 操作
forward_list 是单向链表,没有办法获取一个元素的前驱链接
forward_list 未定义 insert, emplace 和 erase,而是定义了 insert_after, emplace_after 和 erase_after 的操作,为了删除某个元素,需要知道它的前一个元素
| 操作 | 说明 |
|---|---|
| lst.before_begin() lst.cbefore_begin() |
返回指向链表首元素之前不存在的元素的迭代器 |
| lst.insert_after(p, t) lst.insert_after(p, n, t) lst.insert_after(p, b, e) lst.insert_after(p, il) |
在迭代器 p 之后插入元素。il 是花括号列表 |
| lst.emplace_after(p, args) | 使用元素构造器直接在容器中直接创建元素,插入位置在 p 之后 |
| lst.erase_after(p) lst.erase_after(b, e) |
删除指定位置之后的元素。删除元素不包含 b |
改变容器大小
list<int> ilist(10, 42);
ilist.resize(15); // 将5个值为0的元素添加到 ilist 的末尾
ilist.resize(25, -1); // 将10个值为-1的元素添加到 ilist 的末尾
ilist.resize(5); // 从 ilist 末尾删除20个元素
容器操作可能使迭代器失效
添加元素:
- 对于 vector 或 string,存储空间被重新分配后,指向容器的迭代器,指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器,指针和引用仍有效,但指向插入位置之后元素的迭代器,指针和引用将会失效。
- 对于 deque,插入到除首尾位置之外的任何位置都会导致迭代器,指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
- 对于 list 和 forward_lsit,指向容器的迭代器,指针和引用仍有效。
删除元素:
- 对于 list 和 forward_list,指向容器其他位置的迭代器,引用和指针仍有效
- 对于 deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器,引用或指针也会失效。如果删除 deque 尾元素,则尾后迭代器也会失效,但其他迭代器,引用和指针不受影响;如果删除首元素,则这些也不会受影响
- 对于 vector 和 string,指向被删元素之前元素的迭代器,引用和指针仍有效
不要保存 end 返回的迭代器
当添加/删除 vector 或 string 的元素后,或在 deque 的首元素之外任何位置添加/删除元素后,原来 end 返回的迭代器总是会失效,因此,添加或删除元素的循环程序必须反复调用 end,而不能在循环之前保存 end 返回的迭代器当作容器末尾使用。通常 C++ 标准库实现的 end() 操作都很快。
vector 对象是如何增长的
为了支持快速随机访问,vector 和 string 会将元素连续存储。在增删元素时,如果空间不够容纳新元素,则容器必须分配新空间并将原来的元素拷贝到新位置,然后添加新元素,释放旧空间。为了避免每次添加元素都分配新空间和释放旧空间,在创建容器时会预留一部分空间,当不得不获取新空间时,才会分配更大的空间。
虽然 vector 在每次重新分配内存空间时都要移动所有元素,但使用此策略后,其扩张操作通常比 list 和 deque 还要快。
管理容量的成员函数
| 操作 | 说明 |
|---|---|
| c.shrink_to_fit() | 请将 capacity() 减少为与 size() 相同大小 |
| c.capacity() | 不重新分配内存空间的话,c 可以保存多少元素 |
| c.reserve(n) | 通知容器它应该准备保存 n 个元素,容器实际分配的空间至少是 n,可能更大 |
shrink_to_fit 只适用于 vector, string 和 deque
capacity 和 reserve 只适用于 vector 和 string
shrink_to_fit 要求容器退回不需要的内存空间,此函数指出我们不再需要任何多余的内存空间。
reserve 并不改变容器中元素的数量,它仅影响 vector 预先分配多大的内存空间
只有当需要的内存空间超过当前容量时,reserve 才会改变 vector 的容量,reserve 至少分配与需求一样大的内存空间。
类似的,resize 函数只改变容器中元素的数目,而不是容器的容量
只有在执行 insert 操作时 size 与 capacity 相等,或者调用 resize 或 reserve 时给定的大小超过当前 capacity,vector 才可能重新分配内存空间,分配多少空间取决于具体实现。
额外的 string 操作
构造 string 的其他方法
| 操作 | 说明 |
|---|---|
| string s(cp, n) | s 是 cp 指向的数组中前 n 个字符的拷贝,此数组应至少包含 n 个字符 |
| string s(s2, pos2) | s 是 s2 从下标 pos2 开始的字符的拷贝。若 pos2>s2.size(),则行为未定义 |
| string s(s2, pos2, len2) | 从下标 pos2 开始,长度为 len2 |
pos 参数不能超过 s 的范围,而长度可以,标准库最多拷贝到 string 结尾,不会更多
substr
返回一个 string,原始 string 的一部分或全部的拷贝
string s("hello world");
string s2 = s.substr(0, 5); // hello
string s3 = s.substr(6); // world
string s4 = s.substr(6, 11); // world
string s5 = s.substr(12); // 抛出 out_of_range
其他方法
s.insert(s.size(), 5, '!'); // 末尾添加5个叹号
s.erase(s.size()-5, 5); // 删除最后5个字符
const char* cp = "Stately, plump Buck";
s.assign(cp, 7); // s == "Stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"
string s = "abc", s2 = "hello";
s.insert(0, s2); // s == "helloabc"
s.insert(0, s2, s2.size()); // s == "hellohelloabc"
append 和 replace
append: 追加到末尾
replace: 替换
string s("C++ Primer"), s2 = " 4th edition";
s.append(s2);
// 从位置11 开始删除3个字符,然后插入 "5th"
s.replace(11, 3, "5th"); // C++ Primer 5th edition
| 操作 | 说明 |
|---|---|
| s.insert(pos, args) | 在 pos 前插入 args 大于或小于参数指定的字符串。pos 可以是下标或迭代器,接受下标的版本返回 s 的引用;接受迭代器的版本返回指向第一个插入字符的迭代器 |
| s.erase(pos, len) | 删除从位置 pos 开始的 len 个字符。如果 len 被省略,则删除从 pos 开始到 s 末尾的所有字符。返回一个指向 s 的引用 |
搜索操作
6个不同的搜索函数,每个函数有4个重载版本
返回值是位置
| 操作 | 说明 |
|---|---|
| s.find(args) | 查找 s 中 args 第一次出现的位置 |
| s.rfind(args) | 最后一次出现的位置 |
| s.find_first_of(args) | 在 s 中查找 args 中任何一个字符第一次出现的位置 |
| s.find_last_of(args) | 在 s 中查找 args 中任何一个字符最后一次出现的位置 |
| s.find_first_not_of(args) | 在 s 中查找第一个不在 args 中的字符 |
| s.find_last_not_of(args) | 在 s 中查找最后一个一个不在 args 中的字符 |
| args: | |
| c, pos | s 中位置 pos 开始查找字符 c |
| s2, pos | s 中位置 pos 开始查找字符串 s |
| cp, pos | cp 为C风格字符串指针,字符串以空字符结尾 |
| cp, pos, n | 查找 cp 的前 n 个字符 |
compare
根据 s 等于,大于或小于参数指定的字符串,s.compare 返回 0,正数或负数
| 参数形式 | 说明 |
|---|---|
| s2 | 比较 s 和 s2 |
| pos1, n1, s2 | 将 s 中的 pos1 开始的 n1 个字符与 s2 进行比较 |
| pos1, n1, s2, pos2, n2 | 将 s 中的 pos1 开始的 n1 个字符与 s2 中 pos2 开始的 n2 个字符进行比较 |
| cp | cp 为指针 |
| pos1, n1, cp | pos1, n1 指定了 s 中的位置 |
| pos1, n1, cp, n2 | pos1, n1 指定了 s 中的位置; n2 指定了 cp 的字符数 |
数值转换
int i = 42;
string s = to_string(i);
double d = stod(s);
string s2 = "pi = 3.14";
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
| 参数形式 | 说明 |
|---|---|
| to_string(val) | 返回数值对应的字符串 |
| stoi(s, p, b) stol(s, p, b) stoul(s, p, b) stoll(s, p, b) stoull(s, p, b) |
p 是 size_t 指针,用来保存 s 中第一个非数值字符的下标,默认是0,即函数不保存下表。b 表示转换所用的基数,默认10 |
| stof(s, p) stod(s, p) stold(s, p) |
返回 s 的起始子串的数值,p 作用同上 |
习题9.51
设计一个类,它有三个unsigned成员,分别表示年、月和日。为其编写构造函数,接受一个表示日期的string参数。你的构造函数应该能处理不同数据格式,如January 1,1900、1/1/1990、Jan 1 1900等。
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char** argv){
string dts = "Jan 10 1920";
int format = 0;
int tag = 0;
if (dts.find_first_of(",") < dts.size()){
format = 1;
tag = 1;
} else if (dts.find_first_of("/") < dts.size()){
format = 2;
} else if (dts.find_first_of(" ") < dts.size()){
format = 1;
}
int year, month, day;
switch(format){
case 1:
if (dts.find("Jan") < dts.size()) month = 1;
if (dts.find("Feb") < dts.size()) month = 2;
if (dts.find("Mar") < dts.size()) month = 3;
if (dts.find("Api") < dts.size()) month = 4;
if (dts.find("May") < dts.size()) month = 5;
if (dts.find("Jun") < dts.size()) month = 6;
if (dts.find("Jul") < dts.size()) month = 7;
if (dts.find("Aug") < dts.size()) month = 8;
if (dts.find("Sep") < dts.size()) month = 9;
if (dts.find("Oct") < dts.size()) month = 10;
if (dts.find("Nov") < dts.size()) month = 11;
if (dts.find("Dec") < dts.size()) month = 12;
if (tag == 1) {
int index = dts.find(",");
day = stoi(dts.substr(dts.find(" "), index - dts.find(" ")));
year = stoi(dts.substr(index));
} else {
day = stoi(dts.substr(dts.find(" "), dts.rfind(" ") - dts.find(" ")));
year = stoi(dts.substr(dts.rfind(" ")));
}
break;
case 2:
month = stoi(dts.substr(0, dts.find("/")));
day = stoi(dts.substr(dts.find("/")+1, dts.rfind("/")));
year = stoi(dts.substr(dts.rfind("/")));
break;
default:
year = 0;
month = 0;
day = 0;
}
cout << "year: " << year << ", month: " << month << ", day: " << day << endl;
return 0;
}
容器适配器
adapter,适配器是标准库中的一个通用概念,容器,迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
三个顺序容器适配器:stack, queue 和 priority_queue
定义一个适配器
每个适配器都有两个构造函数:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝该容器来初始化适配器:
stack<int> stk(deq); // 从 deq 拷贝元素到 stk
默认情况下,stack 和 queue 是基于 deque 实现的,priority_queue 是在 vector 之上实现的。在创建一个适配器时将一个顺序容器类型作为第二个类型参数,来重载默认容器类型:
// 在 vector 上实现的空栈
stack<string, vector<string>> str_stk;
// 在 vecotr 上实现,初始化时保存 svec 的拷贝
stack<string, vector<string>> str_stk2(svec);
适配器不能构造在 array 之上。
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 构造
栈适配器(stack)
stack 先进后出
| 操作 | 说明 |
|---|---|
| s.pop() | 删除栈顶元素,但不返回该元素值 |
| s.push(item) s.emplace(args) |
创建一个新元素压栈,该元素通过拷贝或移动 item 而来,或者由 args 构造 |
| s.top() | 返回栈顶元素,但不将元素弹出栈 |
队列适配器(queue 和 priority_queue)
定义在 queue 头文件中
queue 先进先出
| 操作 | 说明 |
|---|---|
| q.pop() | 返回queue 的首元素或 priority_queue 的最高优先级元素,但不删除此元素 |
| q.front() | 返回首元素或尾元素,但不删除此元素 |
| q.back() | 返回最后进入队列的元素,只适用于 queue |
| q.top() | 返回最高优先级元素,但不删除该元素,只适用于 priority_queue |
| q.push(item) q.emplace(args) |
在 queue 末尾或 priority_queue 中恰当的位置创建一个元素,值为 item,或由 args 构建 |
小结
标准库容器是模板类型,用来保存给定类型的对象。顺序容器中的元素是按顺序存放的,通过位置来访问。
所有容器(array除外)都提供搞笑的动态内存管理,可以向容器中添加元素不必担心元素存储的位置。容器负责管理自身的存储,vector 和 string 通过 reserve 和 capacity 提供更细致的内存管理控制。
每个容器都定义了构造函数,添加和删除元素的操作,确定容器大小和返回特定元素的迭代器的操作。其他一些有用的操作比如搜索或排序,并不是由容器定义的,而是由标准库算法实现的。
添加和删除元素时可能会使迭代器,指针或引用失效,如 insert 和 erase

浙公网安备 33010602011771号