您的位置:首页 > 编程语言 > C语言/C++

C++ STL常见容器(vector list deque 顺序容器篇)

2020-01-14 09:42 429 查看

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参考手册

  • 点赞
  • 收藏
  • 分享
  • 文章举报
Wlxxs 发布了5 篇原创文章 · 获赞 3 · 访问量 438 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: