目录
简介:容器就是一些特定类型对象的集合,顺序容器就是一种元素的顺序和它的值无关,只是将元素与元素加入容器时的位置相对应的容器类别。
1. 容器类型
容器 | 类型 | 优 | 劣 |
vector | 可变大小数组 | 快速随机访问,尾部插入、删除元素很快,顺序存储,计算地址速度很快 | 除尾部外其它位置插入、删除元素很慢 |
deque | 双端队列 | 快速随机访问,头尾部插入、删除元素很快 | 除尾部外其它位置插入、删除元素很慢 |
list | 双向链表 | 双向顺序访问,任意位置插入、删除元素很快 | 随机访问,内存开销大 |
forward_list | 单链表 | 单向顺序访问,任意位置插入、删除元素很快 | 随机访问,内存开销大 |
array | 固定大小数组 | 随机快速访问 | 不允许插入和删除元素,顺序存储,计算地址速度很快 |
string | 字符串数组 | 快速随机访问,尾部插入、删除元素很快,顺序存储,计算地址速度很快 | 除尾部外其它位置插入、删除元素很慢 |
2. 容器操作(除标注外,其余操作也同样适用于关联容器)
2.1 类型别名
类型别名:vector<int>::reference | 含义 |
iterator | 迭代器 |
const_iterator | 只能读取元素,不能修改元素的迭代器 |
size_type | 无符号整型,保存容器的最大容量值 |
difference_type | 带符号整型,两个迭代器之间的距离 |
value_type | 容器的元素类型 |
reference | 类似于:value_type&,容器元素类型的引用 |
const_reference | 类似于:const value_type&元素的const左值类型 |
2.2 容器定义和初始化
容器定义和初始化 | 含义 |
C c; | 默认构造函数 |
C c1( c2 ); C c1 = c2; | 把c2拷贝为c1,c1和c2必须容器类型相同、元素类型也相同,如果C是array还要求大小相同。 |
C c( b , e ); | 用迭代器b和e之间的元素构造c的元素,b和e指向的元素类型必须与C的元素类型相容(不是相同,就是可以转换的意思),array不支持 |
C c{ a , b , c , ...}; C c = { a , b , c , ...}; | 列表初始化,a,b,c分别初始化容器c,列表内的元素类型必须与C的元素类型相容(不是相同,就是可以转换的意思),array不支持 |
C c( n ); | c中包含有n个元素,这些元素进行了值初始化,是explicit的,只适用于顺序容器,且array不支持 |
C c( n , t ); | c中包含有n个元素,每个元素用值 t 来进行初始化,只适用于顺序容器,且array不支持 |
注意:最后两种,传入大小参数的初始化,如果顺序容器的元素是不提供默认构造函数的类型,那么要显示的写上初始化值。
vector<string> ive(10,"hi"); //这里string是有默认构造函数的,举个例子
2.3 赋值与swap、assign替换
赋值与swap | 含义 |
c1 = c2; | 将c1替换为c2 |
c1 = { a , b , c ,...... }; | 将c1中的全部元素用列表里的全部元素替换 |
a.swap(b); | 交换a与b的元素 |
swap( a , b ); | 交换a与b的元素 |
a.assign( il ); | 将a中的元素替换为初始化列表il中的元素,只适用于顺序容器,且array不支持 |
a.assign( n , t ); | 将a中的元素替换为n个值为t的元素,只适用于顺序容器,且array不支持 |
注意:
1.赋值相关运算会导致指向左边容器内部的迭代器、引用、指针全部失效,而swap不会。(array和string除外,string调用swap也会失效)
2.swap效率很高,是因为实际上元素本身并没有交换,swap只是交换了两个容器的内部数据结构,但array除外,array会进行元素的交换,所以对array的swap操作时间和array的元素数量有关。
3.swap的交换,虽然不会导致迭代器、引用、指针失效,且它们指向的元素不变,但会使他们指向的容器变化。如:iter在swap之前指向svec1[3],在swap之后则会指向svec2[3]。
2.4 大小
大小 | 含义 |
c.size(); | c中元素的数目,forward_list容器不支持此操作 |
c.max_size(); | c可以保存的最大元素数目 |
c.empty(); | c中有元素,返回false,c中没元素,返回true |
c.length(); | string独有,和size()的是一样的 |
2.5 添加/删除元素(array不支持)
注:不同的容器,操作的接口不一样。
添加/删除元素(array不支持) | 含义 |
c.insert( args ); | 将args中的元素拷贝到c里 |
c.emplace( inits ); | 使用inits构造c中的一个元素 |
c.erase( args ); | 删除args指定元素 |
c.clear(); | 清空c中的元素 |
2.6 关系运算符
关系运算符 | 含义 |
==、!= | 所有容器都支持==和!=运算 |
<、<=、>、>= | 关系运算符(无序关联容器不支持) |
比较的条件:两个容器必须是相同类型的容器,即要求容器类型、元素类型都相等。
比较的过程:两个容器之间的比较其实是进行元素的逐对比较。如果元素本身不支持某些运算符的比较,则不能比较两个容器之间的大小。
1.两容器大小相等,元素两两相等,则两容器相等,否则不等。
2.两容器大小不同,较小的容器中每个元素都等于较大容器中的元素,则较小的容器小于较大的容器。
3.两容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。
2.7 获取迭代器的函数
迭代器函数 | 含义 |
begin()、end() | 指向容器的首元素和微元素之后位置的元素 |
cbegin()、cend() | 返回只读类型的迭代器 |
rbegin()、rend() | 返回反向读写类型的迭代器 |
crbegin()、crend() | 返回反向只读类型的迭代器 |
2.8 array容器的特殊操作
标准库array的大小也是array类型的一部分,意味着声明时必须指定容器大小,如:
array<type,value> containname; //value就是array容器的大小
普遍来说,我们不能对内置数组类型进行拷贝或给对象赋值的操作,但是array容器并无此限制:
int array[10]={0,1,2,3,4,5,6,7,8,9};
int cpy[10]=array; // err
array<int,10> a={0,1,2,3,4,5,6,7,8,9};
array<int,10> b=a; // true,类型匹配即合法
array只支持默认构造和拷贝构造。
3. 顺序容器操作
3.1 向顺序容器添加元素的操作
想顺如容器添加元素的操作 | 含义 |
c.push_back(t); c.emplace_back(args); | 在c的尾部创建一个值为t或由args创建的元素,push_back只能接收1或2个参数,emplace_back可以接收多个参数(构造函数有多个参数)。返回void,forward_list不支持此操作 |
c.push_front(t); c.emplace_front(args); | 在c的头部创建一个值为t或由args创建的元素,push_back只能接收1或2个参数,emplace_back可以接收多个参数(构造函数有多个参数)。返回void,vector和string不支持此操作 |
c.insert(p,t); c.emplace(p,args); | 在迭代器p指向的元素之前创建一个值为t或由args创建的元素,push_back只能接收1或2个参数,emplace_back可以接收多个参数(构造函数有多个参数)。返回指向新添加的元素的迭代器。forward_list有自己版本的insert和emplace |
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。 |
c.emplace(p,a,b,c,...); | 将参数为a,b,c,....的构造函数的元素,插入到迭代器p指向的元素之前。返回指向新添加的第一个元素的迭代器,若为空,则返回p。 |
注意:向一个vector、string或deque插入元素会导致所有指向容器的迭代器、指针、引用全部失效。因为:向以上三个容器里插入元素后,会重新分配内存空间,迭代器先前指向的空间里的元素可能已经发生了变化,所以会失效。
emplace和insert功能相同,它们有什么区别?
emplace 使用直接构造,是将参数传递给容器管理的内部空间里直接构造元素。
insert 和push_back都使用拷贝构造,是将元素类型的对象拷贝到容器中,即 emplace 可以避免产生不必要的临时变量。
class Container
{
public:
Container(int a,int b)
{
A = a;
}
int getA() { return A; }
private:
int A;
};
vector<Container> vein2;
vein2.emplace(vein2.begin(), 1, 2); //不会产生临时变量
vein2.insert(vein2.begin(),Container(1,2)); //会产生一个临时变量
for (auto it : vein2)
{
cout << it.getA() << " ";
}
3.2 访问元素
在顺序容器中访问元素的操作 | 含义 |
c.back(); | 返回c中尾元素的引用,若c为空,则函数行为未定义,forward_list不支持 |
c.front(); | 返回c中首元素的引用,若c为空,则函数行为未定义 |
c[n] | 返回c中下标为n的元素的引用,n是一个无符号整数。若n>=c.size(),则函数行为未定义,list和forward_list不支持 |
c.at(n); | 返回c中下标为n的元素的引用,n是一个无符号整数。若n越界,则抛出out_of_range异常,list和forward_list不支持 |
如果我们不确定所使用的下标n是否合法,则可以使用at成员函数,因为它会抛出一个异常。
3.3 删除元素
顺序容器的删除操作(array都不支持) | 含义 |
c.pop_back(); | 删除c中尾元素。若c为空,则函数行为未定义。函数返回void,forward_list不支持 |
c.pop_front(); | 删除c中首元素。若c为空,则函数行为未定义。函数返回void,vector、string不支持 |
c.erase(p); | 删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器,若p指向尾元素,则返回尾后迭代器。若p是尾后迭代器(end 成员返回的迭代器),则函数行为未定义 |
c.erase(b,e); | 删除迭代器b和e所指定范围内的元素,返回一个指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器(end 成员返回的迭代器),则函数也返回尾后迭代器,删除范围是一个左闭右开的区间,[b,e),即如果b==e,则一个也不会删除,如果b==c.begin(),e==c.end(),则会删除所有元素,返回c.end()。 |
c.clear(); | 删除c中所有元素,返回void |
注意:删除deque中首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。
3.4 forward_list 的特殊操作
在forward_list中插入或删除元素 | 含义 |
lst.before_begin() lst.cbefore_begin() | 返回指向链表首元素的上一个元素的迭代器,因为这个元素是不存在的(类似于单链表的头结点),所以不可进行解引用操作。cbefore_begin()返回一个const_iterator |
lst.insert_after(p,t) lst.insert_after(p,n,t) lst.insert_after(p,b,e) lst.insert_after(p,il) | 在迭代器p指向的元素之后的位置插入元素。t是一个对象,n是数量,b,e是表示一个范围由一对迭代器指定,il是一个列表。返回一个指向最后一个插入元素的迭代器。如果范围为空,则返回p,若p为尾后迭代器,则函数行为未定义。 |
lst.emplace_after(p,args) | 使用args在p指定的位置之后创建一个元素。返回指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义。 |
lst.erase_after(p) lst.erase_after(b,e) | 删除迭代器p指向的位置之后的元素,或删除从b到e(不包含e)之间的元素(左闭右开区间)。返回一个指向被删元素之后元素的迭代器,若不存在这样的元素,则返回尾后迭代器,如果p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义。 |
3.5 改变容器大小(array不支持)
顺序容器大小操作 | 内容 |
c.resize(n) | 调整c的大小为n个元素,若n<c.size(),则多出的元素被丢弃,若必须添加新元素,则对新元素进行值初始化 |
c.resize(n,t) | 调整c的大小为n个元素。任何新添加的元素都初始化未值t,如果元素类型为类类型,且没有提供默认构造函数,则必须提供构造函数的参数。 |
注意:如果resize缩小容器,则指向被删除的元素的迭代器、引用和指针都会失效,对vector、string、或deque进行resize可能会导致迭代器、引用和指针都会失效
3.6 管理容器容量大小
为了避免容器每次添加新元素都要重新分配内存空间而导致效率变慢的情况,每次容器都会分配比新的空间需求更大的内存空间,容器会预留这些空间进行备用,这样就不用每次一添加元素就要重新分配空间了。
容器大小管理操作 | 含义 |
c.shrink_to_fit() | 将capacity()减少为与size()相同大小,这只是一个请求,标准库并不保证退还内存,只支持vector、string和deque |
c.capacity(); | 不重新分配空间的话,c可以保存多少元素,只支持vector、string |
c.reserve(n) | 分配至少能容纳n个元素的内存空间,只支持vector、string |
capacity就是告诉我们容器在不扩张内存空间的情况下可以容纳多少元素
reserve就是允许我们通知容器它应该准备保存多少个元素
size就是告诉我们容器里已经保存了多少元素
4 额外的string操作
4.1 构造string的其它方法
除了上述通用的构造方法外,string提供另外三种构造函数
构造string的其它方法 | 含义 |
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(),则构造函数未定义。 |
4.2 子字符串的操作
子字符串的操作 | 含义 |
s.substr(pos,n) | 返回一个string,包含s中从pos开始的n个字符的拷贝。pos默认为0,n的默认值为s.size()-pos,即拷贝从pos开始的所有字符。 |
4.3 修改string的操作
修改string的操作 | 含义 |
s.insert(pos,args) | 在pos之前插入args指定的字符。pos可以是一个小标或一个迭代器。接受下标的版本会返回一个指向s的引用,接受迭代器的版本返回指向第一个插入字符的迭代器 |
s.erase(pos,len) | 删除从位置pos开始的len个字符。如果len被省略,则删除从pos开始直至s末尾的所有字符。返回一个指向s的引用 |
s.assign(args) | 将s中的字符替换为args指定的字符。返回一个指向s的引用 |
s.append(args) | 将args追加到s之后,返回一个指向s的引用 |
s.replace(range,args) | 删除s中范围为range内的字符,替换为args指定的字符。range可以是一对迭代器,下标,或者是一个长度。返回一个指向s的引用 |
4.4 string的搜索操作
string的搜索操作 | 含义 |
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_first_not_of(args) | 查找s中第一个不在args中的字符 |
s.find_last_not_of(args) | 查找s中最后一个不在args中的字符 |
args只能是:
c是一个字符,s2是字符串,cp是c风格的指针,n是cp指向的数组的前n个字符,pos是指定的开始位置
c,pos
s2,pos
cp,pos
cp,pos,n
以上搜索成功则返回一个string::size_type的值,失败则返回一个string::nops的static成员(npos是一个const string::size_type类型值为-1)。
string number("0123456789);
string name("r2d2");
name.find_first_of(number); //返回1
string dept("01234p3");
dept.find_first_not_of(number); //返回5
4.5 string数值转换
函数 | 含义 |
to_string(val) | 返回数值val的string表示 |
stoi(s,p,b)、stol(s,p,b)、stoul(s,p,b)、stoll(s,p,b)、stoull(s,p,b) | 返回s的起始子串(表示整数内容)的数值 |
stof(s,p)、stod(s,p)、stold(s,p) | 返回s的起始子串(表示浮点数内容)的数值 |
注意:转换时,string的第一个非空白符必须是符号(+、-)或数字。
5. 容器适配器
适配器是一种机制,能使某种事物的行为看起来像另外一种不同的类型。
适配器主要有:stack、queue、priority_queue。
所有适配器都支持的操作和类型 | |
size_type | 一种类型,足以保存当前类型的最大对象的大小 |
value_type | 元素类型 |
container_type | 实现适配器的底层容器的类型 |
A a; | 创建一个名为a的空适配器 |
A a(c) | 创建一个名为a的适配器,并带有容器c的一个拷贝 |
关系运算符 | 每个适配器都支持所有关系运算符,返回底层容器的比较结果 |
a.empty() | 判断是否为空 |
a.size() | 返回a中的元素数目 |
swap(a,b)、a.swap(b) | 交换a,b适配器的内容,a,b必须具有相同类型,并且底层容器也要相同 |
所有适配器都必须具有添加和删除元素的能力,所以底层容器不能为array、forward_list。
5.1 栈适配器
头文件:#include<stack.h>
stack默认基于deque实现,也可以在list或vector之上实现
stack只要求,push_back、pop_back、back操作,所以可以用除array、forward_list之外的任意一个容器来构造,即把一个个容器作为元素压入栈中stack<vector<int>> stve。
函数 | 含义 |
s.pop() | 删除栈顶元素,但不返回该元素值 |
s.push(item) | 创建一个新元素压入栈顶 |
s.emplace(args) | 由args构造 |
s.top() | 返回栈顶元素 |
5.2 队列适配器
头文件:#include<queue>
queue:是一种FIFO先进先出的存储和访问策略,默认基于deque实现,且它要求back、push_back、front、push_front操作,所以不能用vector来构造。
priority_queue:允许你自己定义优先级,新插入的元素会排在优先级比它低的元素之前,高->低,默认基于vector实现,除了queue要求的操作外,还需要有随机访问的能力,所以它不能由list来构造,优先级的确认由<运算符来决定,需要重载。
函数 | 含义 |
q.pop() | 返回queue的首元素或priority_queue的最高优先级的元素 |
q.front() | 返回首元素或尾元素 |
q.back() | 只适用于queue |
q.top() | 返回最高优先级的元素,只适用于priority_queue |
q.push(item) | 在末尾创建一个元素 |
q.emplace(args) | 由args构造 |