面试题总结(三)、《STL源码剖析》相关面试题总结
声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的《STL源码剖析》,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助。
一、STL简介
STL提供六大组件,彼此可以组合套用:
- 容器
容器就是各种数据结构,我就不多说,看看下面这张图回忆一下就好了,从实现角度看,STL容器是一种class template。
- 算法
各种常见算法,如sort,search,copy,erase等,我觉得其中比较值得学习的就是sort,next_permutation,partition,merge sort,从实现角度看,STL算法是一种function template。 - 迭代器
扮演容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,从实现角度看,迭代器是一种将operator*,operator->,operator++,operator--等指针相关操作予以重载的class template。所有STL容器都附带有自己专属的迭代器,只有容器设计者才知道如何设计迭代器。原生指针也是一种迭代器。是设计模式的一种,所以被问到了解的设计模式可以用来凑数。 - 仿函数
行为类函数,可作为算法的某种策略,从实现角度看,仿函数是一种重载了operator()的class或class template。一般函数指针可视为狭义的仿函数。 - 配接器
一种用来修饰容器或者仿函数或迭代器接口的东西。比如queue和stack,看着像容器,其实就是deque包了一层皮。 - 配置器
负责空间配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放额class template。
二、关于容器的一些问题
2.1 当vector的内存用完了,它是如何动态扩展内存的?它是怎么释放内存的?用clear可以释放掉内存吗?是不是线程安全的?
- vector内存用完了,会以当前size大小重新申请2*size的内存,然后把原来的元素复制过去,把新元素插上,然后释放原来的内存。
- 一般我们释放vector里的元素使用clear,其实它不能释放内存,要想释放内存要使用swap,这样:
vector<type> v;//.... 这里添加许多元素给v//.... 这里删除v中的许多元素vector<type>(v).swap(v);//此时v的容量已经尽可能的符合其当前包含的元素数量//对于string则可能像下面这样string(s).swap(s);
- 引用《effective stl》的第十二条:当涉及 STL容器和线程安全性时,你可以指望一个 STL库允许多个线程同时读一个容器,以及多个线程对不同的容器做写入操作。你不能指望 STL库会把你从手工同步控制中解脱出来,而且你不能依赖于任何线程支持。必须自己去写多线程安全措施。
2.2 map是怎么实现的?查找的复杂度是多少?能不能边遍历边插入?
红黑树和散列
O(logn)
不可以,map不像vector,它在对容器执行erase操作后不会返回后一个元素的迭代器,所以不能遍历地往后删除。
2.3 写多读少应该用什么容器?
私以为是链表,链表的插入操作时常数时间复杂度,访问操作是O(n),是最适合写多读少的容器。
2.4 vector每次insert或erase之后,以前保存的iterator会不会失效?
理论上会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看作会失效,原则上是不能使用过期的内存
但是vector一般底层是用数组实现的,我们仔细考虑数组的特性,不难得出另一个结论,
insert时,假设insert位置在p,分两种情况:
a) 容器还有空余空间,不重新分配内存,那么p之前的迭代器都有效,p之后的迭代器都失效
b) 容器重新分配了内存,那么p之后的迭代器都无效咯erase时,假设erase位置在p,则p之前的迭代器都有效并且p指向下一个元素位置(如果之前p在尾巴上,则p指向无效尾end),p之后的迭代器都无效
2.5 hash_map和map的区别在哪里?
hash_map底层是散列的所以理论上操作的平均复杂度是常数时间,map底层是红黑树,理论上平均复杂度是O(logn),下面是借鉴的网上的总结:
这里总结一下,选用map还是hash_map,关键是看关键字查询操作次数,以及你所需要保证的是查询总体时间还是单个查询的时间。如果是要很多次操作,要求其整体效率,那么使用hash_map,平均处理时间短。如果是少数次的操作,使用 hash_map可能造成不确定的O(N),那么使用平均处理时间相对较慢、单次处理时间恒定的map,考虑整体稳定性应该要高于整体效率,因为前提在操作次数较少。如果在一次流程中,使用hash_map的少数操作产生一个最坏情况O(N),那么hash_map的优势也因此丧尽了。
2.6 为何map和set不能像vector一样有个reserve函数来预分配数据?
map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map声明的时候从参数中传入的Alloc。例如:
map, Alloc > intmap;
这时候在intmap中使用的allocator并不是Alloc, 而是通过了转换的Alloc,具体转换的方法时在内部通过
Alloc::rebind重新定义了新的节点分配器,详细的实现参看彻底学习STL中的Allocator。
其实你就记住一点,在map和set里面的分配器已经发生了变化,reserve方法你就不要奢望了。
2.7 当数据元素增多时(10000和20000个比较),map和set的插入和搜索速度变化如何?
算一下就知道了,首先你得知道map和set的底层都是红黑树,红黑树的搜索近似于二分查找,二分查找呢,平均时间复杂度是log2n,这里简写成logn,
狂按计算器:log(10000) = 13.3log(20000) = 14.3
看到了不,理论上平均就多了一次,所以你懂的,插起来。。。
2.8 auto_ptr可以做vector的元素呢?为什么?
不能。因为STL的标准容器规定它所容纳的元素必须是可以拷贝构造和可被转移赋值的。而auto_ptr不能,可以用shared_ptr智能指针代替。
三、关于迭代器的一些问题
3.1 traits技术原理及应用
这个问题还真不知咋讲,简单来说,在STL算法中用到迭代器时(肯定会用到),会用到迭代器所指之物的型别,假设算法中有必要声明一个变量,以迭代器所指之物为型别,但是C++只支持sizeof()、并未
支持typeof()。即使typeid(),也只能获得型别名称,不能拿来声明变量,所以这里就要用到作为”特性萃取机“的traits技术,当然,要让traits有效运作,每个迭代器设计的时候的遵守约定,自行以内嵌型别定义的方式定义出相应型别。
详情请看书或者移步Traits技术:类型的if-else-then(STL核心技术之一)
四、关于算法的一些问题
4.1 快排算法的枢轴位置是怎么选择的?
三点中值法,取整个序列的头、尾、中央三个位置的元素,以其中值作为枢轴。
4.2 简单说一下next_permutation和partition的实现?
next_permutation(下一个排列)
首先,从最尾端开始往前寻找两个相邻元素,另第一个元素为i,第二个元素为ii,且满足i<ii。找到这样一组相邻元素后,再从尾端开始往前检验,找出第一个大于i的元素j,将i,j元素对调,再将ii之后的所有元素颠倒排列。此即所求“下一个”排列组合。partition
令头端迭代器first向尾部移动,尾部迭代器last向头部移动。当first所指的值大于或等于枢轴时就停下来,当last所指的值小于或等于枢轴时也停下来,然后检验两个迭代器是否交错。如果first仍然在last左边,就将连着元素互换,然后各自调整一个位置(向中央逼近),再继续进行相同的行为。如果发现两个迭代器叫错了,表示整个序列已经调整完毕。
五、关于内存配置的一些问题
5.1 stl对于小内存块请求与释放的处理
STL考虑到小型内存区块的碎片问题,设计了双层级配置器,第一级配置直接使用malloc()和free();第二级配置器则视情况采用不同的策略,当配置区大于128bytes时,直接调用第一级配置器;当配置区块小于128bytes时,便不借助第一级配置器,而使用一个memory pool来实现。究竟是使用第一级配置器还是第二级配置器,由一个宏定义来控制。SGI STL中默认使用第二级配置器。
二级配置器会将任何小额区块的内存需求量上调至8的倍数,(例如需求是30bytes,则自动调整为32bytes),并且在它内部会维护16个free-list, 各自管理大小分别为8, 16, 24,…,128bytes的小额区块,这样当有小额内存配置需求时,直接从对应的free list中拔出对应大小的内存(8的倍数);当客户端归还内存时,将根据归还内存块的大小,将需要归还的内存插入到对应free list的最顶端。
小结:
STL中的内存分配器实际上是基于空闲列表(free list)的分配策略,最主要的特点是通过组织16个空闲列表,对小对象的分配做了优化。
1)小对象的快速分配和释放。当一次性预先分配好一块固定大小的内存池后,对小于128字节的小块内存分配和释放的操作只是一些基本的指针操作,相比于直接调用malloc/free,开销小。
2)避免内存碎片的产生。零乱的内存碎片不仅会浪费内存空间,而且会给OS的内存管理造成压力。
3)尽可能最大化内存的利用率。当内存池尚有的空闲区域不足以分配所需的大小时,分配算法会将其链入到对应的空闲列表中,然后会尝试从空闲列表中寻找是否有合适大小的区域,
但是,这种内存分配器局限于STL容器中使用,并不适合一个通用的内存分配。因为它要求在释放一个内存块时,必须提供这个内存块的大小,以便确定回收到哪个free list中,而STL容器是知道它所需分配的对象大小的,比如上述:
stl::vector array;
array是知道它需要分配的对象大小为sizeof(int)。一个通用的内存分配器是不需要知道待释放内存的大小的,类似于free(p)。
详情请看书或移步STL源码剖析---空间配置器
六、关于线程安全的一些问题
待续……
面试题总结(三)、《STL源码剖析》相关面试题总结的更多相关文章
- STL源码剖析 迭代器(iterator)概念与编程技法(三)
1 STL迭代器原理 1.1 迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...
- 《STL源码剖析》相关面试题总结
原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...
- (原创滴~)STL源码剖析读书总结1——GP和内存管理
读完侯捷先生的<STL源码剖析>,感觉真如他本人所说的"庖丁解牛,恢恢乎游刃有余",STL底层的实现一览无余,给人一种自己的C++水平又提升了一个level的幻觉,呵呵 ...
- STL源码剖析读书笔记之vector
STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...
- STL源码剖析之序列式容器
最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...
- STL源码剖析 — 空间配置器(allocator)
前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...
- 《STL源码剖析》学习之traits编程
侯捷老师在<STL源码剖析>中说到:了解traits编程技术,就像获得“芝麻开门”的口诀一样,从此得以一窥STL源码的奥秘.如此一说,其重要性就不言而喻了. 之前已经介绍过迭代器 ...
- 《STL源码剖析》读书笔记
转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...
- 通读《STL源码剖析》之后的一点读书笔记
直接逼入正题. Standard Template Library简称STL.STL可分为容器(containers).迭代器(iterators).空间配置器(allocator).配接器(adap ...
随机推荐
- 无线安全之破解WPA/WPA2 加密WiFi
准备 可以使用无线网络的Kali Linux 由于古老的WPE加密的WiFi已经几乎没有了,所以这里我就不去细说如何破解WPE加密的WiFi了.今天就来聊聊 如何来使用Kali Linux来破解Wpa ...
- C# 异步同步调用
本文将主要通过“同步调用”.“异步调用”.“异步回调”三个示例来讲解在用委托执行同一个“加法类”的时候的的区别和利弊. 首先,通过代码定义一个委托和下面三个示例将要调用的方法: /*添加的命名空间 u ...
- [HEOI2016/TJOI2016]求和(第二类斯特林数)
题目 [HEOI2016/TJOI2016]求和 关于斯特林数与反演的更多姿势\(\Longrightarrow\)点这里 做法 \[\begin{aligned}\\ Ans&=\sum\l ...
- shell检查网络出现异常、僵尸进程、内存过低后,自动重启
#!/bin/bash while : do neterror=$(/bin/netstat -a | grep -cw "CLOSE_WAIT") echo "get ...
- 2062326 齐力锋 实验四《Java面向对象程序设计Android开发》实验报告
北京电子科技学院(BESTI) 实 验 报 告 课程: 程序设计与数据结构 班级: 1623 姓名: 齐力锋 学号: 20162326 成绩: 指导教师: 娄嘉鹏/王志强 实验日期: 2017年5 ...
- CVE补丁安全漏洞【学习笔记】
更新安卓系统的CVE补丁网站:https://www.cvedetails.com/vulnerability-list/vendor_id-1224/product_id-19997/version ...
- iOS 10 系统 AVPlayer视频播放不了问题解决
使用[AVAudioPlayer Play]时出现了异常... 由于xcode中设置了当所有异常出现时的断点,,解决办法是将all改为Objective-C: libc++abi.dylib`__cx ...
- Linux Shell脚本简介
Shell 诞生于 Unix,是与 Unix/Linux 交互的工具,单独地学习 Shell 是没有意义的,请先参考Unix/Linux入门教程,了解 Unix/Lunix 基础. 近几年来,Shel ...
- iOS开发进阶 - 使用Carthage管理iOS第三方库
移动端访问不佳,请访问我的个人博客 最近在研究Swift,一不小心发现一个好的的管理iOS第三方库Carthage,就跟第一次使用CocoaPods时一样兴奋不已,在研究了大半天后终于能用了,使用起来 ...
- 汇编指令与Intrinsics指令的对应关系汇总
汇编指令与Intrinsics指令的对应关系汇总 参考网址:https://software.intel.com/sites/landingpage/IntrinsicsGuide/ 1.赋值指令:m ...