顺序容器简介

顺序容器类型 描述
vector 可变大小数组,支持快速访问,在尾部之外的地方插入或删除时可能很慢
deque 双端队列。支持快速访问,在头尾插入删除会很快。
list 双向列表。只支持双向顺序访问。插入删除很快
forward_list 单向列表。只支持单向顺序访问,在任何位置插入或删除都很快
array 固定大小数组,支持快速随机访问,不能添加删除元素
string 与vector类似,专门用于保存字符。随机访问快,在尾部添加删除快

其中array和forward_list是新C++标准增加的类型。与内置数组相比,array是一种更安全更容易使用的数组类型。而 forward_list设计目标是大道与最好的手写单向链表相当的性能,因此它没有size操作,而对其他容器,size是快速的常量时间操作。

选用容器的基本原则

  • 除非有很好的原因,否则应该使用vector;
  • 如果程序有很多很小的元素,且空间的额外开销很重要,则不要使用list和forward_list;
  • 如果要求随机访问元素,则使用vector或deque;
  • 如果需要在容器中间插入删除,应该使用list或forward_list;
  • 如果需要在容器头尾插入删除,不需要在中间插入删除,则使用deque;
  • 如果只有在读入数据时,需要在容器中间插入元素,随后要随机访问元素,则首先要确定是真的需要在中间插入,能否在尾部插入然后重排来实现,如果还是必须在中间插入,则先用list接受数据在拷贝到vector中;

注意:较旧的编译器需要在vector<vector<string> >的两个尖括号之间加上空格。

所有容器的通用操作:

相关类型总结

size_type        无符号整型,足以存储此容器类型的最大可能容器长度;注意与size_t区别,size_t是unsigned类型,用于指明数组长度或下标,它必须是一个正数,std::size_t,它是标准类库std内。

iterator        此容器类型的迭代器类型

const_iterator     元素的只读迭代器类型

reverse_iterator    按逆序寻址元素的迭代器(不支持forward_lsit)

const_reverse_iterator 元素的只读(不能写)逆序迭代器(不支持forward_lsit)

difference_type    足够存储两个迭代器差值的有符号整型,可为负数;注意与ptrdiff_t区别,ptrdiff_t是signed类型,用于存放同一数组中两个指针之间的差距,它可以使负数,std::ptrdiff_t.它是标准类库std内。

value_type       元素类型

reference       元素的左值类型,是 value_type& 的同义词

const_reference     元素的常量左值类型,等效于 const value_type&

构造函数总结

C<T> c 创建一个名为c的空容器,C是容器类型名,如vectorT是元素类型,如intstring。适用于所有容器
C c(c2) 创建容器c2的副本cc2c必须具有相同的容器类型,并存放相同类型的元素。适用于所有容器

C c{a,b,c...}

C c={a,b,c...}

c初始化为初始化列表中元素的拷贝。列表中元素类型必须与c的元素类型相容,对于array类型,列表中元素数目必须等于

或小于array的大小,任何遗漏的元素都进行值初始化。

C c(n) 创建有n个初始化元素的容器c。只适用顺序容器
C c(n, t) 使用n个为t的元素创建容器c,其中值t必须是容器类型C的元素类型的值,或者是可以转换为该类型的值。只适用顺序容器
C c(b, e) 创建容器c,其中元素是迭代器be标示的范围内元素的副本。适用于所有容器

将一个容器创建为另一个容器的拷贝的两种方法:

  • 直接拷贝整个容器;两个容器的类型和元素类型必须匹配;
  • 拷贝迭代器指定的元素范围;不要求容器类型相同,元素类型也可以不同只要能够相互转换。

如果元素是内置类型或有默认构造函数的类类型,怎可以只提供容器大小,如果元素没有默认构造函数就必须显示指定初始值。

array对类类型初始化时,需要该类类型由默认构造函数,array类型可以拷贝或赋值,但要求初始值类型和容器类型相同,且元素类型和大小一样。

赋值操作

