顺序容器类型:
vector 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快
list 双向列表。只支持双向顺序访问。在 list 中任何位置进行插入/删除操作速度都很快
forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作都很快
array 固定大小数组。支持快速随机访问。不能添加或删除元素
string 与 vector 相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度很快
容器操作(后面再提具体每个容器操作对元素的其他限制):
类型别名
iterator 此容器的迭代器类型
const_iterator 可以读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型,与 value_type& 含义相同
const_reference 元素的 const 左值类型,即 const value_type&
构造函数
C c; 默认构造函数,构造空容器
C c1(c2); 构造 c2 的拷贝 c1
C c(b, e); 构造 c,将迭代器 b 到 e 指定的范围内的元素拷贝到 c(array 不支持)
C c{a, b, c, d...} 列表初始化 c
赋值与 swap
c1 = c2; 将 c1 中的元素替换为 c2 中的元素
c1 = {a, b, c, d...} 将 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 中存储了元素,返回 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
虽然我们可以在容器中保存几乎任何类型,但某些容器操作对元素类型有自己的特殊要求。我们可以为不支持特定操作需求的类型定义容器,但这种情况下就只能使用那些没有特殊要求的容器操作了:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 class no_default{//no_default 是一个没有默认构造函数的类型 6 int x; 7 public: 8 no_default(int a) : x(a){} 9 ~no_default(){} 10 }; 11 12 int main(void){ 13 int init = 1; 14 std::vector<no_default> v1(10, init);//正确,提供了元素初始化器(执行结果是v1中有10个1) 15 // std::vector<no_default> v2(10);//错误,必须提供一个元素初始化器 16 return 0; 17 }
begin 和 end 成员:
1 #include <iostream> 2 #include <list> 3 using namespace std; 4 5 int main(void){ 6 list<string> a = {"Milton", "Shakespeare", "Austen"}; 7 auto it1 = a.begin(); //list<string>::iterator 8 auto it2 = a.rbegin();//list<string>::reverse_iterator 9 auto it3 = a.cbegin();//list<string>::const_iterator 10 auto it4 = a.crbegin();//list<string>::const_reverse_iterator 11 12 cout << *it1 << endl;//Milton 13 it1++; 14 cout << *it1 << endl;//Shakespeare 15 *it1 = "jflsfk"; 16 cout << *it1 << endl;//jflsfk 17 cout << endl; 18 19 cout << *it2 << endl;//Austen 20 it2++; 21 cout << *it2 << endl;//jflsfk 22 *it2 = "Shakespeare"; 23 cout << *it2 << endl;//Shakespeare 24 cout << endl; 25 26 cout << *it3 << endl;//Milton 27 it3++; 28 cout << *it3 << endl;//Shakespeare 29 // *it3 = "hello";//错误,常量迭代器类似于具有底层const的指针 30 cout << endl; 31 32 cout << *it4 << endl;//Austen 33 it4++; 34 cout << *it4 << endl;//Shakespeare 35 // *it4 = "hello";//错误,常量迭代器类似于具有底层const的指针 36 cout << endl; 37 38 const list<string> b = {"hello", "gg", "yy"}; 39 auto it5 = b.begin(); 40 auto it6 = b.rbegin(); 41 auto it7 = b.cbegin(); 42 auto it8 = b.crbegin(); 43 44 cout << *it5 << endl;//hello 45 it5++; 46 cout << *it5 << endl;//gg 47 // *it5 = "aa";//错误,const对象的begin()返回的是一个const_iterator 48 49 // b = a;//错误 50 51 list<string> const c = {"hello", "gg", "yy"};//可以将list<string>看作int,那么b和c分别相当于const int 和 int const,是等价的,都是顶层const 52 53 // c = a;//错误 54 55 return 0; 56 }
通过上面的列子还可以发现不以 c 开头的函数都是被重载过的。也就是说,实际上有两个名为 begin 的成员。一个是 const 成员,返回容器的 const_iterator 类型。另一个是非常量成员,返回容器的 iterator 类型。rbegin,end,rend 的情况类型。当我们对一个非常量成员调用这些成员时,返回的是 const 版本。只有在对一个 const 对象调用这些函数时,才会得到一个 const 版本。与 const 指针和引用类似,可以将一个普通的 iterator 转换成对应的 const_iterator,反之则不行。
而以 c 开头版本是 c++11 标准引入的,用以支持 auto 与 begin 和 end 函数结合使用。无论对常量容器还是非常量容器,其返回的都是 const_iterator。
即 auto 与 begin 或 end 结合时,获得的迭代器类型依赖于容器类型,而以 c 开头的版本是可以获得 const_iterator 的,无论容器的类型是否为 const。
将一个容器初始化为另一个容器的拷贝:
1 #include <iostream> 2 #include <list> 3 #include <vector> 4 #include <deque> 5 #include <forward_list> 6 using namespace std; 7 8 int main(void){ 9 list<string> authors = {"Milton", "Shakespeare", "Austen"};//列表初始化 10 vector<const char*> gel = {"a", "an", "the"};//列表初始化 11 12 list<string> list1(authors);//正确,类型都匹配 13 // deque<string> a(authors);//错误,容器类型不匹配 14 // vector<string> v(gel);//错误,元素类型不匹配 15 16 forward_list<string> f(gel.begin(), gel.end());//正确,只元素能相互转化即可 17 18 return 0; 19 }
为了创建一个容器为另一个容器的拷贝,两个容器的类型及其元素类型必须匹配。不过,当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。而且,新容器和原容器中的元素也可以不同,只要能将拷贝的元素转换为要初始化的容器元素类型即可。
标准库 array:
1 #include <iostream> 2 #include <array> 3 using namespace std; 4 5 int main(void){ 6 array<int, 10> a;//类型为保存10个int的数组 7 array<string, 10> b;//类型为保存10个string的数组 8 9 array<int, 10>::size_type i;//数组类型包括元素类型和大小 10 // array<int>::size_type j;//错误,array<int>不是一个类型 11 12 array<int, 3> c = {1, 2, 3};//列表初始化 13 array<int, 5> d = {1};//d[0]为1,剩余元素都为0 14 //这两点和内置数组是一样的 15 16 int e[5] = {1, 2, 3, 4, 5}; 17 // int cpy[5] = e;//错误,内置数组不支持拷贝或赋值 18 array<int, 5> f = {1, 2, 3, 4, 5}; 19 array<int, 5> copy = f;//正确,只要数组类型匹配即合法 20 // copy = e;//错误,内置数组不能被赋值给array 21 // array<int, 4> g = f;//错误,数组大小不一致 22 23 return 0; 24 }
定义以及使用 array 类型时必须指定容器的元素类型和大小
数组类型包含元素的类型和大小,因此拷贝初始化时不仅要求初始值的类型必须与要创建的容器相同,大小也要相同
array 的列表初始化规则和内置数组一致
assign:
1 #include <iostream> 2 #include <list> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 list<string> names; 8 vector<const char*> old_style; 9 // names = old_style;//错误,容器类型不匹配不能赋值 10 names.assign(old_style.cbegin(), old_style.cend());//正确,可以将const char*转化成string 11 12 list<string> a(1);//一个元素,为空string 13 a.assign(10, "hello");//10个元素,每个都是hello 14 for(const auto indx : a){ 15 cout << indx << " "; 16 } 17 cout << endl; 18 19 a.clear(); 20 a.insert(a.begin(), 10, "hello");//通过输出可以发现这两条语句与前面的两条语句等效 21 for(const auto indx : a){ 22 cout << indx << " "; 23 } 24 cout << endl; 25 return 0; 26 }
赋值运算符要求左边和右边的运算对象具有相同的类型。此外顺序容器(array除外)还定义了一个名为 assign 的成员,允许我们从一个不同但相容的类型赋值,或从容器的一个子序列赋值。
需要注意的是:赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。
swap:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 int main(void){ 6 std::vector<string> v1(10, "f"); 7 std::vector<string> v2(24, "a"); 8 auto it = v1.begin();//此时it指向v1的第一个元素,是f 9 cout << &it << endl;//0x6afeb8 10 11 swap(v1, v2); 12 13 cout << *it << endl;//f 14 cout << &it << endl;//0x6afeb8 15 16 for(const auto indx : v1){//输出24个a 17 cout << indx << endl; 18 } 19 cout << endl; 20 for(const auto indx : v2){//输出10个f 21 cout << indx << endl; 22 } 23 return 0; 24 }
除 array 外,swap 不会对任何元素进行拷贝、删除或插入操作,因此可以保证在常数时间内完成。元素不会被移动也意味着,除 string 外,指向容器的迭代器、引用和指针在 swap 操作之后都不会失效。它们任指向 swap 操作之前所指向的那些元素,但是这些元素已经不属于原来的容器了。
insert:
注意:只有 vector、deque、list、string 支持 insert 成员
1 #include <iostream> 2 #include <list> 3 #include <vector> 4 using namespace std; 5 6 int main(void){ 7 vector<string> a; 8 list<string> b; 9 10 //在指定位置之前插入某个元素 11 b.insert(b.begin(), "hello");//等价于b.push_front("hello"); 12 a.insert(a.begin(), "hello");//vector不支持push_front,但我们通过insert达到了同样的效果 13 14 //在指定位置之前插入范围内元素 15 vector<string> v = {"kjf", "fksl", "fjsl", "fls"}; 16 b.insert(b.begin(), v.end() - 2, v.end());//将v中最后两个元素插入到b的开始位置 17 b.insert(b.end(), {"fjksl", "fskl", "fsl"});//在b的末尾插入一个列表 18 19 list<string> c; 20 auto it = c.begin(); 21 string s; 22 while(cin >> s){ 23 it = c.insert(it, s);//返回插入元素的迭代器 24 } 25 return 0; 26 }
需要注意的是:向一个 vector、string 或 deque 插入元素会使原来的迭代器、引用和指针失效。
emplace:
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 class gel{ 6 friend ostream& operator<<(ostream&, const gel&); 7 8 private: 9 int x, y, z; 10 11 public: 12 gel(int a, int b, int c) : x(a), y(b), z(c){} 13 ~gel(){} 14 }; 15 16 ostream& operator<<(ostream &os, const gel &it){ 17 os << it.x << " " << it.y << " " << it.z; 18 return os; 19 } 20 21 int main(void){ 22 vector<gel> a; 23 a.emplace_back(1, 2, 3);//隐式使用了构造函数 24 // a.push_back(1, 2, 3);//错误,没有接受3个参数的push_back版本 25 a.push_back(gel(2, 3, 4));//创建一个临时的gel对象传递给push_back 26 27 // a.emplace_back();//错误,gel类没有默认构造函数 28 a.emplace(a.begin(), 1, 2, 3);//将gel(1, 2, 3)插入到a的首元素前 29 30 for(const auto indx : a){ 31 cout << indx << endl; 32 } 33 return 0; 34 }
c++11标准引入了三个新成员——emplace_front、emplace 和 emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应 push_back、insert 和 push_back。
注意:emplace 函数在容器中直接构造元素。传递给 emplace 函数的参数必须与元素类型的构造函数匹配。