C++ STL中容器的使用总结
2018年05月06日 22:19:01 sinat_27652257 阅读数:75
set
set<string>result;
result.insert(str);
result.erase(iter)//iter为集合迭代器
for(set<string>::iterator it=result.begin();it!=result.end();it++)
{
res.push_back(*it);//res为向量
}
set是用二叉树构成的,所以不能像vector一样可以用【】来访问一个元素
C++ STL中容器的使用全面总结 - CSDN博客 https://blog.csdn.net/u014465639/article/details/70241850
【C++】STL常用容器总结之一:容器与迭代器 - CSDN博客 https://blog.csdn.net/hero_myself/article/details/52304794
一、容器的定义
容器就是保存其它对象的对象,这种“对象”还包含了一系列处理“其它对象”的方法。
二、容器的种类
1、顺序容器:是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。顺序容器的元素排列次序与元素值无关,而是由元素添加到容器里的次序决定。
顺序容器包括:vector(向量)、list(列表)、deque(队列)
2、关联容器:关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。元素是有序的集合,默认在插入的时候按升序排列。
关联容器包括:map(集合)、set(映射)、multimap(多重集合)、multiset(多重映射)
3、容器适配器:本质上,适配器是使一种不同的行为类似于另一事物的行为的一种机制。容器适配器让一种已存在的容器类型采用另一种不同的抽象类型的工作方式实现。适配器是容器的接口,它本身不能直接保存元素,它保存元素的机制是调用另一种顺序容器去实现,即可以把适配器看作“它保存一个容器,这个容器再保存所有元素”。
STL 中包含三种适配器:栈stack 、队列queue 和优先级队列priority_queue
容器类自动申请和释放内存,因此无需new和delete操作。
三、不同容器的使用方法
顺序容器
1、vector(需要导入头文件#include <vector>)
只能在vector的最后进行push和pop,不能在vector的头进行push和pop。
①定义与初始化
vector<int> vec1; //默认初始化,vec1为空
vector<int> vec2(vec1); //使用vec1初始化vec2
vector<int> vec3(vec1.begin(),vec1.end());//使用vec1初始化vec2
vector<int> vec4(10); //10个值为0的元素
vector<int> vec5(10,4); //10个值为4的元素
vector<string> vec6(10,"null"); //10个值为null的元素
vector<string> vec7(10,"hello"); //10个值为hello的元素
②常用的操作方法
vec1.push_back(100); //添加元素
int size = vec1.size(); //元素个数
bool isEmpty = vec1.empty(); //判断是否为空
cout<<vec1[0]<<endl; //取得第一个元素
vec1.insert(vec1.end(),5,3); //从vec1.back位置插入5个值为3的元素
vec1.pop_back(); //删除末尾元素
vec1.erase(vec1.begin(),vec1.end());//删除之间的元素,其他元素前移
cout<<(vec1==vec2)?true:false; //判断是否相等==、!=、>=、<=...
vector<int>::iterator iter = vec1.begin(); //获取迭代器首地址
vector<int>::const_iterator c_iter = vec1.begin(); //获取const类型迭代器
vec1.clear(); //清空元素
- 反向迭代器的概念点此进入
③遍历方法
//下标法(vector的特有访问方法,一般容器只能通过迭代器访问)
int length = vec1.size();
for(int i=0;i<length;i++)
{
cout<<vec1[i];
}
cout<<endl<<endl;
//迭代器法
vector<int>::const_iterator iterator = vec1.begin();
for(;iterator != vec1.end();iterator++)
{cout<<*iterator;
}
2、list(需要导入头文件#include <list>)
①定义与初始化
list<int> lst1; //创建空list
list<int> lst2(3); //创建含有三个元素的list
list<int> lst3(3,2); //创建含有三个元素的值为2的list
list<int> lst4(lst2); //使用lst2初始化lst4
list<int> lst5(lst2.begin(),lst2.end()); //同lst4
②常用的操作方法
lst1.assign(lst2.begin(),lst2.end()); //分配值
lst1.push_back(10); //添加值
lst1.pop_back(); //删除末尾值
lst1.begin(); //返回首值的迭代器
lst1.end(); //返回尾值的迭代器
lst1.clear(); //清空值
bool isEmpty1 = lst1.empty(); //判断为空
lst1.erase(lst1.begin(),lst1.end()); //删除元素
lst1.front(); //返回第一个元素的引用
lst1.back(); //返回最后一个元素的引用
lst1.insert(lst1.begin(),3,2); //从指定位置插入3个值为2的元素
lst1.rbegin(); //返回第一个元素的前向指针
lst1.remove(2); //相同的元素全部删除
lst1.reverse(); //反转
lst1.size(); //含有元素个数
lst1.sort(); //排序
lst1.unique(); //删除相邻重复元素
③遍历方法
//迭代器法
for(list<int>::const_iterator iter = lst1.begin();iter != lst1.end();iter++)
{
cout<<*iter;
}
cout<<endl;
3、deque(需要导入头文件#include <deque>)
deque容器类与vector类似,支持随机访问和快速插入删除,它在容器中某一位置上的操作所花费的是线性时间。与vector不同的是,deque还支持从开始端插入数据:push_front()。其余类似vector操作方法的使用。
4、顺序容器使用方法小结(操作的共同点)
①添加元素
函数名 | 意义 |
c.push_back(t) | 在容器c的尾部添加值为t的元素。返回void 类型 |
c.push_front(t) | 在容器c的前端添加值为t的元素。返回void 类型 只适用于list和deque容器类型。 |
c.insert(p,t) | 在迭代器p所指向的元素前面插入值为t的新元素。返回指向新添加元素的迭代器。 |
c.insert(p,n,t) | 在迭代器p所指向的元素前面插入n个值为t的新元素。返回void 类型 |
c.insert(p,b,e) | 在迭代器p所指向的元素前面插入由迭代器b和e标记的范围内的元素。返回 void 类型 |
②容器大小的操作
函数名 | 意义 |
c.size() | 返回容器c中元素个数。返回类型为 c::size_type |
c.max_size() | 返回容器c可容纳的最多元素个数,返回类型为c::size_type |
c.empty() | 返回标记容器大小是否为0的布尔值 |
c.resize(n) | 调整容器c的长度大小,使其能容纳n个元素,如果n<c.size(),则删除多出来的元素;否则,添加采用值初始化的新元素 |
c.resize(n,t) | 调整容器c的长度大小,使其能容纳n个元素。所有新添加的元素值都为t |
③访问元素
函数名 | 意义 |
c.back() | 返回容器 c 的最后一个元素的引用。如果 c 为空,则该操作未定 义 |
c.front() | 返回容器 c 的第一个元素的引用。如果 c 为空,则该操作未定义 |
c[n] | 返回下标为 n 的元素的引用。如果 n <0 或 n >= c.size(),则该操作未定义 只适用于 vector 和 deque 容器 |
c.at(n) | 返回下标为 n 的元素的引用。如果下标越界,则该操作未定义 只适用于 vector 和 deque 容器
|
④删除元素
函数名 | 意义 |
c.erase(p) | 删除迭代器p所指向的元素。返回一个迭代器,它指向被删除元素后面的元素。如果p指向容器内的最后一个元素,则返回的迭代器指向容器超出末端的下一位置。如果p本身就是指向超出末端的下一位置的迭代器,则该函数未定义 |
c.erase(b,e) | 删除迭代器b和e所标记的范围内所有的元素。返回一个迭代器,它指向被删除元素段后面的元素。如果e本身就是指向超出末端的下一位置的迭代器,则返回的迭代器也指向容器的超出末端的下一位置 |
c.clear() | 删除容器c内的所有元素。返回void |
c.pop_back() | 删除容器c的最后一个元素。返回void。如果c为空容器,则该函数未定义 |
c.pop_front() | 删除容器c的第一个元素。返回void。如果c为空容器,则该函数未定义 只适用于 list 或 deque 容器 |
⑤赋值与swap
函数名 | 意义 |
c1 = c2 | 删除容器c1的所有元素,然后将c2的元素复制给c1。c1和c2的类型(包括容器类型和元素类型)必须相同 |
c1.swap(c2) | 交换内容:调用完该函数后,c1中存放的是 c2 原来的元素,c2中存放的则是c1原来的元素。c1和c2的类型必须相同。该函数的执行速度通常要比将c2复制到c1的操作快 |
c.assign(b,e) | 重新设置c的元素:将迭代器b和e标记的范围内所有的元素复制到c中。b和e必须不是指向c中元素的迭代器 |
c.assign(n,t) | 将容器c重新设置为存储n个值为t的元素 |
常用操作的共同点小结
| |||||||||||||||
全部都可用 |
| ||||||||||||||
| |||||||||||||||
只适用于 list或deque |
| ||||||||||||||
| |||||||||||||||
只适用于 vector和 deque |
|
关联容器
1、map(需要导入头文件#include <map>)
2、set(需要导入头文件#include <set>)
顺序容器和关联容器共有函数
empty() size()
begin | 该函数两个版本返回iterator或const_iterator,引用容器第一个元素 |
end | 该函数两个版本返回iterator或const_iterator,引用容器最后一个元素后面一位 |
rbegin | 该函数两个版本返回reverse_iterator或const_reverse_iterator,引用容器最后一个元素 |
rend | 该函数两个版本返回reverse_iterator或const_reverse_iterator,引用容器第一个元素前面一位 |
erase | 从容器中清除一个或几个元素 |
clear | 清除容器中所有元素 |
容器适配器
不支持迭代器
要使用适配器,需要加入一下头文件:
#include <stack> //stack
#include<queue> //queue、priority_queue
种类 | 默认顺序容器 | 可用顺序容器 | 说明 |
stack | deque | vector、list、deque | |
queue | deque | list、deque | 基础容器必须提供push_front()运算 |
priority_queue | vector | vector、deque | 基础容器必须提供随机访问功能 |
三种容器适配器提供的操作
1.stack
-
stack<int> s;
-
stack< int, vector<int> > stk; //覆盖基础容器类型,使用vector实现stk
-
s.empty(); //判断stack是否为空,为空返回true,否则返回false
-
s.size(); //返回stack中元素的个数
-
s.pop(); //删除栈顶元素,但不返回其值
-
s.top(); //返回栈顶元素的值,但不删除此元素
-
s.push(item); //在栈顶压入新元素item
- 2.queue & priority_queue
漏了一个 pop()
//弹出队列的第一个元素(队顶元素),注意此函数并不返回任何值
-
queue<int> q; //priority_queue<int> q;
-
q.empty(); //判断队列是否为空
-
q.size(); //返回队列长度
-
q.push(item); //对于queue,在队尾压入一个新元素
-
//对于priority_queue,在基于优先级的适当位置插入新元素
-
//queue only:
-
q.front(); //返回队首元素的值,但不删除该元素
-
q.back(); //返回队尾元素的值,但不删除该元素
-
//priority_queue only:
-
q.top(); //返回具有最高优先级的元素值,但不删除该元素
队列和优先级队列支持的操作 | |
---|---|
q.empty() | 如果队列为空,则返回true,否则返回false |
q.size() | 返回队列中元素的个数 |
q.pop() | 删除队首元素,但不返回其值 |
q.front() | 返回队首元素,但不删除该元素 该操作只适用于队列 |
q.back() | 返回对尾元素,但不删除该元素 该操作只适用于队列 |
q.top() | 返回具有最高优先级的元素值,但不删除该元素 该操作只适用于优先级队列 |
q.push(item) | 对于queue,在队尾插入一个新的元素, 对于priority_queue,在基于优先级的适当位置插入新元素 |
priority_queue允许用户为队列中存储的元素设置优先级。这种队列不是直接将新元素放置在队列尾部,而是放在比它优先级低的元素前面。
四、各种容器的元素在内存中的储存方式
1、vector(向量):相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,
由于它的特性我们完全可以将vector 看作动态数组。在创建一个vector 后,它会自动在内存中分配一块连续的
内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定,这个大小即capacity ()函数
的返回值。当存储的数据超过分配的空间时vector 会重新分配一块内存块,但这样的分配是很耗时的,效率非常低。
2、deque(队列):它不像vector 把所有的对象保存在一块连续的内存块,而是采用多个连续的存储块,并且在一个
映射结构中保存对这些块及其顺序的跟踪。向deque 两端添加或删除元素的开销很小,它不需要重新分配空间。
3、list(列表):是一个线性链表结构,它的数据由若干个节点构成,每一个节点都包括一个信息块(即实际存储的数据)、
一个前驱指针和一个后驱指针。它无需分配指定的内存大小且可以任意伸缩,这是因为它存储在非连续的内存空间中,
并且由指针将有序的元素链接起来。
4、set, multiset, map, multimap 是一种非线性的树结构,具体的说采用的是一种比较高效的特殊的
平衡检索二叉树—— 红黑树结构。
五、各种容器优劣分析
1、Vector:
优点:
A、支持随机访问,访问效率高和方便,它像数组一样被访问,即支持[ ] 操作符和vector.at()。
B、节省空间,因为它是连续存储,在存储数据的区域都是没有被浪费的,但是要明确一点vector 大多情况下并不是满存的,在未存储的区域实际是浪费的。
缺点:
A、在内部进行插入、删除操作效率非常低。
B、只能在vector 的最后进行push 和pop ,不能在vector 的头进行push 和pop 。
C、 当动态添加的数据超过vector 默认分配的大小时要进行内存的重新分配、拷贝与释放,这个操作非常消耗能。
2、List:
优点:
不使用连续的内存空间这样可以随意地进行动态操作,插入、删除操作效率高;
缺点:
A、不能进行内部的随机访问,即不支持[ ] 操作符和vector.at(),访问效率低。
B、相对于verctor 占用更多的内存。
3、Deque:
优点:
A、支持随机访问,方便,即支持[ ] 操作符和vector.at() ,但性能没有vector 好;
B、可以在两端进行push 、pop 。
缺点:
在内部进行插入、删除操作效率低。
综合:
vector 的查询性能最好,并且在末端增加数据也很好,除非它重新申请内存段;适合高效地随机存储。
list 是一个链表,任何一个元素都可以是不连续的,但它都有两个指向上一元素和下一元素的指针。所以它对插入、删除元素性能是最好的,而查询性能非常差;适合 大量地插入和删除操作而不关心随机存取的需求。
deque 是介于两者之间,它兼顾了数组和链表的优点,它是分块的链表和多个数组的联合。所以它有被list 好的查询性能,有被vector 好的插入、删除性能。 如果你需要随即存取又关心两端数据的插入和删除,那么deque 是最佳之选。
4、关联容器的特点是明显的,相对于顺序容器,有以下几个主要特点:
A, 其内部实现是采用非线性的二叉树结构,具体的说是红黑树的结构原理实现的;
B, set 和map 保证了元素的唯一性,mulset 和mulmap 扩展了这一属性,可以允许元素不唯一;
C, 元素是有序的集合,默认在插入的时候按升序排列。
基于以上特点,
A, 关联容器对元素的插入和删除操作比vector 要快,因为vector 是顺序存储,而关联容器是链式存储;比list 要慢,是因为即使它们同是链式结构,但list 是线性的,而关联容器是二叉树结构,其改变一个元素涉及到其它元素的变动比list 要多,并且它是排序的,每次插入和删除都需要对元素重新排序;
B, 关联容器对元素的检索操作比vector 慢,但是比list 要快很多。vector 是顺序的连续存储,当然是比不上的,但相对链式的list 要快很多是因为list 是逐个搜索,它搜索的时间是跟容器的大小成正比,而关联容器 查找的复杂度基本是Log(N) ,比如如果有1000 个记录,最多查找10 次,1,000,000 个记录,最多查找20 次。容器越大,关联容器相对list 的优越性就越能体现;
C, 在使用上set 区别于vector,deque,list 的最大特点就是set 是内部排序的,这在查询上虽然逊色于vector ,但是却大大的强于list 。
D, 在使用上map 的功能是不可取代的,它保存了“键- 值”关系的数据,而这种键值关系采用了类数组的方式。数组是用数字类型的下标来索引元素的位置,而map 是用字符型关键字来索引元素的位置。在使用上map 也提供了一种类数组操作的方式,即它可以通过下标来检索数据,这是其他容器做不到的,当然也包括set 。(STL 中只有vector 和map 可以通过类数组的方式操作元素,即如同ele[1] 方式)。
六、其它
1、STL中hashtable,hashset,hashmap,set,map,unordered_map、unordered_set的区别
①hashtable,hashset,hashmap,unordered_map、unordered_set与set,map的根本区别在于底层的实现不同,前者底层都是由hashtable来提供的,后者都是由红黑树来提供;前者查询时间虽然是O(1),但是并不是前者查询时间一定比后者短,因为实际情况中还要考虑到数据量,而且前者的hash函数的构造速度也没那么快,所以不能一概而论,应该具体情况具体分析。后者保证了一个稳定的动态操作时间,查询、插入、删除都是O(logN),最坏和平均都是。
②unordered_map、unordered_set在C++11的时候被引入标准库了,而hashset,hashmap没有,所以建议还是使用unordered_map比较好。
③hashset与set相比较,它里面的元素不一定是经过排序的,而是按照所用的hash函数分派的,它能提供更快的搜索速度(当然跟hash函数有关)。(hashmap与map区别一样)
七、迭代器
vector<int>::iterator iter; // 将iter声明为int类型的向量迭代器
begin返回的迭代器指向容器内第一个元素,end返回的迭代器指向容器内“最后一个元素的下一个位置”,
end操作返回的迭代器并不指向vector中任何实际的元素,它只是起到一个哨兵的作用,表示我们已经处理完vector中的所有元素。