作者简介和专栏内容见专栏介绍:https://www.nowcoder.com/creation/manager/columnDetail/0eL5bM  麻烦看到贴子的伙伴点点赞大家点赞订阅支持下,提前祝各位offer多多,有问题评论区见~~  图解版    STL  什么是STL?   C++ STL从广义来讲包括了三类:算法,容器和迭代器。      算法包括排序,复制等常用算法,以及不同容器特定的算法。    容器就是数据的存放形式,包括序列式容器和关联式容器,序列式容器就是list,vector等,关联式容器就是set,map等。    迭代器就是在不暴露容器内部结构的情况下对容器的遍历。    解释一下什么是trivial destructor   “trivial destructor”一般是指用户没有自定义析构函数,而由系统生成的,这种析构函数在《STL源码解析》中成为“无关痛痒”的析构函数。  迭代器:++it、it++哪个好,为什么  1) 前置返回一个引用,后置返回一个对象  2) 前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低  STL迭代器如何实现   1、 迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器,除此之外,STL中迭代器一个最重要的作用就是作为容器与STL算法的粘合剂。   2、 迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器内部必须保存一个与容器相关联的指针,然后重载各种运算操作来遍历,其中最重要的是*运算符与->运算符,以及++、--等可能需要重载的运算符重载。这和C++中的智能指针很像,智能指针也是将一个指针封装,然后通过引用计数或是其他方法完成自动释放内存的功能。   3、最常用的迭代器的相应型别有五种:value type、difference type、pointer、reference、iterator catagoly;  STL中迭代器失效的情况有哪些?   以vector为例:   插入元素:   1、尾后插入:size < capacity时,首迭代器不失效尾迭代失效(未重新分配空间),size == capacity时,所有迭代器均失效(需要重新分配空间)。   2、中间插入:中间插入:size < capacity时,首迭代器不失效但插入元素之后所有迭代器失效,size ==capacity时,所有迭代器均失效。   133删除元素:   尾后删除:只有尾迭代失效。   中间删除:删除位置之后所有迭代失效。    deque 和 vector 的情况类似, 而list双向链表每一个节点内存不连续, 删除节点仅当前迭代器失效,erase返回下一个有效迭代器;map/set等关联容器底层是红黑树删除节点不会影响其他节点的迭代器, 使用递增方法获取下一个迭代器 mp.erase(iter++);   unordered_(hash) 迭代器意义不大, rehash之后, 迭代器应该也是全部失效.   STL中hashtable的实现?  STL中的hashtable使用的是开链法解决hash冲突问题的。  hashtable中的bucket所维护的list既不是list也不是slist,而是其自己定义的由hashtable_node数据结构组成的linked-list,而bucket聚合体本身使用vector进行存储。hashtable的迭代器只提供前进操作,不提供后退操作   在hashtable设计bucket的数量上,其内置了28个质数[53, 97, 193,...,429496729],在创建hashtable时,会根据存入的元素个数选择大于等于元素个数的质数作为hashtable的容量(vector的长度),其中每个bucket所维护的linked-list长度也等于hashtable的容量。如果插入hashtable的元素个数超过了bucket的容量,就要进行重建table操作,即找出下一个质数,创建新的buckets vector,重新计算元素在新hashtable的位置。  STL中hash_map扩容发生什么?   1) hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。   2) 向前操作:首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易完成前进操作,如果目前正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。  简单说一下traits技法   traits技法利用“内嵌型别“的编程技巧与编译器的template参数推导功能,增强C++未能提供的关于型别认证方面的能力。常用的有iterator_traits和type_traits。   iterator_traits   被称为特性萃取机,能够方面的让外界获取以下5中型别:      value_type:迭代器所指对象的型别    difference_type:两个迭代器之间的距离 、   pointer:迭代器所指向的型别    reference:迭代器所引用的型别    iterator_category:三两句说不清楚,建议看书     type_traits   关注的是型别的特性,例如这个型别是否具备non-trivial defalt ctor(默认构造函数)、non-trivial copyctor(拷贝构造函数)、non-trivial assignment operator(赋值运算符)和non-trivial dtor(析构函数),如果答案是否定的,可以采取直接操作内存的方式提高效率,一般来说,type_traits支持以下5中类型的判断  __type_traits<T>::has_trivial_default_constructor__type_traits<T>::has_trivial_copy_constructor__type_traits<T>::has_trivial_assignment_operator__type_traits<T>::has_trivial_destructor__type_traits<T>::is_POD_type  STL中的allocator、deallocator   1) 第一级配置器直接使用malloc()、free()和relloc(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当配置器区块小于128bytes时,为了降低额外负担,使用复杂的内存池整理方式,而不再用一级配置器;   2) 第二级配置器主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-list,各自管理大小为8~128bytes的小额区块;   3) 空间配置函数allocate(),首先判断区块大小,大于128就直接调用第一级配置器,小于128时就检查对应的free-list。如果free-list之内有可用区块,就直接拿来用,如果没有可用区块,就将区块大小调整至8的倍数,然后调用refill(),为free-list重新分配空间;   4) 空间释放函数deallocate(),该函数首先判断区块大小,大于128bytes时,直接调用一级配置器,小于128bytes就找到对应的free-list然后释放内存。  STL的两级空间配置器   1、首先明白为什么需要二级空间配置器?      我们知道动态开辟内存时,要在堆上申请,但若是我们需要频繁的在堆开辟释放内存,则就会在堆上造成很多外部碎片,浪费了内存空间;    每次都要进行调用malloc、free函数等操作,使空间就会增加一些附加信息,降低了空间利用率;    随着外部碎片增多,内存分配器在找不到合适内存情况下需要合并空闲块,浪费了时间,大大降低了效率。     于是就设置了二级空间配置器,当开辟内存<=128bytes时,即视为开辟小块内存,则调用二级空间配置器。关于STL中一级空间配置器和二级空间配置器的选择上,一般默认选择的为二级空间配置器。 如果大于128字节再转去一级配置器器。   一级配置器   一级空间配置器中重要的函数就是allocate、deallocate、reallocate 。 一级空间配置器是以malloc(),free(),realloc()等C函数执行实际的内存配置 。大致过程是:   1、直接allocate分配内存,其实就是malloc来分配内存,成功则直接返回,失败就调用处理函数   2、如果用户自定义了内存分配失败的处理函数就调用,没有的话就返回异常   3、如果自定义了处理函数就进行处理,完事再继续分配试试   二级配置器  1、维护16条链表,分别是0-15号链表,最小8字节,以8字节逐渐递增,最大128字节,你传入一个字节参数,表示你需要多大的内存,会自动帮你校对到第几号链表(如需要13bytes空间,我们会给它分配16bytes大小),在找到第n个链表后查看链表是否为空,如果不为空直接从对应的free_list中拔出,将已经拨出的指针向后移动一位。   2、对应的free_list为空,先看其内存池是不是空时,如果内存池不为空:   (1)先检验它剩余空间是否够20个节点大小(即所需内存大小(提升后) * 20),若足够则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下,这样下次再有相同大小的内存需求时,可直接拨出。   (2)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中。   (3)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中(找到相应的free_list),然后再给内存池申请内存,转到3。   3、内存池为空,申请内存   此时二级空间配置器会使用malloc()从heap上申请内存,(一次所申请的内存大小为2 * 所需节点内存大小(提升后)* 20 + 一段额外空间),申请40块,一半拿来用,一半放内存池中。   4、malloc没有成功   在第三种情况下,如果malloc()失败了,说明heap上没有足够空间分配给我们了,这时,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从比它所需节点空间大的free_list中拔除一个节点来使用。如果这也没找到,说明比其大的free_list中都没有自由区块了,那就要调用一级适配器了。   释放时调用deallocate()函数,若释放的n>128,则调用一级空间配置器,否则就直接将内存块挂上自由链表的合适位置。   STL二级空间配置器虽然解决了外部碎片与提高了效率,但它同时增加了一些缺点:   1.因为自由链表的管理问题,它会把我们需求的内存块自动提升为8的倍数,这时若你需要1个字节,它会给你8个字节,即浪费了7个字节,所以它又引入了内部碎片的问题,若相似情况出现很多次,就会造成很多内部碎片;   2.二级空间配置器是在堆上申请大块的狭义内存池,然后用自由链表管理,供现在使用,在程序执行过程中,它将申请的内存一块一块都挂在自由链表上,即不会还给操作系统,并且它的实现中所有成员全是静态的,所以它申请的所有内存只有在进程结束才会释放内存,还给操作系统,由此带来的问题有:   1.即我不断的开辟小块内存,最后整个堆上的空间都被挂在自由链表上,若我想开辟大块内存就会失败;  2.若自由链表上挂很多内存块没有被使用,当前进程又占着内存不释放,这时别的进程在堆上申请不到空间,也不可以使用当前进程的空闲内存,由此就会引发多种问题。  常见容器性质总结?   1.vector 底层数据结构为数组 ,支持快速随机访问   2.list 底层数据结构为双向链表,支持快速增删   3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问   deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:   [堆1] --> [堆2] -->[堆3] --> ...   每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.   4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时   5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)   6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现   7.set 底层数据结构为红黑树,有序,不重复   8.multiset 底层数据结构为红黑树,有序,可重复   9.map 底层数据结构为红黑树,有序,不重复   10.multimap 底层数据结构为红黑树,有序,可重复   11.unordered_set 底层数据结构为hash表,无序,不重复   12.unordered_multiset 底层数据结构为hash表,无序,可重复  13.unordered_map 底层数据结构为hash表,无序,不重复   14.unordered_multimap 底层数据结构为hash表,无序,可重复  vector与list的区别与应用?怎么找某vector或者list的倒数第二个元素   1) vector数据结构   vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库
点赞 0
评论 0
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务