第9章 顺序容器
容器是指什么?
一个容器指的是一些特定类型对象的集合。
9.1 顺序容器概述
顺序容器,sequential container,提供了控制元素存储何访问顺序的能力,顺序取决于元素加入容器时的位置,与元素值无关。
标准库提供了3种容器适配器,分别为容器操作定义了不同的接口,来与容器适配。
顺序容器类型
vector | 可变大小数组,随机访问快,一般在尾部插入/删除元素。在外部之外的插入/删除操作可能很慢 |
deque | 双端队列,随机访问快,头尾插入/删除快 |
list | 双向链表,只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快 |
forward_list | 单向链表,只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快 |
array | 固定大小数组,支持快速随机访问。不能添加或删除元素 |
string | 与vector相似的容器,但专门用于保存字符。 |
确定使用哪种顺序容器
通常,选择使用vector,除非有明确地使用其他容器的理由。
如果不确定使用哪种容器,可以只使用vector,list的公共操作:使用迭代器,不要用下标进行操作,避免随机访问。
容器选择基本原则:
- 通常选择vector,除非有很好的理由使用其他容器。
- 如果程序有很多小元素,且空间额外开销很重要,就不要使用list或者forward_list。
- 如果程序要求随机访问元素,使用vector或deque。
- 如果程序要求在容器中间插入/删除元素,使用list, forward_list。
- 如果程序要求在头尾位置插入/删除元素,但不会在中间插入/删除元素,使用deque。
- 如果程序只有在读取输入时,才需要在容器中间位置插入元素,随后需要随机访问元素,则
- 如果需要在容器中间位置添加元素,可以先用vector追加数据,然后调用sort进行排序。
- 可以考虑输入阶段用list,输入完成后将list拷贝到一个vector。
9.2 容器库概述
容器类型有3类操作:
- 所有容器都提供的;
- 仅针对顺序容器、关联容器、无序容器的;
- 只适用于小部分容器的;
本节介绍第1类 适用于所有容器的。
容器保存元素类型限制
顺序容器几乎可以保存任意类型元素,某些容器操作对元素类型有自己特殊要求。
例如,vector可以保存vector类型元素
vector<vector<string>> lines; // 老版本编译器可能要求2个尖括号之间有空格,即vector<vector<string> >
顺序容器构造的一个函数版本,接受一个容器大小参数,使用了元素默认构造函数,但又的类型没有默认构造函数。定义保存这种类型对象容器时,不能只传递一个元素数目参数。
// 假定noDefault是一个没有默认构造函数的类型
vector<noDefault> v1(10, init); // 正确,init是元素初始化器
vector<noDefault> v2(10); // 错误,因为noDefault没有默认构造函数,必须提供一个元素初始化器
容器操作
类型别名
iterator 容器类型的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整型,保存容器大小
difference_type 带符号整型,2个迭代器之间的距离
value_type 元素类型
const_reference 元素的const左值类型(const value_type&)
构造函数
C c; 默认构造函数,构造空容器
C c1(c2); 构造c2的拷贝c1
C c(b,e); 构造c,将迭代器b和e指定范围内的元素拷贝到e(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) <=>a.swap(b)
大小
c.size() c中元素数目(forward_list不支持)
c.max_size() c可保存的最大元素数目
c.empty() 判断c中是否存储了元素。c为空,返回false;否则,返回true
添加/删除元素(不适用于array,不同容器中,这些操作的接口不同)
c.insert(args); 将args中元素拷贝进c
c.emplace(inits); 使用inits构造c中的一个元素
c.erase(args); 删除args指定的元素
c.clear(); 删除c中的所有元素,返回void
关系运算符
== , != 所有容器都支持相等,不相等运算符
<, <=, >, >= 关系运算符(无序关联容器不支持)
获取迭代器
c.begin(), c.end() 返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(), c.cend() 返回const_iterator
反向容器的额外成员(不支持forward_list)
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(), c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() 返回const_reverse_iterator
9.2.1 迭代器
迭代器是访问容器元素的对象,俗称光标(cursor),提供遍历访问容器元素的接口,而不用关心容器对象的内存分配细节。
C++迭代器有如下特点:
- 如果一个迭代器提供某个操作,那么所有提供相同操作的实现方式都相同。例如,标准容器类型提供容器元素访问操作,而所有迭代器都是通过解引用运算符实现该操作。
- forward_list迭代器不支持递减运算符(--)。
迭代器范围
一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或尾元素之后的位置。这两个迭代器通常称为begin, end,或者first, last。
不管end还是last,指向尾元素下一位置,而非容器最后一个元素,即元素位置范围:左闭合区间[begin, end)。
注意:编译器不会强制要求begin和end的合理位置,需要程序员保证。
使用左闭合范围蕴含的编程假定
// 利用循环+迭代器,遍历容器元素
while(begin != end) {
*begin = val; // 通过解引用方式,访问begin指向的元素并赋值
++begin; // 移动迭代器到下一位置
}
9.2.2 容器类型成员
size_type, iterator, const_iterator,
反向迭代器:提供反向遍历容器的迭代器;
类型别名:value_type 表示元素类型,reference或const_reference表示元素类型的一个引用,在泛型编程中会比较有用(第16章介绍);
// iter是通过list<string>定义的一个迭代器类型
list<string>::iterator iter;
// count是通过vector<int>定义的difference_type类型
vector<int>::difference_type count;
9.2.3 begin和end成员
begin,end生成指向容器的第一个元素和尾元素之后位置的迭代器。常用于包含容器所有元素的迭代器范围。
begin,end多个版本:
普通版本,不带任何前缀,返回普通迭代器(常用);
带r版本返回反向迭代器;
带c版本返回const迭代器; (用以支持auto)
list<string> a = {"Milton", "Sharkspeare", "Austen"};
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 = a.crbegin(); // list<string>::const_reverse_iterator
注意:当不需要写访问时,应该使用cbegin和cend。
9.2.4 容器定义和初始化
除array外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
两个容器的元素类型必须相同或者可以类型转换,才能用一个容器初始化另外一个。
C c; // 默认构造函数。如果C是array,则c中元素按默认方式初始化;否则c为空
C c1(c2); // c1初始化为c2的拷贝。c1和c2必须是相同类型(容器类型相同 and 元素类型相同)。如果C是array,c1和c2大小必须相同
C c1 = c2; // <=> C c1(c2)
C c{a,b,c...}; // c初始化为初始化列表中元素的拷贝。列表元素类型必须与c的元素类型相同
C c = {a,b,c...}; // <=> C c{a,b,c...}
C c(b,e); // c初始化为迭代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与C的元素类型相容(不适用于array)。要拷贝的元素包括b指向的元素,但不包括e指向的元素。
// 只有顺序容器(除array外,因为array有专门指定大小的方法)的构造函数才能接受大小参数
C seq(n); // seq包含n个元素,对这些元素进行值初始化;此构造函数是explicit的。(不适用于string)
C seq(n,t); // seq包含n个初始化为值t的元素
将一个容器初始化为另一个容器的拷贝
2种方法:
- 直接拷贝整个容器;
- 拷贝由迭代器对指定的元素范围(array除外);
第1种方法,要求2个容器类型 + 元素类型必须匹配。第2种方法,不要钱容器类型相同,元素类型也不一定相同,但是要能将元素转换为要初始化的容器的元素类型。
list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};
list<string> list2(authors); // 正确:类型匹配
deque<string> authlist(authors); // 错误:容器类型不匹配
vector<string> words(articles); // 错误:容器类型不匹配
forward_list<string> words(articles.begin(), articles.end()); // 正确:参数是迭代器对,元素类型可以转换为要初始化的类型即可。const char* 可以转换为string
// 注意:要拷贝的元素包括articles.begin()指向的元素,但不包括articles.end()指向的元素
列表初始化
- 列表初始化指定容器每个元素值
- 指示了容器大小
list<string> authors = {"Milton", "Shakespeare", "Austen"};
vector<const char*> articles = {"a", "an", "the"};
与容器大小相关的构造函数
除上面提到与关联容器相同的构造函数外,顺序容器(除array外)还提供另一个构造函数,指定容器大小和初值(可省略,前提是元素类型有默认的初始化方式或者默认构造函数)。
vector<int> ivec(10, -1); // 10个int元素,每个初值-1
list<string> svec(10, "hi!"); // 10个string元素,每个初值"hi!"
forward_list<int> ivec(10); // 10个int元素,每个初值0
deque<string> svec(10); // 10个string元素,每个初值为空
标准库array具有固定大小
大小是array类型的一部分,所以array不支持普通的容器构造函数,这些构造函数都会确定容器的大小,或隐式或显式。注意区分与其他顺序容器,在指定容器大小方式上的区别。
// array类型
array<int, 42> // 类型为:保存42个int的数组
array<string, 10> // 类型为:保存10个string的数组
// 使用array类型,需要同时指定元素类型和数组大小
array<int, 10>::size_type i; // 数组类型要包括元素类型和大小
array<int>::size_type j;; / 错误:因为array<int>不是一个类型
// 定义array对象
array<int, 10> ia1; // 10个默认初始化(为0)的int
array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8}; // 列表初始化
array<int, 10> ia3 = {42}; // ia3[0] 为42,剩余元素为0
内置数组与标准库容器数组array
不能对内置数组进行拷贝或对象赋值,但array均可以
int digs[10] = {0,1,2,3,4,5,6,7,8};
int cpy[10] = digs; // 错误:内置数组不能拷贝或赋值
array<int, 10> digitis = {0,1,2,3,4,5,6,7,8};
array<int, 10> copy = digits; // 正确:array类型匹配,即可进行拷贝
9.2.5 赋值和swap
除array外,容器允许直接拷贝或赋值。array对象间赋值,如果2个array大小不相等,赋值后,= 左边array大小变为右边array大小。
c1 = c2; // c1, c2是除array外的容器对象
c1 = {a,b,c}; // c1是除array外的容器对象
array<int, 10> a1 = {0,1,2,3,4,5,6,7,8};
array<int, 10> a2 = {0}; // 10个元素均为0
a1 = a2; // 正确:用a2元素替换a1元素,拷贝后a1大小由10变为1
a2 = {0}; // 错误,不能将花括号列表赋值给array , 只有在初始化时才可以
容器赋值运算
c1 = c2;
c = {a,b,c,...}; // c不能是array
swap(c1, c2); // 交换c1和c2的元素。 要求c1, c2具有相同的类型。swap通常比拷贝元素(c1 = c2)快
// assign操作不适用于关联容器和array
seq.assign(b, e); // 将seq元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq的元素
seq.assign(il); // 将seq元素替换为初始化列表il中的元素
seq.assign(n, t); // 将seq中的元素替换为n个值为t的元素
注意:赋值运算会导致指向左边容器内部迭代器、引用和指针失效。而swap操作将容器内容交换,不会导致指向容器的迭代器、引用和指针失效(除容器类型为array和string外)
使用assign(仅顺序容器)
assign要求容器的元素类型相容即可,类似于用一个容器的迭代器对初始化另外一个容器。
assign第二个版本,接受一个整型值和一个元素值。用指定数目的相同元素值,替换容器中原有元素。
// assign支持容器类型不匹配,但是元素类型相容的赋值
list<string> name;
vector<const char*> oldstyle;
names = oldstyle; // 错误:容器类型不匹配,不能直接拷贝
names.assign(oldstyle.begin(), oldstyle.end); // 正确:可以将const char* 转换为string,也就是说元素类型相容
// assign支持用指定数目元素值,替换容器内容
list<string> slist1(1); // 1个元素,空string
slist1.assign(10, "Hiya!"); // 10个元素,每个都是"Hiya!"
// <=> 等价于下面的语句
slist1.clear();
slist.insert(slist1.begin(), 10, "Hiya!");
使用swap
交换2个相同容器类型的元素。除array外,swap不对任何元素进行拷贝、删除或插入,可以保证在常数时间内完成。个人理解,可以简单成,交换的只是2个容器的指针/引用,没有发生元素交换。
而对array,swap操作会发生真正的元素交换,交换时间与array元素个数成正比。
// 交换svec1和svec2,交换后svec1大小为24,svec2大小为10
vector<string> svec1(10); // 10个元素的vector
vector<string> svec2(24); // 24个元素的vector
swap(svec1, svec2);
9.2.6 容器大小操作
- size(), 返回容器元素数目,除forward_list外的容器都支持;
- empty(), size为0是返回true,否则返回false;
- max_size(), 容器所能容纳的最大元素的值;
9.2.7 关系运算符
按顺序逐个比较元素。
== 所有元素都相等(容器大小相同)
< 能比较的元素都相等,但左边容器小于右边;或者,左边不等于右边的第一个元素,小于右边对应位置元素
类同"<"
注意:容器的关系运算,需要元素支持对应的运算符
vector<int> v1 = {1,3,5,7,9};
vector<int> v2 = {1,3,9};
vector<int> v3 = {1,3,5,7};
vector<int> v4 = {1,3,5,7,9};
v1 < v2 // true: 因为v1[2] = 5 < v[2] = 9
v1 < v3 // false: 所有元素都相等,但v3元素个数更少
v1 == v4 // true: 所有元素都相等,且元素个数相等
v1 == v2 // false: 元素数目不一样,肯定不相等
9.3 顺序容器操作
9.3.1 向顺序容器添加元素
注意:向一个vector, string或deque插入元素,会使所有指向容器的迭代器、引用和指针失效。
// 操作会改变容器大小;array不支持
forward_list 有自己专有版本的insert和emplace;
forward_list不支持push_back和emplace_back;
vector和string不支持push_front和emplace_front;
c.push_back(t); // 在c尾部创建一个值为t或由args创建的元素。返回void
c.emplace_back(args);
c.push_front(t); // 在c头部创建一个值为t或由args创建的元素。返回void
c.emplace_back(args);
c.insert(p, t); // 在迭代器p指向的元素之前创建一个值为t或由args创建的元素。返回指向新添加的元素的迭代器
c.emplace(p,args);
c.insert(p, n, t); // 在迭代器p指向的元素之前插入n个值为t的元素。返回指向新添加的第一个元素的迭代器;若n为0,则返回p
c.insert(p, b, e); // 将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素之前。b和e不能指向c中的元素。返回指向新添加的第一个元素的迭代器;若范围为空,则返回p
c.insert(p, il); // il是一个花括号元素值列表,插入到迭代器p指向元素位置之前。返回第一个新添加的元素的迭代器。若列表为空,则返回p
使用push_back
除array和forward_list外,其他顺序容器都支持push_back,将元素插入到容器末尾。
array和forward_list不支持push_back,因为array是固定大小,而forward_list是单向链表,插入元素到末尾需要遍历整个链表。
注意:通过push_back插入到容器的元素是拷贝,而不是对象本身。也就是说,对容器内容的修改,不会影响用来初始化容器元素的对象。
例,在vector末尾追加string
vector<string> container;
string word;
while(cin >> word) {
container.push_back(word);
}
push_front
push_front将元素插入到容器开头,用法类似于push_back。
vector不支持push_front;list, forward_list, deque支持push_front
在容器特定位置添加元素
insert提供一般的添加功能,可以指定在容器中插入位置。
vector, deque, list, string都支持insert。forward_list提供特殊版本的insert
vector<string> svec;
list<string> slist;
slist.insert(slist.begin(), "Hello!"); // 将元素插入到指定位置之前 <=> slist.push_front("Hello!");
// vector不支持push_front,但可以insert到begin()
svec.insert(svec.begin(), "Hello!"); // 插入到vector末尾以外位置,速度可能很慢
插入范围内元素
注意:要插入的范围不能是容器自身迭代器范围
// 1. 插入多个相同元素
svec.insert(svec.end(), 10, "Anna"); // 将10个string元素"Anna"插入到svec末尾
vector<string> v = {"quasi", "simba", "frollo", "scar"};
// 2. 插入另外一个容器迭代器对指定范围元素
slist.insert(slist.begin(), v.end() - 2, v.end()); // 将容器v最后2个元素插入到slist开头
// 3. 插入元素列表
slist.insert(slist.end(), {"these", "words", "will", "go", "at", "the", "end"}); // 将花括号括起来的元素列表插入到slist末尾
使用insert返回值
insert返回指向第一个新加入元素的迭代器。
list<string> lst;
auto iter = lst.begin();
while (cin >> word) {
iter = lst.insert(iter, word); // <=> lst.push_front(word);
}
使用emplace操作
emplace_front, emplace, emplace_back用于构造而非拷贝元素,插入元素位置分别与push_front, insert, push_back相同。
何为构造元素?
传入emplace的参数,会被直接当作容器元素的构造参数,用来构造新元素。
class Sales_data{
public:
Sales_data(string &id, double price) {
this->id = id;
this->price = price;
}
~Sales_data();
private:
string id;
double price;
}
int main() {
vector<Sales_data> vec;
vec.push_back(new Sales_data("123-456789", 12.5));
vec.emplace_back("123-456888", 15.6); // 等价于上面的push_back语句
return 0;
}
9.3.2 访问元素
注意front, back和begin, end的区别,前者得到的是容器元素的首元素和末尾元素,要求容器不能为空;后者得到的是容器的迭代器,begin指向首元素位置,end指向最后一个元素的下一个位置。
// at和下标操作只适用于string, vector, deque, array. back不适用于forward_list
c.back(); // 返回c中尾元素的引用。若c为空,函数行为未定义
c.front(); // 返回c中首元素的引用。若c为空,函数行为未定义
c[n]; // 返回c中下标n的元素的引用,n是无符号整数。若n >= c.size(),函数行为未定义
c.at(n); // 返回下标为n的元素的引用。如果下标越界,则抛出out_of_range异常
安全访问的随机访问
访问容器元素时,一定要先确保存在,否则可能导致未定义行为。
如下面的例子,
vector<string> svec; // 空vector
cout << svec[0]; // 运行时错误:svec为空,没有元素
cout << svec.at(0); // 下标越界,抛出out_of_range异常
9.3.3 删除元素
顺序容器 的删除操作。
注意:删除元素的成员函数并不检查参数,删除元素之前,程序员必须确保它们(待删除元素)存在。
// 操作会改变容器大小,不适用于array
// forward_list有特殊版本erase
// forward_list不支持pop_back; vector, string不支持pop_front
c.pop_back(); // 删除c尾元素。若c为空,函数未定义,返回void
c.pop_front(); // 删除c首元素。...
c.erase(p); // 删除迭代器p所指元素,返回删除元素后的元素的迭代器。
c.clear(); // 删除c所有元素。返回void
pop_front和pop_back成员函数
pop_front , pop_back分别删除首元素,尾元素。
vector, string不支持push_front, pop_front。forward_list不支持pop_back。
不能对一个空容器,执行弹出操作
while (!ilist.empty()) {
process(ilist.front()); // 对ilist首元素进行自定义处理
ilist.pop_front(); // 删除首元素
}
从容器内部删除一个元素
erase从容器指定位置或位置范围删除元素。用法类似于insert。
// 1. 删除指定位置元素
list<int> lst = {0,1,2,3,4,5,6};
auto it = lst.begin();
while(it != lst.end()) {
if (*it % 2) {
it = lst.erase(it); // 删除指定位置元素,返回删除元素下一位置
}
else ++it;
}
// 2. 删除指定范围元素
elem1 = lst.erase(lst.begin(), lst.end()); // 调用后elem1 == lst.end()
// 3. 删除所有元素
lst.clear();
9.3.4 特殊的forward_list操作
lst.before_begin(); // 返回指向链表首元素之前不存在的元素的迭代器。该迭代器不能解引用
lst.cbefore_begin(); // 同before_begin,不过得到的是const_iterator
// 注意:插入元素的迭代器,不能是待插入容器的迭代器
lst.insert_after(p, t); // 在迭代器p之后的位置插入元素。t是一个对象,n是数量,b, e是表示范围的一对迭代器
lst.insert_after(p, n, t); // p之后,插入n个重复的t
lst.insert_after(p, b, e); // p之后插入迭代器对[b, e)之间的元素
lst.insert_after(p, il); // il是花括号列表
emplace_after(p, args); // p之后用args构造的对象元素
lst.erase_after(p); // 删除p指向的位置之后的元素
lst.erase_after(b, e); // 删除迭代器对(b, e)的元素,返回被删除元素之后后的元素的迭代器
9.3.5 改变容器大小
resize用来增大或缩小容器。array不支持resize
如果当前size < 增大后的size,则新元素添加到容器后部;
如果当前size > 增大后的siez,则容器后部元素删除;
list<int> ilist(10, 42); // 10个int,值都是42
ilist.resize(15); // 增加5个int到ilist末尾,值为0
ilist.resize(25, -1); // 增加10个int到ilist末尾,值-1
ilist.resize(5); // 减少到5个int,也就是删除末尾20个元素
9.4 vector操作是如何增长的
原则:只有迫不得已的时候,才可以分配新的内存空间。
每个vector实现可能不一样,不过,都是在分配新内存时,分配足够内存空间用于存储,而不是一个一个元素增加存储空间。
管理容量的成员函数
// 容器大小管理操作
// shrink_to_fit只适用于vector/string和deque
// capacity和reserve只适用于vector和string
c.shrink_to_fit(); // 将capacity()减少为与size()相同大小
c.capacity(); // 不重新分配内存空间的话,c可以保存多少元素
c.reserve(n); // 分配至少能容纳n个元素的内存空间
capacity和size
size, 容器已经保存的元素的数目;
capacity, 在不分配新的内存空间的前提下它最多可以保存多少元素;
vector<int> ivec;
cout << "ivec: size: " << ivec.size()
<< " capacity: " << ivec.capacity() << endl;
for (vector<int>::size_type ix = 0; ix != 24; ++ix) {
ivec.push_back(ix);
}
// size为24,capacity应该 >= 24,具体是多少取决于具体的标准库实现
cout << "ivec: size: " << ivec.size()
<< " capacity: " << ivec.capacity() << endl;
ivec.reserve(50); // 会让capacity至少为50
输出
ivec: size: 0 capacity: 0
ivec: size: 24 capacity: 32
9.5 额外的string操作
// 除了字符串字面量构造string外,构造string的其他方法
// n, len2, pos2都是无符号值
string s(cp, n); // s是cp指向的数组中前n个字符的拷贝,此数组至少应该包含n个字符
string s(s2, pos2); // s是string s2从下标pos2开始的字符的拷贝。若pos2 > s2.size(),该构造函数行为未定义
sring s(s2, pos2, len2); // s是string s2从下标pos2开始len2个字符的拷贝。...
substr操作
求子字符串。
如果开始位置超过string大小,抛出out_of_range异常。
string s("hello world"); // 11个元素(字符)
string s2 = s.substr(0, 5); // s2 = hello = s[0..5), s[0]开始5个字符
string s3 = s.substr(6); // s3 = world = s[6..end)
string s4 = s.substr(6, 11); // s4 = world = s[6..end), s[0]开始11个字符
string s5 = s.substr(12); // 12 > 11,抛出out_of_range异常
9.5.2 改变string的其他方法
string支持顺序容器的赋值运算,assign, insert和erase操作。
其中,insert, erase除了接受迭代器参数的版本,还有接受下标的版本。insert, assign还有接受C风格字符串版本。
// 接受下标版本insert,erase
s.insert(s.size(), 5, '!'); // 在s末尾插入5个感叹号
s.erase(s.size()-5, 5); // 从s删除末尾5个字符
// 接受C风格字符串版本insert, assign
const char *cp = "Stately, plump Buck";
s.assign(cp, 7); // s == "Stately"
s.insert(s.size(), cp + 7); // s == "Stately, plump Buck", s末尾插入cp[7]到cp末尾字符( )
append和replace函数
append,在string末尾追加新元素;
replace,在指定位置删除指定长度的元素;
assign,替换string的所有内容;
// append
string s("C++ Primer"), s2 = 5;
s.append(" 4th Ed.");
// <=>
s.insert(s.size(), " 4th Ed."); // s == "C++ Primer 4th Ed."
// replace
s2.replace(11, 3, "5th"); // 将s2[11]开始3个字符替换为"5th"
// <=>
s2.erase(11, 3);
s2.insert(11, "5th"); // s == "C++ Primer 5th Ed."
// 替换的文本内容不一定跟删除的文本内容一样长
s2.replace(11, 3, "Fifth"); // s == "C++ Primer Fifth Ed."
9.5.3 string搜索操作
搜索成功返回string::size_type值,表示匹配发生位置的下标。搜索失败,则返回名为string::npos的static成员(-1)。
注意:string位置下标用size_type表示,用int表示不是很适合。
string name("AnnaBelle");
auto pos1 = name.find("Anna"); // pos1 == 0
// 查找失败,返回npos
string lowercase("annabelle");
pos1 = lowercase.find("Anna"); // pos1 == npos
// 查找给定字符串中任何一个字符匹配的位置
string numbers("0123456789"), name("r2d2");
auto pos = name.find_first_of(numbers); // 在name中,查找numbers任意一字符出现在name中的第一个位置
string dept("03714p3");
auto pos = dept.find_first_not_of(numbers); // dept中查找第一个不在numbers中的字符的位置
string搜索操作
指定从某个位置开始,查找字符/字符串出现的第一个/最后一个位置,或者不出现的位置。
// 搜索操作返回指定字符出现的下标,如果未找到则返回npos
s.find(args); // 查找s中args第一次出现的位置
s.rfind(args); // 查找s中args最后一次出现的位置
s.find_first_of(args); // 在s中查找args中任一字符第一次出现的位置
s.find_last_of(args); // 在s中查找args中任何一个字符最后一次出现的位置
s.find_frist_not_of(args); // 在s中查找第一个不在args中的字符
s.find_last_not_of(args); // 在s中查找最后一个不在args中的字符
args 必须是以下形式之一
c, pos // 从s中位置pos开始查找字符c。pos默认为0
s2, pos // ...查找字符串s2.pos默认为0
cp, pos // ...查找指针cp指向的以空字符结尾的C风格字符串。pos默认0
cp, pos, n // ...查找cp指向的数组的前n个字符。pos和n无默认值
9.5.4 compare函数
类似于C标准库strcmp函数,用来比较当前string与指定string对象的大小。可以指定字符串的起始位置、字符数。
也支持string与C风格字符串进行比较。
string s1, s2;
s1.compare(s2);
s1.compare(pos1, n1, s2); // 比较s[pos1..pos1 + n1 - 1]与s2
s1.compare(pos1, n1, s2, pos2, n2); // 比较s[pos1..pos1 + n1 - 1]与s2[pos2..pos2 + n2 - 1]
数值转换
类似于Java中的Integer.parseInt("123"),C++也提供了将string转换成数值类型(int/double等)的库函数,也提供了数值转换为string转换函数。
to_string, 数值 => string
stod,string => double
stoi,string => int
应用示例,
int i = 42;
string s = to_string(i); // 将整数i转换为字符表示形式
double d = stod(s); // 将字符串s转换为浮点数
*** string和数值之间的转换***
to_string(val); // 任意算术类型(int/short/long/double/float) => string
/* p是size_t指针,用来保存s中第一个非数值字符的下标,默认为0,即函数不报存下标。b表示转换所用的基数,默认10 */
stoi(s, p, b); // string => int
stol(s, p, b); // string => long
stoul(s, p, b); // string => unsigned long
stoll(s, p, b); // string => long long
stoull(s, p, b); // string => unsigned long long
stof(s, p); // string => float
stod(s, p); // string => double
stold(s, p); // string => long double
9.6 容器适配器
标准库定义了3个顺序容器适配器:stack, queue, priority_queue。
适配器(adaptor)是标准库一个通用概念,容器、迭代器和函数都有迭代器。
// 所有容器适配器都支持的操作和类型
size_type 一种类型,足以保存当前类型的最大对象的大小
value_type 元素类型
container_type 实现适配器的底层容器类型
A a; 创建一个名为a的空适配器
A a(c); 创建名为a适配器,带有容器c的拷贝
关系运算符==, !=, <, <=, >, >= 每个适配器都支持的
a.empty(); 判断容器是否为空
a.size(); 返回a中元素数目
swap(a, b); 交换a和b的内容,a,b必须具有相同类型,底层容器类型也必须相同
a.swap(b);
定义一个适配器
对于适配器能建造在哪些容器之上,是有规定的。不过,所有的适配器都要求容器有添加和删除元素、访问尾元素的能力,因此适配器不能建造在array和forward_list上。
默认情况下,stack和queue是基于deque实现的,priority_queue是在vector基础上实现的。
stack可以构造于除array和forward_list之外的任何容器类型;
queue可以构造于list/deque之上;
deque可以构造于vector/deque之上,但不能基于list;
// 定义一个stack
deque<int> deq; // deq是一个deque(双端队列)
stack<int> stk(deq); // 从deq拷贝元素到stk
// 在vector基础上实现空栈
stack<string, vector<string>> str_stk;
// str_stk2在vector基础上构造,初始化时保存svec的拷贝
stack<string, vector<string>> str_stk2(svec); // svec是vector<string> 容器类型对象
// 栈适配器的用法
stack<int> intStack;
// 填满栈
for (size_t ix = 0; ix != 10; ++ix)
intStack.push(ix);
while (!intStack.empty()) {
int value = intStack.top();
// 使用栈顶值的代码
intStack.pop(); // 弹出栈顶值
}
栈操作
// 栈默认基于deque,也可以构建在list/vector之上
s.pop(); //注意跟vector区别,没有_back, _front后缀
s.push(item);
s.emplace(args);
s.top();
队列操作
// 队列默认基于deque,也可以基于list/vector
// priority_queue默认基于vector,也可以基于deque
q.pop(); // 返回queue首元素,或priority_queue最高优先级元素
q.front();
q.back(); // 只适用于queue
q.top(); // 返回最高优先级元素,不删除。只适用于priority_queue
q.push(item);
q.emplace(args);