C++面试八股文快问快答のSTL篇

Posted 谁吃薄荷糖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++面试八股文快问快答のSTL篇相关的知识,希望对你有一定的参考价值。

STL篇

vector的底层原理(此题本人踩坑,需重视)

vector底层是一个动态数组,包含三个迭代器,start和finish之间是已经被使用的空间范围,end_of_storage是整块连续空间包括备用空间的尾部。

当空间不够装下数据(vec.push_back(val))时,会自动申请另一片更大的空间(1.5倍或者2倍),然后把原来的数据拷贝到新的内存空间,接着释放原来的那片空间[vector内存增长机制]。

当释放或者删除(vec.clear())里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。因此,对vector的任何操作一旦引起了空间的重新配置,指向原vector的所有迭代器会都失效了。

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中erase方法与algorithn中的remove方法区别

vector中erase方法真正删除了元素,迭代器不能访问了
remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。

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的内存是分配在栈上还是堆上?(此题比较刁钻,有陷阱,需要分具体情况回答,本人踩过坑)

需要分情况来回答,如下:

std::vector<T> vec;
std::vector<T>* Vec = new std::vector<T>();
std::vector<T*> vec;

对于std::vector<T> vec;

vec在栈上(stack),而其中的元素T保存在堆上(heap);

对于std::vector<T>* Vec = new std::vector<T>();

vec和其中的元素T都保存在堆上;

对于std::vector<T*> vec;

vec在栈上(stack),而其中的元素T保存在堆上(heap);和第一种情况类似。

list的底层原理

ist的底层是一个双向链表,使用链表存储数据,并不会将它们存储到一整块连续的内存空间中。恰恰相反,各元素占用的存储空间(又称为节点)是独立的、分散的,它们之间的线性关系通过指针来维持,每次插入或删除一个元素,就配置或释放一个元素空间。
list不支持随机存取,如果需要大量的插入和删除,而不关心随即存取。

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

vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。
list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁,比如写多读少的场景。
需要从首尾两端进行插入或删除操作的时候需要选择deque。

priority_queue的底层原理

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

map 、set、multiset、multimap的底层原理

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

红黑树的特性:

每个结点或是红色或是黑色;
根结点是黑色;
每个叶结点是黑的;
如果一个结点是红的,则它的两个儿子均是黑色;
每个结点到其子孙结点的所有路径上包含相同数目的黑色结点。

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

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

为何map和set每次Insert之后,以前保存的iterator不会失效?

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

当数据元素增多时(从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就像指向结点的指针,内存没变,指向内存的指针也不会变。

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

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

set的底层实现实现为什么不用哈希表而使用红黑树?

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

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

构造函数:hash_map需要hash function和等于函数,而map需要比较函数(大于或小于)。
存储结构:hash_map以hashtable为底层,而map以RB-TREE为底层。
总的说来,hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别。而map的查找速度是logn级别。但不一定常数就比log小,而且hash_map还有hash function耗时。
如果考虑效率,特别当元素达到一定数量级时,用hash_map。
考虑内存,或者元素数量较少时,用map。

迭代器失效的问题

插入操作:

对于vector和string,如果容器内存被重新分配,iterators,pointers,references失效;如果没有重新分配,那么插入点之前的iterator有效,插入点之后的iterator失效;
对于deque,如果插入点位于除front和back的其它位置,iterators,pointers,references失效;当我们插入元素到front和back时,deque的迭代器失效,但reference和pointers有效;
对于list和forward_list,所有的iterator,pointer和refercnce有效。 删除操作:
对于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有效。
对于关联容器map来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用,否则会导致程序无定义的行为。

STL线程不安全的情况

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

正确释放vector的内存(clear(), swap(),shrink_to_fit())

#include<iostream>
#include<vector>
using namespace std;
int main()

    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);

    cout << "size:" << v.size() << endl;
    cout << "capacity:" << v.capacity() << endl;
    
    v.clear();
    cout << "after clear size:" << v.size() << endl;
    cout << "after clear capacity:" << v.capacity() << endl;
    return 0;


//输出

size:5
capacity:6
after clear size:0
after clear capacity:6

详细请查阅:实战c++中的vector系列–正确释放vector的内存(clear(), swap(), shrink_to_fit())

C++ 清空队列(queue)的几种方法

C++中的queue自身是不支持clear操作的,但是双端队列deque是支持clear操作的。

方法一:直接用空的队列对象赋值

queue<int> q1;
// process
// ...
q1 = queue<int>();

方法二:遍历出队列

while (!Q.empty()) Q.pop();

方法三:使用swap,这种是最高效的,定义clear,保持STL容器的标准。

void clear(queue<int>& q) 
	queue<int> empty;
	swap(empty, q);

引经据典

感谢以下博主的文章,如有遗漏,请联系我添加,谢谢!


https://blog.csdn.net/weixin_43519366/article/details/118634870

https://blog.csdn.net/qq_31349683/article/details/112390579

以上是关于C++面试八股文快问快答のSTL篇的主要内容,如果未能解决你的问题,请参考以下文章

C++面试八股文快问快答の基础篇

快问快答,MySQL面试夺命20问

快问快答,计算机网络面试夺命20问

大厂面试快问快答,10分钟搞定MySQL夺命20问,你都能接住吗?

大厂面试快问快答,10分钟搞定MySQL夺命20问,你都能接住吗?

大厂面试快问快答,10分钟搞定MySQL夺命20问,你都能接住吗?