STL常见面试题总结

一.Vector原理
1.vector是动态数组,所以和数组一样拥有一段连续的内存空间,并且起始地址不变。
2.因为vector地址空间是连续的,所以能高效的进行随机访问,时间复杂度为o(1)。
3.在vector中插入和删除元素,需要对现有元素进行复制、移动,时间复杂度为o(n)。
4.如果vector中存储的对象很大,或者构造函数复杂,那么插入等开销会很大。因为拷贝现有对象时需要调用拷贝构造函数。

二 vector扩容原理
1新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就会分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素。注意不是在原来空间后直接增加空间
2对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
3不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。

三 vector扩容为什么以2倍增长
1时间和空间的权衡,简单来说, 空间分配的多,平摊时间复杂度低,但浪费空间也多。
2 均摊添加每个元素的开销最小。

四 vector扩容为什么以1.5倍增长
理想分配方案是是在第N次分配的时候能重用之前N-1次释放的内存,如果按照1.5分配,1,1.5,3,4.5……当你需要分配4.5时,前面已分配5.5,你可以直接利用,把旧数据move过去。但选择两倍的增长比如像这样:1,2,4,8,16,32,… 每次需要申请的空间都大于用到前面释放的内存(4>2+1),无法重用。

五 vector常用接口:
• 清空vector可以使用成员函数c.clear()
• 判断vector是否为空,可以使用成员函数empty(),如果为空返回true,否则返回false
• vector输出最后一个元素的引用可以用back()成员函数,如果容器为空,则行为未定义
• vector输出第一个元素的引用可以用front()成员函数,如果容器为空,则行为未定义
• vector支持用下标访问元素,类似数组一样c[n]其中n是一个无符号整数,如果n大于容器的长度,那么行为未定义
• vector为了防止越界访问,其中有成员函数c.at(n),返回下标为n的元素的引用。如果下标越界,那么抛出out_of_range的异常
• pop_back()成员函数用来删除vector中的最后一个元素,如果容器为空会出现未定义行为。
• c.erase(it)成员函数,删除迭代器it所指向的元素,返回一个指向被删除元素之后的迭代器,如果it指向最后一个元素,那么返回以为尾后迭代器(通常是end())。若it就是end(),那么行为未定义。
• c.erase(beg,ed)删除[beg,ed)范围的元素,同时返回最后一个元素的后面的迭代器,如果ed就是尾后迭代器,那么还返回一个尾后迭代器。
• vector中begin和end函数返回的是什么?
begin返回的是第一个元素的迭代器,end返回的是最后一个元素后面位置的迭代器。前闭后开区间【)
• vector中的reserve和resize的区别
reserve是直接扩充到已经确定的大小,可以减少多次开辟、释放空间的问题(优化push_back),就可以 提高效率,其次还可以减少多次要拷贝数据的问题。reserve只是保证vector中的空间大小(capacity)最少 达到参数所指定的大小n。reserve()只有一个参数。
resize()可以改变有效空间的大小,也有改变默认值的功能。capacity的大小也会随着改变。resize()可以有 多个参数。
• vector中的size和capacity的区别
size表示当前vector中有多少个元素(finish - start);
capacity函数则表示它已经分配的内存中可以容纳多少元素(end_of_storage - start);
• vector迭代器失效的情况
当插入一个元素到vector中,由于引起了内存重新分配,所以指向原内存的迭代器全部失效。 当删除容器中一个元素后,该迭代器所指向的元素已经被删除,那么也造成迭代器失效。erase方法会返回下 一个有效的迭代器,所以当我们要删除某个元素时,需要it=vec.erase(it)。
• 正确释放vector的内存(clear(), swap(), shrink_to_fit())
vec.clear():清空内容,但是不释放内存。
vector().swap(vec):清空内容,且释放内存,想得到一个全新的vector。 vec.shrink_to_fit():请求容器降低其capacity和size匹配。 vec.clear();vec.shrink_to_fit();:清空内容,且释放内存。
• vector中erase方法与algorithn中的remove方法区别
vector中erase方法真正删除了元素,迭代器不能访问了。
remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。

六 List原理

  1. list是由双向链表实现的,因此内存空间是不连续的。
  2. list的随机访问效率不好,需要遍历元素,时间复杂度为o(n)。
    3.底层是双向链表,所以每个元素有两个指针的额外空间开销。
    4.在任何位置都能高效地插入和删除元素。只要改变元素的指针值,不需要拷贝元素。

七 vector、list、queue选择原则:
1需要对数据高效地随机访问(存取),而不在乎插入和删除的效率,采用vector
2需要大量插入、删除数据,而不关心随机访问数据,采用list
3需要随机访问数据,而且关心前后增删数据的能力,采用deque
4对数据中间的增删操作比较多:采用list,建议在排序的基础上,批量进行增删可以对运行效率提供最大的保证

八 map的底层实现
容器的数据结构是采用红黑树进行管理,插入的元素健位不允许重复,所使用的节点元素的比较函数,只对元素的健值进行比较,元素的各项数据可通过健值检索出来。map容器是一种关联容器。

九map和unordered_map的实现机理:
map:是基于红黑树来实现的(红黑树是非常严格的平衡二叉搜索树),红黑树具有自动排序功能,红黑树的每一个节点都代表着map中的一个元素,因此对于map的查找,删除和插入操作都是对红黑树的操作。
unordered_map:是基于哈希表来实现的,查找的时间复杂度是O(1),在海量数据处理中有着广泛的应用。

十 map和unordered_map的优缺点
map的优点:(1)map是有序的(2)基于红黑树实现,查找的时间复杂度是O(n)
map的缺点:空间占用率比较高,因为内部实现了红黑树,虽然提高了运行效率,但是每个节点都要保存父亲节点和孩子节点和红黑树的性质,使得每一个节点都占用大量的空间。
适用的情况:对于要有序的结构,适用map
unordered_map的优点:因为内部是哈希表来实现的,所以查找效率会非常高
unordered_map的缺点:哈希表的建立比较费时
适用的情况:对于查找问题,适用unordered_map会更好一点。

十一 Map插入元素方法
map<int, string> mapStudent;
1 mapStudent.insert(pair<int, string>(1, “student_one”));
2 mapStudent.insert(map<int, string>::value_type (1, “student_one”));
3 mapStudent[1] = “student_one”;
以上三种用法,虽然都可以实现数据的插入,但是它们是有区别的,当然了第一种和第二种在效果上是完成一样的,用insert函数插入数据,在数据的插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,但是用数组方式就不同了,它可以覆盖以前该关键字对应的值

十二 为何map和set的插入删除效率比其他序列容器高
因为不需要内存拷贝和内存移动

十三 当数据元素增多时(从10000到20000),map的set的查找速度会怎样 变化?
RB-TREE用二分查找法,时间复杂度为logn,所以从10000增到20000时,查找次数从log10000=14次到 log20000=15次,多了1次而已。

十四 map 、set、multiset、multimap的特点
set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。 map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序(因为红黑树也是 二叉搜索树,所以map默认是按key排序的),map中元素的key不允许重复,multimap可以重复。 map和set的增删改查速度为都是logn,是比较高效的。

十五 为何map和set每次insert之后, 以前保存的iterator不会失效?
存储的是结点,不需要内存拷贝和内存移动。 插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内 存的指针也不会变。 6

十六 为何map和set不能像vector一样有个reserve函数来预分配数据?
在map和set内部存储的已经不是元素本身了,而是包含元素的结点。也就是说map内部使用的Alloc并不是 map声明的时候从参数中传入的Alloc。

十七 set底层实现
底层是红黑树,set会根据待定的排序准则,自动将元素排序。不允许元素重复。

十八 set, multiset (map,multimap)
set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。因为是排序的,所以set中的元素不能被修改,只能删除后再添加。

十九 set的底层实现实现为什么不用哈希表而使用红黑树?
set中元素是经过排序的,红黑树也是有序的,哈希是无序的 如果只是单纯的查找元素的话,那么肯定要选哈希表了,因为哈希表在的最好查找时间复杂度为O(1),并且 如果用到set中那么查找时间复杂度的一直是O(1),因为set中是不允许有元素重复的。而红黑树的查找时 间复杂度为O(lgn)

二十 hash表
hash表的实现,包括STL中的哈希桶长度常数。
hash表的实现主要涉及两个问题:散列函数和碰撞处理。
1)hash function (散列函数)。最常见的散列函数:f(x) = x % TableSize .
2)碰撞问题(不同元素的散列值相同)。解决碰撞问题的方法有许多种,包括线性探测、二次探测、开链等做法。SGL版本使用开链法,使用一个链表保持相同散列值的元素。

二十一 你怎样理解迭代器?
Iterator(迭代器)用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示,相当于智能指针。

二十二 迭代器失效问题
vector 迭代器
当插入一个元素后,插入点之前的迭代器如果未扩容则不受影响,插入点之后的迭代器失效;
当插入一个元素后,capacity 如果有变化,则容器需要重新分配内存,所有迭代器都会失效;
当进行删除操作后,指向删除点及之后元素的迭代器全部失效。
deque 迭代器
在容器 begin/end 插入操作所有迭代器不受影响;
在容器非 begin/end 的位置插入和删除操作都会使指向该容器元素的所有迭代器失效。
在容器 begin/end 删除元素会使指向被删除元素的迭代器失效;
List/forward_list 迭代器
list insert 操作不会使 list 迭代器失效;
list erase 操作会使当前指向被删除元素的迭代器失效,其它迭代器正常。
set 迭代器
set 的 insert 操作不会使 set 迭代器失效;
set erase操作会使当前指向被删除元素的迭代器失效,其它迭代器正常。
map 迭代器
map 的 insert 操作不会使 map 迭代器失效;
map erase 删除操作会使当前指向被删除元素的迭代器失效

二十三 vector为何每次insert之后,以前保存的iterator不会失效?
答:iterator这里就相当于指向节点的指针,内存没有变,指向内存的指针怎么会失效呢(当然被删除的那个元素本身已经失效了)。相对于vector来说,每一次删除和插入,指针都有可能失效,调用push_back在尾部插入也是如此。因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其他内存覆盖或者内存已经被释放了。即使时push_back的时候,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。特别时在和find等算法在一起使用的时候,牢记这个原则:不要使用过期的iterator。

二十四 vector、list、map、deque用erase(it)后,迭代器的变化。
vector和deque是序列式容器,其内存分别是连续空间和分段连续空间,删除迭代器it后,其后面的迭代器都失效了,此时it及其后面的迭代器会自动加1,使it指向被删除元素的下一个元素。
list删除迭代器it时,其后面的迭代器都不会失效,将前面和后面连接起来即可。
map也是只能使当前删除的迭代器失效,其后面的迭代器依然有效。

二十五 不允许有遍历行为的容器有哪些(不提供迭代器)?
1)queue,除了头部外,没有其他方法存取deque的其他元素。
2)stack(底层以deque实现),除了最顶端外,没有任何其他方法可以存取stack的其他元素。
3)heap,所有元素都必须遵循特别的排序规则,不提供遍历功能。

二十六 stl中alloc
SGI 版本STL的默认配置器std::alloc。参见:《STL源码剖析》
1)考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层配置器。第一级配置器直接使用malloc()和free();第二级则视情况采取不同的策略:当配置区块超过128bytes时,视为“足够大”,便调用第一级配置器;当配置区块小于128bytes时,视之为“过小”,为了降低额外负担,便采用memory pool(内存池)整理方式,而不在求助于第一级配置器。
2)内存池的核心:内存池和16个自由链表(各自管理8,16,…,128bytes的小额区块)。在分配一个小区块时,首先在所属自由链表中寻找,如果找到,直接抽出分配;若所属自由链表为空,则请求内存池为所属自由链表分配空间;默认情况下,为该自由链表分配20个区块,若内存池剩余容量不足,则分配可分配的最大容量;若内存池连一个区块都无法分配,则调用chunk_alloc为内存池分配一大块区块;若内存不足,则尝试调用malloc分配,否则返回bad_alloc异常。

二十七 STL线程不安全的情况
在对同一个容器进行多线程的读写、写操作时;
在每次调用容器的成员函数期间都要锁定该容器;
在每个容器返回的迭代器(例如通过调用begin或end)的生存期之内都要锁定该容器; 在每个在容器上调用的算法执行期间锁定该容器。

二十八 priority_queue的底层原理
priority_queue:优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最 高的那一个。

  • 5
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STLC++中非常重要的一个库,也是面试中经常会涉及到的话题。以下是一些常见STL面试题: 1. STL中的容器有哪些?它们的特点和用途是什么? STL中的容器有vector、list、deque、set、map等。它们的特点和用途如下: - vector:支持快速随机访问,尾部插入和删除,适用于需要经常进行插入和删除操作的场景。 - list:支持快速插入和删除操作,但不支持随机访问,适用于需要进行频繁插入和删除操作的场景。 - deque:支持随机访问和尾部插入和删除,但不支持在中间进行插入和删除操作,适用于需要经常进行尾部操作的场景。 - set:内部元素有序,不允许重复元素,适用于需要元素有序并且不能重复的场景。 - map:内部元素有序,不允许重复元素,但每个元素包含一个键值对,适用于需要键值对有序并且键不能重复的场景。 2. STL中的迭代器有哪些?它们的特点和用途是什么? STL中的迭代器有输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器。它们的特点和用途如下: - 输入迭代器:只能读取容器中的元素,适用于遍历容器的场景。 - 输出迭代器:只能写入容器中的元素,适用于向容器中添加元素的场景。 - 前向迭代器:支持顺序遍历、读写元素和单向移动,适用于需要顺序遍历容器的场景。 - 双向迭代器:支持顺序遍历、读写元素和双向移动,适用于需要顺序遍历容器且需要在中间进行插入和删除操作的场景。 - 随机访问迭代器:支持随机读写和双向移动,适用于需要随机访问容器的场景。 3. STL中的算法有哪些?它们的特点和用途是什么? STL中的算法有排序、查找、遍历、拷贝、删除等。它们的特点和用途如下: - 排序:对容器中的元素进行排序,包括快速排序、归并排序、堆排序等。 - 查找:在容器中查找指定元素,包括二分查找、线性查找等。 - 遍历:对容器中的元素进行遍历,包括for_each、transform等。 - 拷贝:将容器中的元素拷贝至另一个容器中,包括copy、copy_if等。 - 删除:从容器中删除指定元素,包括remove、remove_if等。 4. STL中的智能指针有哪些?它们的特点和用途是什么? STL中的智能指针有unique_ptr、shared_ptr和weak_ptr。它们的特点和用途如下: - unique_ptr:独占式智能指针,只能有一个指针指向某个对象,适用于需要独占资源的场景。 - shared_ptr:共享式智能指针,多个指针可以同时指向同一个对象,适用于需要共享资源的场景。 - weak_ptr:弱引用智能指针,不控制资源生命周期,适用于需要引用计数但不独占资源的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值