STL容器简介

1、STL简介   

2、STL中的容器及底层实现: 

3、STL容器的公用函数:

下一篇:STL容器之vector、list


1、STL简介   

    STL提供六大组件,彼此可以组合套用:
  (1)容器:容器用来存放数据,从实现的角度看,STL容器是一种类模板,包括vector、list、deque、set、map等。
  (2)算法:STL算法是一种函数模板,各种常用的算法如sort、search、copy、erase等。
  (3)迭代器:扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,共有五种类型,以及其它衍生变化。从实现的角度看,迭代器是一种将operator*、operator->、operator++、operator--等指针相关操作予以重载的类模板,所有STL容器都附带有自己专属的迭代器。
  (4)仿函数:行为类似函数,可以作为算法的某种策略。从实现的角度看,仿函数是一种重载了operator()的类或者类模板。
  (5)适配器:一种用来修饰容器或仿函数或迭代器接口的东西。例如stack和queue,虽然看似容器,但是只能算一种容器适配器,因为它们的底层完全借助deque或list,所有的操作都由底层的deque或list供应。
  (6)分配器:负责空间配置和管理,从实现的角度讲,配置器是一个实现了动态空间配置、空间管理、空间释放的类模板。通常,分配器是由两级分配器构成的内存管理器,当申请的内存大于128B时,就启用第一级分配器通过malloc直接向系统的堆空间分配,如果申请的内存小于128B时,就启用第二级分配器,从一个预先定义好的内存池取一块内存交付给用户,这个内存池由16个不同大小(8~128B)空闲列表组成,分配器会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲列表取表头块给用户。这种做法有两个优点:1)小对象快速分配,小对象从内存池分配;2)小对象从内存池分配,避免了内部碎片的产生。    

    六大组件关系:容器(containers)通过分配器(allocators)取得数据储存空间,算法(algorithms)通过迭代器(iterators)存取容器的内容,仿函数(functors)可以协助算法完成不同的策略变化,适配器(adapters)可以修饰或套接仿函数。

