一、概念
| 语法 | 解释 |
|---|---|
| vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 |
| deque | 双端队列。支持快速随机访问。在头尾位置插入、删除速度很快 |
| list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入、删除操作速度都很快 |
| forward_list | 单向链表。只支持单向顺序访问。在链表任何位置进行插入、删除操作速度都很快 |
| array | 固定大小数组。支持快速随机访问。不能添加或删除元素 |
| string | 与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入、删除速度快 |
forward_list没有size操作。其它容器有。
通常,使用vector是最好的选择。
二、概览
容器均定义为模版类。
顺序容器几乎可以保存任意类型的元素。
vector<vector<string>> lines; //lines是一个vector,其元素类型是string的vector
vector<noDefault> v1(10, init); //正确,提供了元素初始化器
vector<noDefault> v2(12); //错误:noDefault没有默认构造函数,必须提供一个元素初始化器
2.1 迭代器
迭代器范围[begin,end),分别指向同一个容器中的元素或者是尾元素之后的位置。
end可以与begin指向相同的位置,但不能指向begin之前的位置。
1 |
|
2.2 容器类型成员
在不了解容器中元素类型的情况下。如果需要元素类型,可以使用value_ type。如果需要元素类型的引用,可以使用reference或const_reference。
list<string>::iterator iter;
vector<int>::difference_type count;
2.3 begin和end成员
begin和end操作生成指向容器中第一个元素和尾元素之后位置的迭代器。
1 | lsit<string> a = {"Milton","Shakespeare","Austen"}; |
不以c开头的函数都是被重载过的。一个是const成员,返回容器的const_iterator;另一个是非常量成员,返回容器的iterator类型。rbegin、end和rend情况类似。
以c开头的版本是c++新标准投入的,用以支持auto与begin和end函数结合使用。
1 | //显式指定 |
2.4 容器定义和初始化
| 语法 | 解释 |
|---|---|
| C c | 默认构造函数。如果C是一个array,则c中元素按默认方式初始化,否则为空 |
| C c1(c2) | c1初始化为c2的拷贝。c1和c2必须是相同类型(容器类型相同、保存的元素类型相同;对于array的类型,大小必须相同) |
| C c1=c2 | |
| C c{a,b,c…} | c初始化为初始化列表中元素的拷贝。列表中的元素必须与C的元素类型相容。对于array类型,列表中元素数目必须等于或小于array的大小,任何遗漏的元素都进行值初始化。 |
| C c ={a,b,c…} | |
| C c(b,e) | c初始化为迭代器b和e指定范围中元素的拷贝。范围中元素的类型必须与C的元素类型相容(array不适用)。 |
顺序容器(不包含array)的构造函数
| 语法 | 解释 |
|---|---|
| C seq(n) | seq包含n个元素,这些元素进行值初始化;explicit的。 |
| C seq(n,t) | seq包含n个初始化为值t的元素 |
1 | list<string> authors = {"Milton","Shakespeare","Austen"}; |
当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同
列表初始化
1 | list<string> authors = {"Milton","Shakespeare","Austen"}; |
显式的指定了容器中每个元素的值。除了array之外,初始化列表还隐含地指定了容器的大小:与初始值一样多。
与顺序容器大小相关的构造函数
1 | vector<int> ivec(10,-1); //10个int元素,每个都初始化为-1 |
没有默认构造函数的元素类型,必须显式指定初始值
只有顺序容器的构造函数才接受大小参数
标准库array具有固定大小
定义一个array时,需要指定元素类型和容器大小。
1 | array<int,42> //类型为:保存42个int的数组 |
为了使用array类型,必须同时指定元素类型和大小。
1 | array<int,10>::size_type i; |
array大小固定。一个默认构造的array是非空的:包含了与其大小一样多的元素,这些元素都被默认初始化。如果进行初始化,初始化值的数目必须小于等于array大小。
1 | array<int,10> ival; //10个默认初始化的int |
不能对内置数组类型进行拷贝或对象赋值操作,但array并无此限制。
1 | int digs[10] = {0,1,2,3,4,5,6,7,8,9}; |
2.5 赋值和swap
容器赋值运算
| 语法 | 解释 |
|---|---|
| c1=c2 | 将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同类型 |
| c={a,b,c…} | 将c1种元素替换为初始化列表中元素的拷贝(array不适用) |
| swap(c1,c2) | 交换c1和c2中的元素。c1和c2必须具有相同的类型。swap通常比从c2向c1拷贝元素快得多 |
| c1.swap(c2) |
assign操作不适用于关联容器和array
| 语法 | 解释 |
|---|---|
| seq.assign(b,e) | 将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素 |
| seq.assign(il) | 将c1种元素替换为初始化列表中元素的拷贝(array不适用) |
| seq.assign(n,t) | 将seq中的元素替换为n个值为t的元素 |
赋值运算符要求左边和右边的运算对象具有相同的类型。顺序容器还定义了assign的成员,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。
1 | list<string> names; |
由于旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器。
1 | list<string> slists1(1); |
使用swap,交换两个相同类型容器的内容。
1 | vector<string> svec1(10); |
除array外,交换两个容器内容的操作保证会很快,元素本身并未交换,swap只是交换了两个容器的内部数据结构。
除array外,swap不会对任何元素进行拷贝、删除或插入操作,可在常数时间完成。
除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。但在swap之后,这些元素已经属于不同的容器。
swap两个array会真正交换它们的元素。所需时间与array中元素的数目成正比。
泛型很重要,使用非成员swap。
2.6 容器大小操作
size()、empty()、max_size()。max_size()返回一个大于等于该类型容器所能容纳的最大元素的值。
forward_list支持max_size empty,不支持size。
2.7 关系运算符
每个容器都支持==、!=
除了无序关联容器外的所有容器关系运算符 > >= < <=
左右两边必须是相同类型的容器,必须保存相同类型的元素。
1 | vector<int> v1 = {1,3,5,7,9,12}; |
只有当其元素类型也定义了相应的比较运算符时,才可以使用关系运算符来比较容器。
三、顺序容器操作
3.1 向顺序容器添加元素
操作
这些操作会改变容器的大小;array不支持这些操作
forward list有自己版本的insert emplace,且不支持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_front(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) | 将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素之前。b和e不能指向c中的元素。返回指向新添加的第一个元素的迭代器;若范围为空,则返回p |
使用push_back
除了array和forward_list之外,每个顺序容器(vector list deque string)都支持。
1 | string word; |
当用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝。
使用push_front
list forward_list deque支持。
1 | list<int> ilist; |
deque和vector一样提供了随机访问元素的能力,同时它提供了vector不支持的push_front。
在容器的特定位置添加元素
insert成员允许我们在容器中任意位置插入0个或多个元素。vector deque list string都支持。forward_list提供了特殊版本的insert成员。
insert函数将元素插入到迭代器所指定的位置之前。
slist.insert(iter,"Hello!");
尽管有些容器不支持push_front操作,但它们对于insert操作并无类似限制。
1 | vector<string> svec; |
插入范围内的元素
insert函数还可以接受更多的参数,这与容器构造函数类似。
1 | svec.insert(svec.end(),10,"Anna"); //将10个元素插入到svec的末尾。 |
使用insert的返回值
1 | list<string> lst; |
使用emplace操作
新标准引入了三个新成员:emplace_front emplace emplace_back,这些操作构造而不是拷贝元素。分别对应于push_front insert push_back。
1 | c.emplace_back("978-069",25,15.88); //在c的末尾构造一个Sales_data对象。 |
emplace函数的参数根据与元素类型而变化,参数必须与元素类型的构造函数相匹配。
1 | c.emplace_back(); //使用Sales_data的默认构造函数 |
3.2 访问元素
包括array在内的每个顺序容器都有一个front函数,而除forward_list之外的所有顺序容器都有一个back成员函数。这两个操作分别是首元素和尾元素的引用。
1 | if(!c.empty()) //在解引用一个迭代器或调用front back之前检查是否有元素 |
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] | 返回c中下标为n的元素的引用。如果下标越界,则抛出out_of_range异常 |
对于一个空容器调用front和back,就像使用一个越界的下标一样,是严重的程序设计错误。
访问成员函数返回的是引用
在容器中访问元素的成员函数返回的都是引用。
1 | if(!c.empty()) |
提供快速随机访问的容器(string vector deque array)也都提供下标运算符。给定的下标必须在范围内。
1 | vector<string> svec; |
3.3 删除元素
这些操作会改变容器的大小;array不支持这些操作
forward list有自己版本的erase,且不支持pop back emplace_back
vector和string不支持pop_ front
| 语法 | 解释 |
|---|---|
| c.pop_back() | 删除c中尾元素。若c为空,则函数行为未定义。函数返回void |
| c.pop_front() | 删除c中首元素。若c为空,则函数行为未定义。函数返回void |
| c.erase(p) | 删除迭代器p所指向的元素,返回一个指向被删除元素之后的迭代器,若p指向尾元素,则返回尾后迭代器。若p是尾后迭代器,则函数行为未定义 |
| c.erase(b,e) | 删除迭代器b和e所制定范围内的元素。返回一个指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器。 |
| c.clear() | 删除c中所有元素,函数返回void |
删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string中删除点之外位置的迭代器、引用和指针都会失效。
pop front和pop back成员函数
vector string foward list不支持pop back。
与元素访问成员函数类似,不能对一个空容器执行弹出操作。返回void,如果需要弹出的元素的值,就必须在执行弹出操作之前保存它。
1 | while(!ilist.empty()) |
从容器内部删除一个元素
若j是i之后的元素,那么erase(i)将返回指向j的迭代器
1 | list<int> lst = {0,1,2,3,4,5,6,7,8,9}; |
删除多个元素
1 | elem1 = slist.erase(elem1, elem2); //调用后,elem1 = elem2 |
删除所有内容
1 | slist.clear(); |
3.4 特殊的forward_list操作
forward_list定义了首前迭代器,这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素。
| 语法 | 解释 |
|---|---|
| lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。cbegin_before()返回一个const_iterator |
| lst.cbefore_begin() | |
| lst.insert_after(p,t) | 在迭代器p之后的位置插入元素。t是一个对象,n是数量,b和e是表示范围的一对迭代器(b和e不能指向lst内),il是一个花括号表。返回一个指向最后一个插入元素的迭代器。如果范围为空,则返回p。若p为尾后迭代器,ze函数未定义 |
| lst.insert_after(p,n,t) | |
| lst.insert_after(p,b,e) | |
| lst.insert_after(p,il) | |
| emplace_after(p,args) | 使用args在p指定的位置之后创建一个元素。返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义。 |
| lst.erase_after(p) | 删除p指向的位置之后的元素,或删除从b之后知道(但不包含)e之间的元素。返回一个指向被删元素之后元素的迭代器,若不存在这样的元素,则返回尾后迭代器。如果p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义 |
| lst.erase_after(b,e) |
1 | forward_list<int> lst = {0,1,2,3,4,5,6,7,8,9}; |
3.5 改变容器的大小
resize来增大或缩小容器。array不支持。
1 | list<int> ilist(10,42); //10个int:每个值都是42 |
如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效。
3.6 容器操作可能使迭代器失效
当使用迭代器时,最小化要求迭代器必须保持有效的程序片段是一个好的方法。
由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确的重新定位迭代器。这个建议对vector string deque尤为重要。
1 | vector<int> vi = {0,1,2,3,4,5,6,7,8,9}; |
不要保存end返回的迭代器
1 | auto begin = v.begin(), end=v.end(); |
如果在一个循环中插入 删除deque string或vector中的元素,不要缓存end返回的迭代器。
四、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个元素的内存空间 |
reserve并不改变容器中元素的数量,仅影响vector预先分配多大的内存空间。
只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间。因此,在调用reserve之后,capacity将会大于或等于传递给reserve的参数。
这样,调用reserve永远也不会减少容器占用的内存空间。类似的,resize成员函数只改变容器中元素的数目,而不是容器的容量。
capacity和size
容器的size是指它已经保存的元素的数目;capacity则是在不分配新的内存空间的前提下它最多可以保存多少个元素。
只有当迫不得已时,才可以分配新的内存空间。
五、额外的string操作
5.1 构造string的其他方法
n、len2和pos2都是无符号值
| 语法 | 解释 |
|---|---|
| string s(cp,n) | s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符 |
| string s(s2,pos2) | s是string s2从下标pos2开始的字符的拷贝。若pos2>s2.size(),构造函数行为未定义 |
| string s(s2,pos2,len2) | s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()*-pos2个字符 |
1 | const char *cp = "hello world!!!"; //以空字符串结束的数组 |
用const char*创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符时停止。如果传递了一个计数值,数组可以不以空字符结尾。如果未传递计数值且未以空字符结尾,或给定计数值大于数组大小,则构造函数的行为未定义。
当从一个string拷贝字符时,可以提供一个可选的开始位置和一个计数值,开始位置必须小于或等于给定的string的大小。如果位置大于size,则构造函数抛出out_of_range异常。如果传递了计数值,则从给定位置开始拷贝这么多字符。最多拷贝到string的结尾。
substr操作
| 语法 | 解释 |
|---|---|
| s.substr(pos,n) | 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size()-pos,即拷贝从pos开始的所有字符 |
如果开始位置超过了string的大小,则substr函数抛出一个out_of_range异常。如果开始位置加上计数值大于string的大小,则substr会调整计数值,只拷贝到string的末尾。
1 | string s("hello world"); |
5.2 改变string的其他方法
除了接受迭代器的insert和erase版本外,string还提供接受下标的版本。下标指出了开始删除的位置,或是insert到给定值之前的位置
1 | s.insert(s.size(),5,'!'); //在s末尾插入5个感叹号 |
标准库还提供了接受C风格字符数组的insert和assign版本。
1 | const char *cp = "Stately, plump Buck"; |
还可以指定将来自其他string或子字符串的字符插入到当前string中或赋予当前string
1 | string s = "some string", s2 = "some other string"; |
append和replace函数
append操作是在string末尾进行插入操作。
1 | string s("C++ Primer"),s2=s; |
replace操作是调用erase和insert的一种简写形式。插入的文本不必与删除的文本一样大。
1 | s.erase(11,3); //s=="C++ Primer Ed." |
5.3 string搜索操作
每个搜索操作都返回string::size_type值,匹配发生位置的下标。如果搜索失败,则返回一个string::npos的static成员。标准库将npos定义为const string::size_type类型,并初始化为-1。由于npos是一个unsigned类型,此初始值意味着npos等于任何string最大的可能大小。
find查找参数指定的字符串。1
2
3
4
5string name("AnnaBelle");
auto pos1 = name.find("Anna"); //pos1 == 0
string lowercase("annaBelle");
pos1 = lowercase.find("Anna"); //pos1 == npos
find_first_of查找与给定字符串中任何一个字符匹配的位置。
1 | string numbers("0123456789"),name("r2d2"); |
指定在哪里开始搜索
接受可选的第二个参数,指定从什么位置开始搜索1
2
3
4
5
6string::size_type pos = 0;
while((pos = name.find_first_of(numbers,pos)) != string::npos)
{
cout<<"found number at index: "<<pos<<" element is "<<name[pos]<<endl;
++pos;
}
逆向搜索
rfind成员函数搜索最后一个匹配,即子字符串最靠右的出现位置。
1 | string river("Mississippi"); |
5.4 compare函数
根据s是等于、大于还是小于参数指定的字符串,s.compare返回0 正数 负数
5.5 数值转换
1 | int i = 42; |
如果string不能转换为一个数值,这些函数抛出一个invalid_argument异常。如果转换得到的数值无法用任何类型来表示,则抛出out_of_range异常。
5.6 容器适配器
标准库定义了三个顺序容器适配器:stack queue priority_queue。
容器、迭代器、函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。
stack和queue是基于deque实现的,priority_queue是在vector之上实现的。
1 | stack<int> stk(deq); //deq是一个deque<int> |
所有适配器都要求容器具有添加和删除元素的能力。因此,不能用array forward_list来构造适配器。
stack只要求push_back pop_back back操作,因此可以使用除array forward_list之外的任何容器类型来构造。
queue适配器要求back push_back front push_front,因此构造于list deque之上,但不能基于vector构造。
priority_queue除了front push_back pop_back,还要求随机访问,因此它构造于vector deque之上,但不能基于list。