c++中有两种类型的容器:顺序容器和关联容器。
顺序容器主要有:vector、list、deque等。其中vector表示一段连续的内存地址,基于数组的实现,list表示非连续的内存,基于链表实现。deque与vector十分相似,采用dynamic array来管理元素,提供随机访问,但是deque的dynamic array头尾两端都开放,可以在头尾两端快速安插和删除。
关联容器主要有map和set等。map是key-value形式的,set是单值,只保存key,且map和set都会根据关键字自动排序,因为底层实现都是红黑树(RB-tree),红黑树是平衡二叉搜索树,两者皆是根据key来排序,自然也就不允许有重复的key(multimap multiset允许可重复key),也不允许修改key值。
除此之外,实际上还有三个容器适配器:stack,queue和priority_queue。stack和queue基于deque实现,priority_queue基于vector实现。适配器会对容器的接口进行重新封装,这样做一方面可以通过改装实现一些特定属性,例如stack的先进后出,就是将deque两端开放封装成一端开放,即隐藏deque的部分接口(push_front,pop_front等),另一方面其实正如适配器的本身的意义一样,提高容器的适用度,容器适配器stack或者queue都是常用的数据结构,具备基本的pop push等功能。
容器类自动申请和释放内存,我们无需new和delete操作
vector
记得我第一次见到这个容器时,我们管它叫做“向量”,也确实这个容器就像是向量一样可以存储多种数据类型的数据,且如同向量一样可以增长,实际上它的底层实现是数组,之所以可以做到增大,是因为在新增数据的时候,若数组满了,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素。
下面写了几个,用法比较简单,但是如果想了解更多可以查看它的源码,里面很多地方的实现非常简洁巧妙。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec;
//vector():创建一个空vector
vector<int> vec2(10);
//vector(int nSize):创建一个vector,元素个数为nSize
vector<int> vec3(10,1);
//vector(int nSize,const t& t):创建一个vector,元素个数为nSize,且值均为t
vector<int> vec4(vec3);
//vector(const vector&):复制构造函数
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
vector<int> vec5(a,a+10);
//vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中
//1.push_back 在数组的最后添加一个数据
//2.pop_back 去掉数组的最后一个数据
vec5.at(5);
//3.at 得到编号位置的数据
//for(vector<int>::iterator iter = vec5.begin(); iter != vec5.end();iter++)
for(auto iter = vec5.begin(); iter != vec5.end();iter++)
cout<<*iter<<" ";
cout<<endl;
//4.begin 得到数组头的指针
//5.end 得到数组的最后一个单元+1的指针
cout<<vec5.front()<<endl;
//6.front 得到数组头的引用
cout<<vec5.back()<<endl;
//7.back 得到数组的最后一个单元的引用
//8.max_size 得到vector最大可以是多大
cout<<vec5.capacity()<<endl;
//9.capacity 当前vector分配的大小
//10.size 当前使用数据的大小
//11.resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
//12.reserve 改变当前vecotr所分配空间的大小
auto ite = vec5.begin();//ite指向begin
advance(ite, 3);//ite向右移动3位,指向begin+3,即3
ite = vec5.erase(ite);//begin+3被删除后,其后的数组向前平移一位,ite依然指向begin+3,但此时是数字4
//13.erase 删除指针指向的数据项
//14.clear 清空当前的vector
//15.rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)
//16.rend 将vector反转构的结束指针返回(其实就是原来的begin-1)
cout<<vec5.empty()<<endl;
//17.empty 判断vector是否为空
vec2.swap(vec5);
//18.swap 与另一个vector交换数据
//19.insert 插入数据,向__position前插入__x,具体细节就不展开了,看参数命名大概能想到
/**
*
insert(const_iterator __position, const value_type& __x)
insert(const_iterator __position, value_type&& __x)
insert(const_iterator __position, initializer_list<value_type> __l)
insert(const_iterator __position, size_type __n, const value_type& __x)
insert(const_iterator __position, _InputIterator __first, _InputIterator __last)
*/
return 0;
}
list
list 容器是由双向链表实现的。所以不能实现快速随机访问,具体表现为不能通过at()和[ ]访问。
记一下list几个特殊操作。
函数名 | 描述 |
---|---|
push_front | 将元素插入开头 |
push_back | 将元素插入结尾 |
pop_front | 删除开头元素 |
pop_front | 删除结尾元素 |
splice | 合并两个链表 |
merge | 合并两个排序链表,若两个链表有序合并后仍有序 |
unique | 删除链表内重复的元素 |
remove | 删除链表内指定的元素 |
remove_if | 删除满足条件的元素 |
sort | 将链表排序 |
reverse | 将链表反序 |
#include <iostream>
#include <list>
using namespace std;
// a predicate implemented as a function:
bool single_digit (const int& value) { return (value<10); }
int main() {
list<int> lis(10,13);
int arr[5] = {9, 18, 5, 3, 11};
list<int> lis2(arr, arr+5);
lis.sort();
lis2.sort();
lis.merge(lis2);
// 在__position之前插入另一个链表
// lis.splice(lis.begin(), lis2);
// lis.unique();
// lis.sort();
lis.remove(9);//去掉9
lis.remove_if(single_digit);//去掉小于10的数字
// 简单的说就是将链表内的元素依次经过_Predicate检查,若返回真则去掉该元素
// 当然,实际上这个函数可以类似于python中map函数那样使用,对链表内的元素依次加工,但并不推荐这样做
/**
* lis.remove_if(_Predicate);
*
* @brief Remove all elements satisfying a predicate.
* @tparam _Predicate Unary predicate function or object.
*
* Removes every element in the list for which the predicate
* returns true. Remaining elements stay in list order. Note
* that this function only erases the elements, and that if the
* elements themselves are pointers, the pointed-to memory is
* not touched in any way. Managing the pointer is the user's
* responsibility.
*/
for(auto iter = lis.begin(); iter != lis.end();iter++)
cout<<*iter<<" ";
return 0;
}
deque
deque最大的特点就是两端开放,这一点和list相似,但是内部的实现是截然不同的,deque底层数据结构是分段数组,具体为:
deque由一些独立的区块组成,第一区块朝某方向扩展,最后一个区块朝另一方向扩展。它允许较为快速地随机访问但它不像vector一样把所有对象保存在一个连续的内存块,而是多个连续的内存块,并且维护护一个存放这些数组首地址的索引数组,如下图所示:
由图可知,实际上deque的随机访问效率会低于vector,因为vector是连续的,而deque是分段的。deque不像vector那样当内存不足时需要数据转移,deque会申请新的分段存储新的数据,并将新的分段首地址放入索引数组内。
deque的各项操作只有一下两点和vector不同:
deque不提供容量操作:capacity()和reverse()。
deque直接提供函数完成首尾元素的插入和删除。
其他均与vector相同。
如果想了解更全面的C++STL方面的知识可以点击链接。cplusplus参考手册