2、STL中的容器及底层实现:

  (1)静态数组array:

  • array是在C语言的数组上封装了一个class,它比C语言的数组安全,它支持快速随机访问,有着固定大小,因此无法增加或者移除元素而改变其大小,它只允许你替换元素值。此外,array不支持(也就是不允许你指定)分配器。

  (2)动态数组vector:

  • vector的数据安排和操作方式和array非常相似,两者唯一的区别在于空间运用的灵活性。array是静态空间,一旦配置了就不能改变大小,要换个更大的空间,需要用户自己操作(重新配置、元素移动、释放空间)。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新的元素。
  • 为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充,这便是容量的概念。换句话说,一个vector的容量永远大于或等于其大小,一旦容量等于其大小,便是满载,下次增加元素时就得另外找空间。
  • 当动态增加时,并不是在原空间之后增加(因为无法保证原空间之后有空间的空间),而是以原大小的两倍另外配置一块较大的空间,然后将内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对于vector的操作,一旦引起空间重新配置,指向原vector的所有迭代器就失效了,务必小心。
  • vector的迭代器就是普通指针。

  (3)双向链表list:

  • list底层数据结构为双向链表,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。因此list的对于空间的使用一点也不浪费,对于任何位置的元素插入或元素移除,list永远是常数时间。
  • list不能像vector那样使用普通指针作为迭代器,因为其节点不保证在存储空间中连续存在。list的迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员存取等操作。
  • list有一个重要性质:插入操作和接合操作都不会造成原有的list迭代器失效,这在vector中是不成立的。
  • vector和list比较:1)vector进行随机存取时,时间复杂度为o(1),在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。2)list的随机存取非常没有效率,时间复杂度为o(n),插入和删除时时间复杂度为O(1)。

  (4)双向动态数组deque:

  • vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作。
  • deque和vector的最大差异:一在于允许常数时间内对头端进行元素的插入或移除操作;二在于deque没有容量的概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。
  • deque的迭代器不是普通指针,其复杂度比vector的迭代器更大,除非有必要,我们应尽可能地使用vector而非deque。
  • deque是由一段一段的定量连续空间构成,一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串在整个deque的头端或尾端。deque最大的任务便是在分段的定量连续空间上,维护其整体连续的假像,并提供随机存取的接口。避开了“重新配置、复制、释放”的轮回,代价是复杂的迭代器结构,从这点上来说deque优于vector。
  • deque采用一块所谓的map(不是STL中的map)作为中央控制区,这里的map是一小块连续的空间,每个元素都是一个指针,指向另一段较大的连续线性空间,称为缓冲区。缓冲区才是deque的存储空间主体。当map使用率已经满载,则需要找一块更大的空间作为map。deque看起来像是list和vector的结合品。

  (5)单向链表forward_list:

  • 单向链表forward_list和双向链表list的区别是forward_list的迭代器属于单向迭代器,而list的迭代器属于双向迭代器。因此forward_list的功能也受到了许多限制,但是forward_list所消耗的空间更小,某些操作更快,不失为一种选择。
  • 单向链表不提供成员函数size(),也没有指向最末元素的锚点,因此单向链表不提供处理最末元素的成员函数,如back()、push_back()和pop_back()等。
  • 根据STL的习惯,插入操作会将新元素插入与指定位置之前,而非之后,作为单向链表,没有办法可以回头定出前一个位置,为此,单向链表提供了insert_after()和erase_after()函数,将元素安插于或删除元素于指定位置之后。
  • 基于同样的考虑,单向链表不提供push_back(),只提供push_front(),即单向链表采用的是头差法,因此单向链表的次序会和元素插入的次序相反。

  (6)有序集合set和multiset:

  • set的底层数据结构为红黑树,所有元素都会根据键值自动排序。set不像map那样可以同时拥有实值和键值,set元素的键值就是实值,实值就是键值。
  • 我们不能通过set的迭代器改变set的元素值,因为set的元素值就是set的键值,关系到set的排列规则,如果任意改变元素值,会严重破坏set的组织。set的迭代器是一种constant iterator。
  • set不允许元素有重复值,而multiset允许元素有重复值,multiset的插入操作是底层机制红黑树的insert_equal(),而set的插入是insert_unique()。

  (7)有序字典map和multimap:

  • map的底层数据结构为红黑树,所有元素都会根据元素的键值自动排序。map的所有元素都是pair,同时拥有实值和键值,pair的第一个元素是键值,第二个元素是实值。
  • 我们不能通过map的迭代器改变map的键值,键值关系到map的排列规则,如果任意改变元素值,会严重破坏set的组织。但是我们可以通过迭代器改变元素的实值,因此map的迭代器既不是一种constant iterator,也不是一种mutable iterator。
  • map不允许键有重复值,而multiset允许键有重复值,multimap的插入操作是底层机制红黑树的insert_equal(),而map的插入是insert_unique()。

  (8)无序集合unordered_set、unordered_multiset和无序字典unordered_map、unordered_multimap:

  • 无序集合和无序字典的底层实现机制为hash table,我们知道hash table存在的一个问题是如何处理冲突,有三种方法可以处理碰撞问题,分别是:线性探测、二次探测、拉链法。而STL中hash table所采用的方法为拉链法。
  • 使用hash table时,必须要指定key和value(如果是unorderd_map或unordered_multimap)的类型、哈希函数、可有可无的定义“等价准则“的一个判断式、可有可无的分配器。
  • 一个unordered_map或unordered_multimap的可以拥有任何类型的key和value,只要它们满足以下两个条件:1)key和value都必须可被复制或可被搬移;2)key必须可被“等价准则”拿来比较。
  • 如果没有指定哈希函数,则使用默认的哈希函数,可用于所有的整型、浮点型、string、指针及若干特殊类型,至于其它类型,比如用户自定义的类型,你必须传入你自己的哈希函数。
  • unordered_multimap、unordered_multiset与unordered_map、unordered_set的唯一区别是,前两种容器采用底层hash table的insert_equal(),而后两种容器采用insert_unique()。

  (9)栈stack和队列queue:

  • 底层一般用listdeque实现,封闭头部即可,SGI STL缺省情况下是以deque作为stack和queue的底层数据结构。不用vector的原因应该是容量大小有限制,扩容耗时,而使用deque或list时扩容时间消耗少;
  • stack和queue不允许有遍历行为,因此stack不提供迭代器;
  • 由于stack和queue是以底部容器完成其所有工作,而具有“修改某物接口,形成另一种风貌”之性质者,称为adapter(配接器),因此stack和queue往往不被归类为容器,而被归类为配接器。

  (10)优先级队列priority_queue:

  • 优先级队列带有权值观念,其内的元素并非按照推入的次序排列,而是自动按照元素的权值排列,权值最高者,排在最前面。
  • 缺省情况下,优先级队列使用一个大顶堆(max heap)完成,这个大顶堆使用一个vector保存,大顶堆可以满足优先级队列所需的“依权值高低自动递减排序”的特性。
  • 和queue和stack一样,priority_queue也是一个配接器,不允许有遍历行为,因为也不提供迭代器。

3、STL容器的公共函数:

vector<int> v1 = {x1,x2,...}第一种初始化方式
vector<int> v2{x1,x2,...}第二种初始化方式

list<int> l;

vecto<int> v3(l.begin(), l.end())

第三种初始化方式,以另种容器的元素为初值

int carray[] = {x1,x2,...}

set<int> c(begin(carray), end(carray))

第四种初始化方式,以某个C语言的array的元素作为初值
for(const auto& elem : coll){ elem }只读的方式访问元素,auto自定确定容器的元素类型
for(auto& elem : coll){ elem }可写的方式访问元素
for(auto pos = coll.cbegin(); pos!=coll.cend(); ++pos){  *pos  }迭代器的方式只读访问元素
for(auto pos = coll.begin(); pos!=coll.end(); ++pos){ *pos }迭代器的方式可写访问元素
c.empty()判断容器是否为空,为空则返回true,否则返回false
c.size()返回容器的元素数量,不适合forward_list<>
c.begin()返回一个iterator,指向第一个元素
c.end()返回一个iterator,指向最末元素的下一个位置
c.cbegin()返回一个const iterator,指向第一个元素
c.cend()返回一个const iterator,指向最末元素的下一个位置
c.clear()移除所有元素,令容器为空,不适用于array<>

参考:《C++标准库》

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值