C++基础之顺序容器
顺序容器简介:
顺序容器类型 | 描述 |
---|---|
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 是容器类型名,如vector ,T 是元素类型,如int ,string 。适用于所有容器 |
C c(c2) |
创建容器c2 的副本c ;c2 和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 ,其中元素是迭代器b 和e 标示的范围内元素的副本。适用于所有容器 |
将一个容器创建为另一个容器的拷贝的两种方法:
- 直接拷贝整个容器;两个容器的类型和元素类型必须匹配;
- 拷贝迭代器指定的元素范围;不要求容器类型相同,元素类型也可以不同只要能够相互转换。
如果元素是内置类型或有默认构造函数的类类型,怎可以只提供容器大小,如果元素没有默认构造函数就必须显示指定初始值。
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++基础之顺序容器的更多相关文章
- C++学习基础四——顺序容器和关联容器
—顺序容器:vector,list,queue1.顺序容器的常见用法: #include <vector> #include <list> #include <queue ...
- C++ 顺序容器基础知识总结
0.前言 本文简单地总结了STL的顺序容器的知识点.文中并不涉及具体的实现技巧,对于细节的东西也没有提及.一来不同的标准库有着不同的实现,二来关于具体实现<STL源码剖析>已经展示得全面细 ...
- C++ 顺序容器
<C++ Primer 4th>读书笔记 顺序容器内的元素按其位置存储和访问.容器类共享公共的接口,每种容器类型提供一组不同的时间和功能折衷方案.通常不需要修改代码,只需改变类型声明,用一 ...
- C++ Primer 随笔 Chapter 9 顺序容器
参考:http://www.cnblogs.com/kurtwang/archive/2010/08/19/1802912.html 1..顺序容器:vector(快速随机访问):list(快速插入 ...
- C++顺序容器知识总结
容器是一种容纳特定类型对象的集合.C++的容器可以分为两类:顺序容器和关联容器.顺序容器的元素排列和元素值大小无关,而是由元素添加到容器中的次序决定的.标准库定义了三种顺序容器的类型:vector.l ...
- c++11の顺序容器
容器是一种容纳特定类型对象的集合.C++的容器可以分为两类:顺序容器和关联容器.顺序容器的元素排列和元素值大小无关,而是由元素添加到容器中的次序决定的.标准库定义了三种顺序容器的类型:vector ...
- 细嚼慢咽C++primer(5)——顺序容器
1 顺序容器的定义 容器是容纳特定类型对象的集合. 顺序容器:将单一类型元素聚集起来成为容器,然后根据位置来存储和访问这些元素,这就是顺序容器. 标准库的三种顺序容器类型:vector, list 和 ...
- ndk学习之c++语言基础复习----C++容器、类型转换、异常与文件流操作
继续来复习C++,比较枯燥,但是这是扎实掌握NDK开发的必经之路,不容小觑. 容器: 容器,就是用来存放东西的盒子. 常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, ...
- c++ 顺序容器学习
所谓容器,就是一个装东西的盒子,在c++中,我们把装的东西叫做“元素” 而顺序容器,就是说这些东西是有顺序的,你装进去是什么顺序,它们在里面就是什么顺序. c++中的顺序容器一共有这么几种: vect ...
随机推荐
- vscode保存代码,自动按照eslint规范格式化代码设置
# vscode保存代码,自动按照eslint规范格式化代码设置 编辑器代码风格一致,是前端代码规范的一部分.同一个项目,或者同一个小组,保持代码风格一致很必要.就拿vue项目来说,之前做的几个项目, ...
- 【JavaScript】论一个低配版Web实时通信库是如何实现的之二( EventSource篇)
前情提要 「 话说上回说到!那WebSocket大侠,巧借http之内力,破了敌阵的双工鸳鸯锁,终于突出重围. 然而玄难未了,此时web森林中飞出一只银头红缨枪,划破夜色. "莫非!?&qu ...
- div css float布局用法
float的应用与用法 想要知道float的用法,首先你要知道float在网页中的用处. 浮动的目的就是为了使得设置的对象脱离标准文档流. 什么是标准文档流? 网页在解析的时候,遵循于从上向下,从左向 ...
- 2019牛客暑期多校训练营(第十场)J - Wood Processing (斜率优化DP)
>传送门< 题意 $n$个宽度为$w_{i}$,高为$h_{i}$ 的 木块,要求分成$k$组,对于每组内的所有木块,高度都变为组内最低木块的高度,宽度保持不变,求变化的最小面积. 分析 ...
- eclipse导入的web项目不能部署到tomcat,显示为java项目
今天在eclipse中导入之前做个项目,想运行起来看看,发现导入之后没法部署. 先解决办法如下: 右键项目 勾选上面三项并选择相应的值后就变成web项目,可以部署在tomcat上了.
- odoo t标签用法
在odoo中,通过QWeb来对模板进行渲染后加载到浏览器中,而模板中有许多的标签来定制各种需求变化,在这里记录学习过程中碰到的标签定义,以方便查询. 模板中的标签统一都是以"t-" ...
- Apache和Apache Tomcat
Apache 和 Tomcat 都是web网络服务器,两者既有联系又有区别,在进行HTML.PHP.JSP.Perl等开发过程中,需要准确掌握其各自特点,选择最佳的服务器配置. Apache是web服 ...
- 读书分享全网学习资源大合集,推荐Python学习手册等三本书「01」
0.前言 在此之前,我已经为准备学习python的小白同学们准备了轻量级但超无敌的python开发利器之visio studio code使用入门系列.详见 1.PYTHON开发利器之VS Code之 ...
- 页面单击按钮弹出modaldialog然后调用ajax处理程序获取数据,给父级页面控件赋值
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="RefTopicList.asp ...
- javascript简单实现深浅拷贝
深浅拷贝知识在我们的日常开发中还算是用的比较多,但是之前的状态一直都是只曾听闻,未曾使用(其实用了只是自己没有意识到),所以今天来跟大家聊一聊js的深浅拷贝: 首先我们来了解一下javascript的 ...