STL常见面试题

1、各种容器的特点和适用情况

这里写图片描述

2、各种容器的底层机制

(1)vector

  vector就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时,会自动申请另一片更大的空间(一般是增加当前容量的50%或100%),然后把原来的数据拷贝过去,接着释放原来的那片空间;当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。

(2)list

  以结点为单位存放数据,结点的地址在内存中不一定连续,每次插入或删除一个元素,就配置或释放一个元素空间。

(3)deque

  deque动态地以分段连续空间组合而成,随时可以增加一段新的连续空间并链接起来。不提供空间保留功能。

【Note】:
(1)除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。对deque排序,为了提高效率,可先将deque复制到一个vector上排序,然后再复制回deque。

(4)map 、set、multiset、multimap

  map 、set、multiset、multimap的底层实现都是红黑树,epoll模型的底层数据结构也是红黑树,linux系统中CFS进程调度算法,也用到红黑树。

红黑树的特性:

1. 根节点是黑色;

2. 不能有两个连续的红节点;

3. 空指针是黑色;

4. 从任意一个结点出发,到后代中空指针的路径上,均包含相同数量的黑色结点。

  对于STL里的map容器,count方法与find方法,都可以用来判断一个key是否出现,count统计的是key出现的次数,因此只能为0/1,而find基于迭代器实现,以mp.end()判断是否找到要求的key。

  set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序,map中元素的key不允许重复,multimap可以重复。

(5)unordered_map、unordered_set

  C++ 11标准中加入了unordered系列的容器。unordered_map记录元素的hash值,根据hash值判断元素是否相同。map相当于java中的TreeMap,unordered_map相当于HashMap。无论从查找、插入上来说,unordered_map的效率都优于hash_map,更优于map;而空间复杂度方面,hash_map最低,unordered_map次之,map最大。

3、vector内存增长机制

  为了支持快速随机访问,vector将元素连续存储,所以如果没有空间容纳新元素,将无法添加元素。如果将已有元素移动到新空间,然后添加新元素,释放旧空间,性能会非常慢。

  为了避免这种代价,当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间(1.5或者2倍),容器预留这些空间作为备用,可以保存更多的新元素。

4、什么情况下用vector,什么情况下用list,什么情况下用deque

  vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。

  list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁。

  在需要从首尾两端进行插入或删除操作的时候需要选择deque。

5、map和set的相关问题

(1)为何map和set的插入删除效率比其他序列容器高?

  因为不需要内存拷贝和内存移动。

(2)为何每次insert之后,以前保存的iterator不会失效?

  因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。

(3)当数据元素增多时,map的set的查找速度会怎样变化?

  map的set的查找速度为logn,是比较高效的。

(4)为何map和set不能像vector一样有个reserve函数来预分配数据?

  因为在map和set内部存储的已经不是元素本身了,而是包含元素的节点。也就是说map内部使用的Alloc并不是map<Key, Data, Compare, Alloc>声明的时候从参数中传入的Alloc。

6、迭代器失效的问题

(1)插入操作
  
  对于vector和string,如果容器内存被重新分配,iterators,pointers,references失效;如果没有重新分配,那么插入点之前的iterator有效,插入点之后的iterator失效;

  对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,deque的迭代器失效,但reference和pointers有效;

  对于list和forward_list,所有的iterator,pointer和refercnce有效。

(2)删除操作

  对于vector和string,插入点之前的iterators,pointers,references有效;off-the-end迭代器总是失效的;

  对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,off-the-end失效,其他的iterators,pointers,references有效;

  对于list和forward_list,所有的iterator,pointer和refercnce有效。

7、unordered_map 与map的区别?什么时候用hash_map,什么时候用map?

构造函数:unordered_map 需要hash函数,等于函数;map只需要比较函数(小于函数).
存储结构:unordered_map 采用hash表存储,map一般采用 红黑树(RB Tree) 实现。因此其memory数据结构是不一样的。

  总体来说,unordered_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑unordered_map 。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,unordered_map 可能会让你陷入尴尬,特别是当你的unordered_map 对象特别多时,你就更无法控制了,而且unordered_map 的构造速度较慢。

8、STL容器的线程安全性

(1)线程安全的情况

1. 多个读取者是安全的。多线程可能同时读取一个容器的内容,这将正确地执行。当然,在读取时不能 有任何写入者操作这个容器;

2. 对不同容器的多个写入者是安全的。多线程可以同时写不同的容器。

(2)线程不安全的情况

1. 在对同一个容器进行多线程的读写、写操作时;

2. 在每次调用容器的成员函数期间都要锁定该容器;

3. 在每个容器返回的迭代器(例如通过调用begin或end)的生存期之内都要锁定该容器;

4. 在每个在容器上调用的算法执行期间锁定该容器。

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/80717222