顺序容器
1 顺序容器概述
名称 | 描述 |
---|---|
vector | 可变大小数组,支持快速随机访问。在尾部之外的位置插入元素或删除元素可能很慢 |
deque | 双端队列,支持快速随机访问。从头尾位置插入/删除速度很快 |
list | 双向链表,只支持双向顺序访问。在list中任何位置进行插入和删除操作速度都很快 |
forward_list | 单向链表,只支持单向顺序访问。在任何位置进行插入和删除操作都很快 |
array | 给定大小数组。支持快速随机访问。不能添加或删除元素 |
string | 与vector 相似的容器,随机访问快,在尾部插入/删除速度快 |
Tips:
- string和vector均是将元素保存在连续的内存空间中,由于储存空间的连续,因此利用下标计算其地址非常快。但在容器的中间位置添加或删除元素会非常耗时:在一次插入操作后,需要移动插入或删除位置之后的所有元素,而且添加一个元素后可能还需要分配额外的储存空间。
- list和forward_list优点是在容器如何位置添加和删除操作都很快。但这两类容器不支持随机访问。访问一个元素时,只能遍历整个容器。额外内存开销比其他容器较大。
- deque支持快速随机访问,在中间位置添加和删除元素的代价搞,但是在两端添加或删除元素都很快
选择容器的基本原则:
- 一般情况下,优先选择
vector
- 若程序有很多小的元素,且空间的额外开销很重要,则不要使用
list
或forward_list
- 若程序要求随机访问元素,应使用
vector
或deque
- 若程序要求在容器的中间插入或删除元素,应使用
list
或forward_list
- 若程序要求在容器的头尾位置插入或删除元素,但不会在中间位置进行插入或删除,则使用deque
- 若程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素。如果必须在中间位置插入元素,则在输入阶段使用list,输入完成之后,将list的内容拷贝到一个
vector
中。
2 容器库概览
类型别名 | 描述 |
---|---|
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 ...}; | 列表初始化c |
大小
名称 | 描述 |
---|---|
c.size() | c中元素的数目(不支持forward_list) |
c.max_size() | c可保存的最大元素数目 |
c.empty() | 若c中储存了元素,返回false,否则返回true |
添加/删除元素(不适用与array)
名称 | 描述 |
---|---|
c.insert(args) | 将args 中的元素拷贝进c |
c.emplace(args) | 使用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 |
2.1 容器类型成员
示例如下:
//iter是通过list<stirng>定义的一个迭代器类型
list<string>::iterator iter;
//count是通过vector<int>定义的一个difference_type类型
vector<int>::difference_type count;
//
2.2 容器定义和初始化
将一个新容器创建为另外一个容器的拷贝的方法有两种:可以直接拷贝整个容器,或者(array除外)拷贝由一个迭代器对指定的元素范围。当将一个容器初始化为另外一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。
示例:
//拷贝元素,直到(但不包括)it指向的元素
deque<string> authList(authors.begin(), it);
2.3 赋值和swap
操作 | 描述 |
---|---|
c1 = c2 | 将c1 中的元素替换为c2 中元素的拷贝 |
c = {a,b,c...} | 将c1 中的元素替换为初始化列表中元素的拷贝(array不适用) |
swap(c1, c2) | 交换c1 和c2 中的元素,两者必须具有相同类型,比拷贝快 |
c1.swap(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的元素 |
Tips:赋值相关运算会导致指向左边容器内部的迭代器,引用和指针失效。而swap操作将容器内容交换不会导致指向容器的迭代器,引用和指针失效(容器类型为array和string的情况除外)
赋值运算要求左边和右边的运算对象具有相同的类型,assign操作用参数指定的元素(的拷贝)替换左边容器中的所有元素。例如可以将一个vector中的一段char*
值赋给一个list中的string:
list<string> names;
vector<const char*> oldstyle;
names.assign(oldstyle.cbegin(),oldstyle.cbegin());
使用swap操作用来交换两个相同类型容器的内容。除array外,交换两个容器内容的操作中,元素本身未交换,swap只是交换了两个容器的内部数据结构。这意味着元素不会被移动,因此除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。而swap两个array会真正交换两者的元素,因此对于array,在swap操作之后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经和另外一个array中对应的元素的值进行了交换。
2.3 容器大小操作
成员函数size返回容器中元素的数目;empty当size为0时返回布尔值true,否则返回false;max_size返回一个大于或等于改类型容器所能容纳的最大元素的值。
2.4 关系运算符
关系运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。比较方式如下:
- 如果两个容器具有相同的大小且所有的容器都两两对应相等,则这两个容器相等;否则两个容器不等。
- 如果两个容器大小不同,但较小的容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
- 如果两个容器都不是另外一个容器的前缀子序列,则比较的结果取决于第一个不相等的元素的比较结果
3 顺序容器操作
3.1 向顺序容器添加元素
操作 | 描述 |
---|---|
c.push_back(t) c.emplace_back(args) | 在c的尾部创建一个值为t或由args 创建的元素,返回void |
c.push_front(t) c.emplace_front(args) | 在c的头部创建一个值为t或由args 创建的元素,返回void |
c.insert(p, t) c.emplace(p, args) | 在迭代器p指向的元素之前创建一个值为t或由args 创建的元素,返回指向新添加的元素的迭代器 |
c.insert(p, n, t) | 在迭代器p指向的元素之前插入n个值为t的元素,若n为0,则返回p;否则返回指向新添加的第一个元素的迭代器 |
c.insert(p, b, e) | 将迭代器b和e指定范围内的元素插入到迭代器p指向的元素之前,b和e之间不能指向e。若范围为空,则返回p;否则返回指向新添加的第一个元素的迭代器 |
c.insert(p, il) | il是一个花括号包围的元素值列表。 |
Tip:这些操作会改变容器的大小,array不支持这些操作;forward_list有自己专用版本的insert和emplace;forward_list不支持push_back和emplace_back;vector和string不支持push_front和emplace_front;
使用insert
的返回值,可以在容器中一个特定位置反复插入元素,示例:
list<string> lst;
auto iter = lst.begin();
while(cin >> word)
{
iter = lst.insert(iter, word); //等价调用push_front
}
使用emplace
操作,是将参数传递给元素类型的构造函数。当调用push或insert成员时,是将元素类型的对象传递给他们,这些对象是拷贝到容器中;当调用emplace成员函数时,则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素。示例:
class Book
{
public:
Book(string n = "",int i = 0)
:name(n),price(i)
{
}
private:
string name;
int price;
};
vector<Book> c;
c.emplace_back("111",123);
c.push_back(Book("111",123));
3.2 访问元素
操作 | 描述 |
---|---|
c.back() | 返回c中尾元素的引用。若c为空,函数行为未定义 |
c.front() | 返回c中首元素的引用。若c为空,函数行为未定义 |
c[n] | 返回c中下标为n的元素的引用,若n>c.size(),则函数行为未定义 |
c.at(n) | 返回c中下标为n的元素的引用,若如果下标越界,则抛出异常:out_of_range |
Tip:at和下标操作只适用于string、vector、deque、array
back不是适用于forward_list
3.3 删除元素
操作 | 描述 |
---|---|
c.pop_back() | 删除c中尾元素,若c为空,则函数行为未定义。函数返回void |
c.pop_front() | 删除c中首元素,若c为空,则函数行为未定义。函数返回void |
c.erase(p) | 删除迭代器p所指向的元素,返回一个指向被删除元素之后元素的迭代器 |
c.erase(b,e) | 删除迭代器b和e所指向范围内的元素。返回一个指向最后一个被删除元素之后元素的迭代器 |
c.clear() | 删除c中的所有元素,返回void |
Tip:
- forward_list有特殊版本的erase
- forward_list不支持pop_back;vector和string不支持pop_front。
- 删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用、指针失效
- 指向vector或string中删除点之后位置的迭代器、引用、指针失效
3.4 特殊的forward_list操作
forward_list是单向链表,由于在单向链表中,没有简单的方法来获取一个元素的前驱,因此在一个forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。与其他容器的操作不同,forward_list定义了名为insert_after、emplace_after和erase_after的操作。
操作 | 描述 |
---|---|
lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用 |
lst.cbefore_begin() | cbefore_begin() 返回一个const_iterator |
lst.insert_after(p, t) | 在迭代器p之后的位置插入元素,t是一个对象 |
lst.insert_after(p, n, t) | n表示数量 |
lst.insert_after(p, b, e) | b和e表示范围的一对迭代器 |
lst.insert_after(p, il) | il表示一个花括号列表 |
emplace_insert(p, args) | 使用agrs 在p指定的位置之后创建一个元素。返回一个指向这个新元素的迭代器 |
lst.erase_after(p) | 删除p指向的位置之后的元素 |
lst.erase_after(b, e) | 删除b之后直到e之间的元素,返回一个指向被删除元素之后元素的迭代器 |
3.5 改变容器大小
操作 | 描述 |
---|---|
c.resize(n) | 调整c的大小为n个元素 |
c.resize(n, t) | 调整c的大小为n个元素。任何新添加的元素都初始化为值t |
resize不适用与array,如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;
对vector、string或deque进行resize操作可能导致迭代器、指针和引用失效。
3.5 容器操作可能使迭代器失效
向容器添加元素后:
- 如果容器是vector或string,且储存空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果空间未出现分配,指向插入位置之前的元素的迭代器、指针和引用仍然有效,但在该位置之后的元素的迭代器、指针和引用将会失效
- 对于deque,插入到除首尾之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效
- 对于list和forward_list,指向容器的迭代器、指针和引用仍然有效。
在容器中删除元素后:
- 对于list和forward_list,指向容器其他位置的迭代器、引用指针仍然有效
- 对于deque,若在首尾之外的任何位置删除元素,那么指向被删除元素之外其他元素的迭代器、指针或引用会失效;若删除首元素,则不受影响;若删除尾元素,则尾后迭代器失效,其他不受影响
- 对于vector或string,指向被删除元素之前元素的迭代器、引用和指针仍然有效
4 vector对象是如何增长的
4.1 管理容量的成员函数
操作 | 描述 |
---|---|
c.shrink_to_fit() | 将capacity() 减少为与size()相同大小 |
c.capacity() | 不重新分配内存空间的话,c可以保存多少元素 |
c.reserve(n) | 分配至少能容纳n个元素的内存空间 |
reserve并不改变容器中元素的数量,仅影响vector预先分配多大的内存空间。
只要没有操作需求超出vector的容量,vector就不能重新分配内存空间。只有当在执行insert操作时,size与capacity相等,或者调用resize或reserve 时给定的大小超过当前capacity,vector才可能重新分配内存空间,分配多少取决于具体实现。==如在需要分配新内存空间时将当前容量翻倍,此时可以调用shrink_to_fit来要求vector将超出当前大小的多余内存退回给系统。==详细说明将另外一篇文章vector对象增长说明
5 额外的string操作
5.1 构造string的其他方法
操作 | 描述 |
---|---|
string s(cp, n) | s是cp 指向的数组前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() ,构造函数的行为未定义 |
substr
操作
示例如下:
string s("hello world");
string s2 = s.substr(0, 5); //s2 = hello
string s3 = s.substr(6); //s2 = world
string s4 = s.substr(6, 11); //s2 = world
string s5 = s.substr(12); //抛出一个out_of_range异常
string类还定义了两个额外的成员函数:append和replace。append操作是在string末尾进行插入操作的一种简写形式。
s2.apppend("4th ed"); //等价方法:将"4th ed"追加到s2
5.2 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, 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无默认值 |
5.3 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可以是任何算术类型 |
stoi(s, p, b) stol(s, p, b) stoul(s, p, b) | 返回s的起始子串(表示整数内容)的数值,返回类型为int、long、 |
stoll(s, p, b) stoull(s, p, b) | unsigned long、long long、unsigned long long。b表示转换用的基数,默认为10,p是size_t指针,用来保存s中第一个非数值字符的下标 |
stof(s, p) stod(s, p) stold(s, p) | 返回s的起始子串(表示浮点数内容)的数值,返回类型分别是float、double、long double |
6 容器适配器
标准库定义了三个顺序容器适配器: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.swap(b)) | 交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同 |
定义一个适配器:默认构造函数创建一个空对象,接受一个容器的构造函数拷贝改容器来初始化适配器。假定deq是一个deque<int>
示例如下:
stack<int> stk(deq); //从deq拷贝元素到stk
在构造适配器时,均要求容器具有添加、删除以及访问尾元素的能力。同时要注意以下几点:
- stack只要求push_back,pop_back和back,因此可以使用除array和forward_list之外的任何容器类型来构造stack
- queue要求back、push_back,front和push_front,因此可以构造与list或deque之上,但不能基于vector构造。
- priority_queue除了front、push_back和pop_back操作之外,还要求随机访问能力,因此可以构造于vector或deque之上,但不能基于list构造。
栈适配器
stack类型定义在stack头文件中,下面的示例展示了如何使用stack:
stack<int> intStack; //空栈
//填满栈
for (size_t ix = 0;ix != 10; ++ix )
{
intStack.push(ix); //intStack保存0到9十个数
}
while ( !intStack.empty()) //intStack中有值就继续循环
{
int value = intStack.top();
//使用栈顶值的代码
intStack.pop(); //弹出栈顶元素,继续循环
}
操作 | 描述 |
---|---|
s.pop() | 删除栈顶元素,但不返回该元素之 |
s.push(item) | 创建一个新元素压入栈顶,该元素通过拷贝或移动item而来 |
s.emplace(args) | 或者由args 构造 |
s.top() | 返回栈顶元素,但不将元素弹出栈 |
栈默认基于deque实现,也可以再list或vector之上实现
队列适配器
queue和priority_queue适配器定义在queue,以下是其支持的操作
操作 | 描述 |
---|---|
q.pop() | 返回queue的首元素或priority_queue的最高优先级的元素 |
q.front() | 返回首元素或尾元素,但不删除此元素 |
q.back() | 只适用于queue |
q.top() | 返回最高优先级元素,但不删除该元素(只适用于priority_queue) |
q.push(item) | 在queue末尾或priority_queue中恰当的位置创建一个元素, |
q.emplace(agrs) | 其值为item,或者由agrs 构造 |
queue默认基于deque实现,priority_queue默认基于vector实现;
queue也可以用list或vector实现,priority_queue也可以用deque实现;