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 ...
随机推荐
- MonkeyRunner 第一天
1.安装集成Android SDK的环境(如Eclipse),主要是为了android的模拟器,安装python编译环境,MonkeyRunner是基于Jython 2.使用Eclipse打开Andr ...
- 2019Hexo博客Next主题深度美化 打造一个炫酷博客(2)-奥怪的小栈
219/8/1 更新 本文转载于:奥怪的小栈 这篇文章告诉你在搭建好博客后,面对网上千篇一律的美化教程怎么才能添加自己独特点,使人眼前一亮. 本站基于HEXO+Github搭建. 所以你需要准备好HE ...
- Day 07--最终修改(三)
2.明天着重学一下逻辑层的语法,以及界面层的数据绑定,与队友交流进度 3.今天修改也终于完成,除了搞c++以外的全部身心都放在这个东西身上也觉得它有点难搞,说明计算机不是吃素的.甚至在使用xml语法的 ...
- CodeForces 909E
题意略. 思路:一个拓扑排序的题目吧.肯定是要先处理后面那个任务,再处理前面那个任务,我的思路是尽力先把主处理器能操作的先操作完,然后再把副处理器能操作完的再操作完,这样循环,直到处理完全部. 定义t ...
- 百度地图小Demo---获取当前地址以及拖拽显示地址
1.效果图 2.源码 主要使用百度地图的JavaScript API文件,以及一个JQuery文件. <!doctype html> <html lang="en" ...
- [Revit]开始:编写一个简单外部命令
1 创建项目 以Visual Stidio作为开发工具,测试平台为Revit 2017 打开VS,创建一个C# .NET Framwork类库项目,选择..net框架版本为.NET Framwork ...
- Badboy中创建Suite, test, step和Template
参考: http://leafwf.blog.51cto.com/872759/1111744 http://www.51testing.com/html/00/130600-1367743.html ...
- 洛谷P1169 [ZJOI2007]棋盘制作 悬线法 动态规划
P1169 [ZJOI2007]棋盘制作 (逼着自己做DP 题意: 给定一个包含0,1的矩阵,求出一个面积最大的正方形矩阵和长方形矩阵,要求矩阵中相邻两个的值不同. 思路: 悬线法. 用途: 解决给定 ...
- HDU 5919 - Sequence II (2016CCPC长春) 主席树 (区间第K小+区间不同值个数)
HDU 5919 题意: 动态处理一个序列的区间问题,对于一个给定序列,每次输入区间的左端点和右端点,输出这个区间中:每个数字第一次出现的位子留下, 输出这些位子中最中间的那个,就是(len+1)/2 ...
- CodeForces 1082 F Speed Dial
题目传送门 题意:现在有n个电话号码,每个电话号码为si,拨打次数为pi. 现在有k 个快捷键,每次拨打号码之前可以先按一次快捷键,然后再输入数字,现在问输入数字次数是多少.快捷键的号码可以不在电话簿 ...