c1 = c2;    c2赋给c1,如果两容器大小不同,赋值后,两者大小与右边容器的大小相同。array允许赋值,但是左右两边的对象必须有相同类型。

c1 = {a,b,c}; c1中的元素替换为列表中的元素,array不支持assign和值列表进行赋值(因为两边大小可能不一样)。

赋值相关运算会导致左边容器内部迭代器、引用和指针失效,除了array和string外,swap不会导致容器的迭代器、引用和指针失效。

顺序容器可以用assign来从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。

seq.assign(b,e);//seq替换为b到e之间的元素

seq.assign(li);//seq替换为初始化列表中的元素

seq.assign(n,t);//seq替换为n个值为t的元素

swap函数

a.swap(b);  a和b交换

swap(a,b);  a和b交换

除了array外,swap交换两个容器内容操作很快,因为他并没有交换元素本身,而是交换了容器内部的数据结构。所以,可以保证在常数时间内完成。

因此,除了string外指向容器的迭代器、引用和指针在swap后也不会失效,只是它们所属的容器变化了。

注意。早期版本只有成员函数版本的swap,新版本两个都有,但是非成员函数的swap对泛型编程很重要,统一使用非成员版本的swap是个好习惯。

其他函数

c.size()      返回容器 c 中的元素个数。返回类型为 c::size_type;(不支持forward_list)

c.max_size()    返回容器 c 可容纳的最多元素个数,返回类型为 c::size_type

c.empty()      返回标记容器大小是否为 0 的布尔值

c.erase(args)    删除args指定的元素

c.clear()      删除容器 c 内的所有元素。返回 void

c.insert(args)   将args中的元素拷贝到c中

c.begin()     返回一个迭代器,它指向容器 c 的第一个元素

c.end()      返回一个迭代器,它指向容器 c 的最后一个元素的下一位置

c.rbegin()    返回一个逆序迭代器,它指向容器 c 的最后一个元素

c.rend()     返回一个逆序迭代器,它指向容器 c 的第一个元素前面的位置

c.crbegin()    返回一个const逆序迭代器,它指向容器 c 的最后一个元素

c.crend()     返回一个const逆序迭代器,它指向容器 c 的第一个元素前面的位置

以c开头的函数是C++新标准引入的用以支持auto与begin和end结合使用。auto与begin和end结合使用时,获取的迭代器类型依赖于容器类型,而c开头的获得的是const_iterator。

每个容器都支持!=和==,除了无序容器外的所有容器都支持关系运算符(>、>=、<、<=)。因此比较容器时,尽量使用!=和==。

 添加元素(array不支持这些操作):

push_back(t);//适用于所有顺序容器

push_front(t);//仅适用于list和deque

insert(p,t);//在迭代器p前面插入一个元素,返回新添加元素的迭代器

insert(p,n,t)//在迭代器p前面插入n个t元素,返回void

insert(p,b,e)//在迭代器p前面插入迭代器b和e之间的元素

还可以插入初始化列表insert(p,li);在迭代器p前面插入li初始化列表。

新标准增加的方法

emplace();//对应insert()的功能

emplace_back(t);//对应push_back()的功能

emplace_front(t);//对应push_front()的功能

emplace不是拷贝元素,而是调用参数传递给元素类型的构造函数直接在容器的内存中构造元素。

forward_list有自己专有版本的insert和emplace;forward_list不支持push_back和emplace_back;

容器中访问元素的函数back()、front()、at()和下标操作返回的都是引用。

如果想下标是合法的可以使用at(),它会检查界限。越界抛出out_of_range异常。

删除元素(array不支持这些操作):

C.pop_back();//forward_list不支持该方法,返回void

C.pop_front();//仅适用于vector和deque容器,返回void

C.erase(p);//删除p所指的元素,返回下一个位置

C.erase(b,e);//删除迭代器b和e之间的元素,返回下一个位置

C.clear();//删除所有元素,返回void

删除前请确保元素存在。

特殊的forward_list操作

