一个容器就是一些特定类型对象的集合。
顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。
一、顺序容器类型
vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。 |
deque | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。 |
list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。 |
forward_list | 单向链表。只支持单项顺序访问。在链表任何位置进行插入/删除操作速度都很快。 |
array | 固定大小数组。支持快速随机访问。不能添加或删除元素。 |
string | 与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。 |
二、容器操作
下面介绍对所有容器都适用的操作。
(一)容器操作汇总
类型别名 | |
iterator | 此容器类型的迭代器类型 |
const_iterator | 可以读取元素,,但不能修改元素的迭代器类型。 |
size_type | 无符号整数类型,足够保存此种容器类型最大可能容器的大小。 |
difference_type | 带符号整数类型,足够保存两个迭代器之间的距离。 |
value_type | 元素类型。 |
reference | 元素的左值类型;与value_type&含义相同。 |
const_reference | 元素的cosnt左值类型(即,const value_type&) |
构造函数 | |
C c; | 默认构造函数,构造空容器 |
C c1(c2); | 构造c2的拷贝c1 |
C c(b,e); | 构造c,将迭代器b和e指定的范围内的元素拷贝到c中。 |
C c{a,b,c...}; | 列表初始化c |
赋值与swap | |
c1=c2 | 将c1中的元素替换为c2中元素 |
c1={a,b,c...} | 将c1中的元素替换为列表中元素 |
a.swap(b) | 交换a和b的元素 |
swap(a,b) | 与a.swap(a,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.begin()返回指向c的首元素位置的迭代器,c.end()返回指向尾元素之后位置的迭代器 |
c.cbegin(),c.cend() | 同上,但返回类型为cosnt_iterator |
反向容器的额外成员(不支持forward_list) | |
reverse_iterator | 按逆序寻址元素的迭代器 |
const_reverse_iterator | 不能修改元素的逆序迭代器 |
c.rbegin(),c.rend() | 返回指向c的尾元素和首元素之前位置的迭代器 |
c.crbegin(),c.crend() | 返回cosnt_reverse_iterator |
(二)容器定义和初始化
每个容器类型都定义了一个默认构造函数。除array之外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接收指定容器大小和元素初始值的参数。
C c; | 默认构造函数。如果C是一个array,则c中元素按默认方式初始化;否则c为空。 |
C c1(c2) | c1初始化为c2的拷贝。c1和c2必须是相同类型(即,它们必须是相同的容器类型,且保存的是相同的元素类型;对于array类型,两者还必须具有相同大小) |
C c1=c2 | 等价于c1(c2)。 |
C c{a,b,c...} | c初始化为初始化列表中元素的拷贝。列表中元素的类型必须与C的元素类型相容。对于array类型,列表中元素数目必须等于或小于array的大小,任何遗漏的元素都进行值初始化。 |
C c={a,b,c...} | 等价于c{a,b,c...}。 |
C c(b,e) | c初始化为迭代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与C的元素类型相容(array不适用)。 |
只有顺序容器(不包括array)的构造函数才能接受大小参数 | |
C seq(n) | seq包含n个元素,这些元素进行了值初始化;此构造函数是explicit的(string不适用)。 |
C seq(n,t) | seq包含n个初始化为值t的元素。 |
(三)容器赋值操作
c1=c2 | 将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同的类型。 |
c={a,b,c...} | 将c中元素替换为初始化列表中元素的拷贝(array不适用) |
seap(c1,c2) | 交换c1和c2中的元素。c1和c2必须具有相同的类型。swap通常比从c2向c1拷贝元素快得多。 |
c1.swap(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只是交换了两个容器的内部数据结构,元素本身并未交换。
除string之外,swap操作之后,指向容器的迭代器、引用和指针都不会失效,它们仍指向swap操作之前所指向的那些元素。但,在swap之后,这些元素已经属于不同的容器了。
对于array来说,swap会真正交换它们的元素。
四、顺序容器操作
顺序容器和关联容器的不同之处在于两者组织元素的方式。这些不同之处直接关系到了元素如何存储、访问、添加以及删除。
(一)向顺序容器中添加元素
除array外,所有标准库容器都提供灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。
下述操作会改变容器的大小;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_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) | il是一个花括号包围的元素值列表。将这些给定值插入到迭代器p指向的元素之前。返回指向新添加的元素的迭代器;若列表为空,则返回p |
(二)访问顺序容器中的元素
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的元素的引用。若下标越界,则抛出一out_of_range异常。 |
(三)顺序容器删除元素
下述操作会改变容器的大小,所以不适用array。
forward_list有特殊版本的erase。
forward_list不支持pop_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)所指范围内的元素。返回一个指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器。 |
c.clear() | 删除c中的所有元素。返回void |
1.forward_list中的插入或删除操作
如下图所示,
当添加或删除一个元素时,删除或添加的元素之前的哪个元素的后继会发生变化。
因此,forward_list定义了专门的函数来实现类似操作。
lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。 |
lst.before_cbegin() | 返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。cbefore_begin()返回一个const_iterator |
lst.insert_after(p,t) | 在迭代器p之后的位置插入元素。t是一个对象。返回一个指向最后一个插入元素的迭代器。若范围为空,则返回p。若p为尾后迭代器,则函数行为未定义。 |
lst.insert_after(p,,n,t) | 在迭代器p之后的位置插入元素。t是一个对象,n是数量。返回一个指向最后一个插入元素的迭代器。若范围为空,则返回p。若p为尾后迭代器,则函数行为未定义。 |
lst.insert_after(p,b,e) | 在迭代器p之后的位置插入元素。b和e是表示范围的一对迭代器(b和e不能指向lst内)。返回一个指向最后一个插入元素的迭代器。若范围为空,则返回p。若p为尾后迭代器,则函数行为未定义。 |
lst.insert_after(p,il) | 在迭代器p之后的位置插入元素。il是一个花括号列表。返回一个指向最后一个插入元素的迭代器。若范围为空,则返回p。若p为尾后迭代器,则函数行为未定义。 |
emplace_after(p,args) | 使用args在p指定的位置之后创建一个元素。返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义。 |
lst.erase_after(p) | 删除p指向的位置之后的元素。返回一个指向被删元素之后元素的迭代器,若不存在这样的元素,则返回尾后迭代器。若p指向lst的尾元素或是一个尾后迭代器,则函数行为未定义。 |
lst.erase_after(b,e) | 删除从b之后直到(但不包含)e之间的元素。返回一个指向被删元素之后元素的迭代器,若不存在这样的元素,则返回尾后迭代器。若p指向lst的尾元素或是一个尾后迭代器,则函数行为未定义。 |
(四)改变顺序容器大小操作
可以用resize来增大或缩小容器,array不支持array。
若当前大小大于所要求的大小,容器后面的元素会被删除。
例:
list<int> ilist(10,42);//创建10个int类型,每个值为42
ilist(15);//将5个值为0的元素添加到ilist末尾
ilist(25,-1);//将10个值为-1的元素添加到ilist末尾
ilist(5);//从ilist末尾删除20个元素
c.resize(n) | 将c的大小调整为n个元素。若n<c.size(),则多出的元素会被丢弃。若必须添加新元素,对新元素进行值初始化。 |
c.resize(n,t) | 将c的大小调整为n个元素。任何新添加的元素都初始化为t |
(五)增加容器大小
下述操作只适用于vector和string类型。
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的大小) |
vector和string类型提供了一些成员函数,允许与它的实现中内存分配部分互动。capacity操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素。reserve操作允许我们通知容器它应该准备保存多少个元素。
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什么也不做。当需求大小小于当前容量时,容器不会退回内存空间。
resize成员函数只改变容器中元素的数目,而不是容器的容量。
五、额外的string操作
(一)构造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的长度时,构造函数至多拷贝s2.size()-pos2个字符) |
s.substr(pos,n) | 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size()-pos,即拷贝从pos开始的所有字符。若开始位置超过了string的大小,则substr函数抛出一个out_of_range异常。若开始的位置加上计数值大于string的大小,则substr会调计数值,只拷贝到string的末尾。 |
substr操作返回一个stirng。它是原始string的一部分或全部的拷贝。若开始位置超过了string的大小,则substr函数抛出一个out_of_range异常。若开始位置加上计数值大于string的大小,则substr会调整计数值,只拷贝到string的末尾。
s.substr(pos,n) | 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size()-pos,即拷贝从pos开始的所有字符。 |
可以传递给substr一个可选的开始位置和计数值:
string s("hello world");
string s2=s.substr(0,5);//s2=hello
string s3=s.substr(6);//s3=world
string s4=s.substr(6,11);//s4=world
string s5=s.substr(12);//抛出一个out_of_range异常
(二)改变string的其他方法
下述的args可以是下列形式之一;append和assign可以使用所有形式。
str不能与s相同,迭代器b和e不能指向s。
str 字符串str
str,pos,len str中从pos开始最多len个字
cp,len 从cp指向的字符数组的前(最多)len个字符
cp cp指向的以空字符结尾的字符数组
n,c n个字符c
b,e 迭代器b和e指定的范围内的字符
初始化列表 花括号包围的,以逗号分隔的字符列表
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的迭代器。返回一个指向s的引用。 |
(三)string搜索操作
string类提供了6个不同的搜索函数,每个函数都有4个重载版本。每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。若搜索失败,则返回一个名为string::npos的static成员。标准库将npos定义为一个const string::size_type类型,并初始化为值-1。由于npos是一个unsigned类型,此初始值意味着npos等于任何string最大的可能大小。
下述的args必须是以下形式之一:
c,pos 从s中位置pos开始查找字符c。pos默认为0
s2,pos 从s中位置pos开始查找字符串s2。pos默认为0
cp,pos 从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认为0
cp,pos,n 从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值
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中的字符 |
(四)compare函数
功能类似于strcmp函数。
s2(即s.compare(s2)) | 比较s和s2 |
pos1,n1,s2 | 将s中从pos1开始的n1个字符与s2进行比较 |
pos1,n1,s2,pos2,n2 | 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较 |
cp | 比较s与cp指向的以空字符结尾的字符数组 |
pos1,n1,cp | 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的字符数组进行比较 |
pos1,n1,cp,n2 | 将s中从pos1开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较 |
(五)数值转换
to_string(val) | 一组重载函数,返回数值val的string表示。val可以是任何算术类型。对每个浮点类型和int或更大的类型,都有相应版本的to_string。与往常一样,小整型会被提升。 |
stoi(s,p,b) | 返回s的起始字串(表示整数内容)的数值,返回值类型分别是int、long、unsigned long、long long、unsigned long long。b表示转换所用的基数,默认值为10。p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即,函数不保存下标 |
stol(s,p,b) | 同上 |
stoul(s,p,b) | 同上 |
stoll(s,p,b) | 同上 |
stoull(s,p,b) | 同上 |
stol(s,p) | 返回s的起始子串(表示浮点数内容)的数值,返回值类型分别是float、double或long double。参数p的作用与整数转换函数中一样。 |
stod(s,p) | 同上 |
stold(s,p) | 同上 |
六、标准库array的初始化
标准库array的大小也是类型的一部分。
当定义一个array时,除了指定元素类型,还要指定容器大小。
例:
array<int,42> a;//类型为:保存42个int的数组
array<int>a;//错误
array不支持普通的容器构造函数。
与其他容器不同,一个默认构造的array是非空的:它包含了与其大小一样多的元素。这些元素都被默认初始化。
如果对array进行列表初始化,初始值的数目必须等于或小于array的大小。若初始值数目小于array的大小,则它们被用来初始化array中靠前的元素,所有剩余元素都会被进行值初始化。
例:
array<int 10> ia={42};//ia[0]被初始化为42,剩余元素为0
七、容器适配器
容器适配器是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的类型。
例如,stack适配器接受一个顺序容器(除array或forward_list外),并使其操作起来像一个stack一样。
三种顺序容器适配器:stack、queue和priority_queue。
size_type | 一种类型,足以保存当前类型的最大对象的大小 |
value_type | 元素类型 |
container_type | 实现适配器的底层容器类型 |
A a; | 创建一个名为a的空适配器 |
A a(c); | 创建一个名为a的适配器,带有容器c的一个拷贝 |
关系运算符 | 每个适配器都支持所有关系运算符:==、!=、<、<=、>和>=,这些运算符返回底层容器的比较结果 |
a.empty() | 若a包含任何元素,返回false,否则返回true |
a.size() | 返回a中的元素数目 |
swap(a,b) | 交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同 |
a.swap(b) | 同上,等价于swap(a,b) |
(一)定义适配器
每个适配器都定义两个构造函数:
1.默认构造函数创建一个空对象。
2. 接受一个容器的构造函数拷贝该容器来初始化适配器。
例:
//从deq拷贝元素到stk,假定deq是一个deque<int>类型
stack<int> stk(deq);
默认情况下,stack和queue是基于deque实现的,priority_queue只在vector之上实现的。
可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。
例:
stack<string,vector<string>> str_stk;//在vector上实现的空栈。
(二)适配器的选择
对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要求容器具有添加和删除元素的能力。因此, 适配器不能构造在array上,也不能用forward_list来构造适配器。
1.stack只要求push_back、pop_back和back操作,因此可以使用除array和forward_list之外的任何容器类型来构造stack。
2.queue适配器要求back、push_back、front和push_front,因此它可以构造于list或deque之上,但不能基于vector构造。
3.priority_queue除了front、push_back和pop_back操作之外还要求随机访问能力,因此它可以构造于vector或deque之上,但不能基于list构造。
(三)stack----栈适配器
头文件:<stack>
栈默认基于deque实现,也可以在list或vector之上实现。
s.pop() | 删除栈顶元素,但不返回该元素值 |
s.push(item) | 创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或者由args构造 |
s.emplace(args) | 同上 |
s.top() | 返回栈顶元素,但不将元素弹出栈 |
(四)队列适配器----queue和priority_queue
头文件:
<queue>
queue默认基于deque实现,priority_queue默认基于vector实现。
queue也可以用list或vector实现,priority_queue也可以用deque实现。
queue使用一种先进先出的存储和访问策略。进入队列的对象被放置到队尾,而离开队列的对象则从队首删除。
priority_queue允许我们为队列中的元素建立优先级。默认情况下,标准库在元素类型上使用<运算符来确定相对优先级。
q.pop() | 返回queue的首元素或priority_queue的最高优先级的元素,但不返回此元素。 |
q.front() | 返回首元素或尾元素,但不删除此元素(只适用于queue) |
q.back() | 同上 |
q.top() | 只返回最高优先级元素,但不删除此元素(只适用于priority_queue) |
q.push(item) | 在queue末尾或priority_queue中恰当的位置创建一个元素,其值为item,或者由args构造 |
q.emplace(args) | 同上 |