forward_list是单向链表,所以在它上面添加或删除元素的操作是通过改变给定元素之后的元素来完成的。

c.push_front() 在开头插入元素
c.pop_front() 删掉开头元素
c.insert_after(pos, elem) 在pos之后插入元素, 返回新元素的位置
c.insert_after(pos, n, elem) 在pos之后插入n个元素elem, 返回第一个新元素的位置
c.insert_after(pos, begin, end) 在pos之后插入元素[begin, end), 返回第一个新元素的位置
c.insert_after(pos, initiallist) 在pos之后插入initiallist, 返回第一个新元素的位置
c.emplace_after(pos, args...) 在pos之后插入args…元素,返回第一个新元素的位置
c.emplace_front(args...) 在开头插入元素args…, 无返回值
c.erase_after(pos) 删掉pos后面那个元素
c.erase_after(begin, end) 删掉(begin, end)之间的元素

before_begin返回一个首前(off-the-beginning)迭代器。这个迭代器允许我们在链表受元素之前不存在的元素之后添加或删除元素。

大小操作

C.size();//返回容器内元素的个数

C.max_size(0;//返回容器可容纳的最大元素个数

C.empty();

C.resize(n);//调整容器的长度,使其能容纳n个元素,如果n<C.size(),删除过多的,else 添加采用值初始化的新元素;类类型需要提供默认构造函数

C.resize(n,t);//调整容器的长度,使其能容纳n个元素,新添加的元素初始化为t

总结1:容器操作可能是迭代器失效:

向容器添加元素时,

如果容器是vector和string,且存储空间被重新分配,则指向容器的迭代器。指针和引用都会失效,如果为重新分配存储空间,指向插入位置之前的元素的迭代器、指针和引用有效,之后的失效;

如果是deque,插入收尾之外的位置都会使迭代器、指针和引用失效,插入首尾时会使迭代器失效;

如果是list和forward_list,都有效。

向容器删除元素时,

如果是list和forward_list,指向其他地方的迭代器、指针和引用都有效;

如果是deque,删除收尾之外的位置都会使迭代器、指针和引用失效;删除首尾时,其他地方的迭代器、指针和引用都有效,但是删除为元素时,尾后迭代器会失效;

如果容器是vector和string,指向被删除元素之前的元素的迭代器、指针和引用仍有效,尾后迭代器会失效;

因此,不要保存尾后迭代器,添加和删除元素常常会使尾后迭代器失效;

下面是迭代器失效的例子和解决方法:

  1 #include <iostream>
2 #include <map>
3 using namespace std;
4
5 typedef map<int, int> Map;
6 typedef map<int, int>::iterator MapIt;
7
8 void print(Map &m)
9 {
10 MapIt it;
11 for(it = m.begin(); it != m.end(); it++)
12 {
13 cout << it->second << " ";
14 }
15
16 cout << endl;
17 }
18
19 void deleteValueFromMap(Map &m, int n = 5)
20 {
21 MapIt it;
22 for(it = m.begin(); it != m.end(); it++)
23 {
24 if(0 == it->second % n)
25 {
26 m.erase(it);
27 }
28 }
29 }
30
31 int main()
32 {
33 Map m;
34 int i = 0;
35 for(i = 0; i < 21; i++)
36 {
37 m[i] = i;
38 }
39
40 print(m);
41
42 deleteValueFromMap(m); // 程序崩溃
43
44 return 0;
45 }

运行上述程序, 结果程序崩溃,什么原因呢? 原来, 对于关联的容器map来说, m.erase(it);后, it就失效了, 而for循环中有it++, 自然而然会出问题啊。 那怎么办呢? 且看:

  1 #include <iostream>
2 #include <map>
3 using namespace std;
4
5 typedef map<int, int> Map;
6 typedef map<int, int>::iterator MapIt;
7
8 void print(Map &m)
9 {
10 MapIt it;
11 for(it = m.begin(); it != m.end(); it++)
12 {
13 cout << it->second << " ";
14 }
15
16 cout << endl;
17 }
18
19 void deleteValueFromMap(Map &m, int n = 5)
20 {
21 MapIt it;
22 MapIt tmp;
23 for(it = m.begin(); it != m.end(); /*不能再自增了*/)
24 {
25 if(0 == it->second % n)
26 {
27 tmp = it++;
28 m.erase(tmp);
29 }
30 else
31 {
32 it++;
33 }
34 }
35 }
36
37 int main()
38 {
39 Map m;
40 int i = 0;
41 for(i = 0; i < 21; i++)
42 {
43 m[i] = i;
44 }
45
46 print(m);
47
48 deleteValueFromMap(m); // 程序ok
49 print(m);
50
51 return 0;
52 }
53 结果为:
54 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
55 1 2 3 4 6 7 8 9 11 12 13 14 16 17 18 19
56
57
58
59
60
61 当然, 上述程序也可以继续简化为:
62
63
64
65 #include <iostream>
66 #include <map>
67 using namespace std;
68
69 typedef map<int, int> Map;
70 typedef map<int, int>::iterator MapIt;
71
72 void print(Map &m)
73 {
74 MapIt it;
75 for(it = m.begin(); it != m.end(); it++)
76 {
77 cout << it->second << " ";
78 }
79
80 cout << endl;
81 }
82
83 void deleteValueFromMap(Map &m, int n = 5)
84 {
85 MapIt it;
86 for(it = m.begin(); it != m.end(); /*不能再自增了*/)
87 {
88 if(0 == it->second % n)
89 {
90 m.erase(it++);
91 }
92 else
93 {
94 it++;
95 }
96 }
97 }
98
99 int main()
100 {
101 Map m;
102 int i = 0;
103 for(i = 0; i < 21; i++)
104 {
105 m[i] = i;
106 }
107
108 print(m);
109
110 deleteValueFromMap(m); // 程序ok
111 print(m);
112
113 return 0;
114 }
115 结果ok.

注意上面第90行m.erase(it++);这句话就不会出现迭代器失效的情况。因为it++;会返回加一之前的迭代器。

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个元素的内存空间,n如果<=capacity(),那么reserve什么也不做;n大于当前容量时,才会分配空间。
c.size()     容器中元素的个数,与capacity是不一样的;

大部分vector采用的分配策略:就是在每次需要分配内存空间时,将当前的容量capacity翻倍;
这也是不确定的,应该具体问题具体分析。

通过在一个初始为空的vector上调用push_back来创建一个n个元素的vector,所花费的时间不能超过n的常数倍。

C++基础之顺序容器的更多相关文章

  1. C++学习基础四——顺序容器和关联容器

    —顺序容器:vector,list,queue1.顺序容器的常见用法: #include <vector> #include <list> #include <queue ...

  2. C++ 顺序容器基础知识总结

    0.前言 本文简单地总结了STL的顺序容器的知识点.文中并不涉及具体的实现技巧,对于细节的东西也没有提及.一来不同的标准库有着不同的实现,二来关于具体实现<STL源码剖析>已经展示得全面细 ...

  3. C++ 顺序容器

    <C++ Primer 4th>读书笔记 顺序容器内的元素按其位置存储和访问.容器类共享公共的接口,每种容器类型提供一组不同的时间和功能折衷方案.通常不需要修改代码,只需改变类型声明,用一 ...

  4. C++ Primer 随笔 Chapter 9 顺序容器

     参考:http://www.cnblogs.com/kurtwang/archive/2010/08/19/1802912.html 1..顺序容器:vector(快速随机访问):list(快速插入 ...

  5. C++顺序容器知识总结

    容器是一种容纳特定类型对象的集合.C++的容器可以分为两类:顺序容器和关联容器.顺序容器的元素排列和元素值大小无关,而是由元素添加到容器中的次序决定的.标准库定义了三种顺序容器的类型:vector.l ...

  6. c++11の顺序容器

      容器是一种容纳特定类型对象的集合.C++的容器可以分为两类:顺序容器和关联容器.顺序容器的元素排列和元素值大小无关,而是由元素添加到容器中的次序决定的.标准库定义了三种顺序容器的类型:vector ...

  7. 细嚼慢咽C++primer(5)——顺序容器

    1 顺序容器的定义 容器是容纳特定类型对象的集合. 顺序容器:将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器. 标准库的三种顺序容器类型:vector, list 和 ...

  8. ndk学习之c++语言基础复习----C++容器、类型转换、异常与文件流操作

    继续来复习C++,比较枯燥,但是这是扎实掌握NDK开发的必经之路,不容小觑. 容器: 容器,就是用来存放东西的盒子. 常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, ...

  9. c++ 顺序容器学习

    所谓容器,就是一个装东西的盒子,在c++中,我们把装的东西叫做“元素” 而顺序容器,就是说这些东西是有顺序的,你装进去是什么顺序,它们在里面就是什么顺序. c++中的顺序容器一共有这么几种: vect ...

随机推荐

  1. Gradle——创建简单的项目

    项目 & 任务 Gradle 的一切都是基于项目和任务的. 构建由一个或多个项目组成.项目的概念很抽象,它取决于你要用Gradle 做什么 .项目可以是 一个 Jar 库或者一个 web 程序 ...

  2. React 现代化测试

    测试的动机 测试用例的书写是一个风险驱动的行为, 每当收到 Bug 报告时, 先写一个单元测试来暴露这个 Bug, 在日后的代码提交中, 若该测试用例是通过的, 开发者就能更为自信地确保程序不会再次出 ...

  3. node.js 初学 自我笔记整理 day01

     node.js   概念问题: Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境.   npm是一个node的包管理工具  ,也是一个网站  ,还是一条命令.N ...

  4. Java虚拟机一看就懂01

    Jvm内存结构 --- 线程隔离区域说明: 1.1.程序计数器 线程私有 是一块内存空间 唯一的一个在Java虚拟机规范中没有规定任何OOM情况的区域(不会OOM?) 1.2.Java虚拟机栈 线程私 ...

  5. 90001PS相关操作

    第一章   PS基础操作 1.1 PS界面介绍 (1)界面包含:菜单栏.状态样式栏.工具栏.绘图区域.工作区. (2)布局可以在左上角进行切换,区分主要为工作区的侧重点不同. (3)布局想恢复可在窗口 ...

  6. odoo模板中的t-标签用法

    在OpenERP中,通过QWeb来对模板进行渲染后加载到浏览器中,而模板中有许多的标签来定制各种需求变化,在这里记录学习过程中碰到的标签定义,以方便查询. 模板中的标签统一都是以"t-&qu ...

  7. thinkPHP中的简单文章推荐(按浏览量)功能实现

    在公司中接触到了thinkPHP框架,其中要在项目中实现文章推荐功能.记录笔记如下: 一.在Controller中获取从文章列表页进入详情页传入的文章ID值. 二.在Controller中绑定数据库查 ...

  8. Mysql系列 - 第3天:管理员必备技能(必须掌握)

    这是mysql系列第3篇文章. 环境:mysql5.7.25,cmd命令中进行演示. 在玩mysql的过程中,经常遇到有很多朋友在云上面玩mysql的时候,说我创建了一个用户为什么不能登录?为什么没有 ...

  9. I-string_2019牛客暑期多校训练营(第四场)

    题意 当a != b且a != rev(b)则认为a串与b串不相等,rev(b)表示b串的反串,例如rev(abcd) = dcba 给出一个串求出该串所有不相等的子串个数 题解 先利用后缀数组求出s ...

  10. Codefroces 939 C Convenient For Everybody

    939 C 题意:若干年以后地球会变成n个时区, 为了方便计时, 每个时区的时间从1:00开始到n:00点结束, 现在将要举行一场c赛, 每个时区内都有ai个人参加,并且比赛开始时间不早于当地时间s: ...