首页 > 编程语言 >C++面试八股文快问快答のSTL篇

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

时间:2023-02-04 10:33:45浏览次数:43  
标签:map set 迭代 STL 元素 C++ vector 内存 快答


文章目录

  • ​​STL篇​​
  • ​​vector的底层原理(此题本人踩坑,需重视)​​
  • ​​vector中的reserve和resize的区别​​
  • ​​vector中的size和capacity的区别​​
  • ​​vector中erase方法与algorithn中的remove方法区别​​
  • ​​vector迭代器失效的情况​​
  • ​​正确释放vector的内存(clear(), swap(), shrink_to_fit())​​
  • ​​vector的内存是分配在栈上还是堆上?(此题比较刁钻,有陷阱,需要分具体情况回答,本人踩过坑)​​
  • ​​list的底层原理​​
  • ​​什么情况下用vector,什么情况下用list,什么情况下用deque​​
  • ​​priority_queue的底层原理​​
  • ​​map 、set、multiset、multimap的底层原理​​
  • ​​为何map和set的插入删除效率比其他序列容器高​​
  • ​​为何map和set每次Insert之后,以前保存的iterator不会失效?​​
  • ​​当数据元素增多时(从10000到20000),map的set的查找速度会怎样变化?​​
  • ​​map 、set、multiset、multimap的特点​​
  • ​​为何map和set的插入删除效率比其他序列容器高,而且每次insert之后,以前保存的iterator不会失效?​​
  • ​​为何map和set不能像vector一样有个reserve函数来预分配数据?​​
  • ​​set的底层实现实现为什么不用哈希表而使用红黑树?​​
  • ​​hash_map与map的区别?什么时候用hash_map,什么时候用map?​​
  • ​​迭代器失效的问题​​
  • ​​STL线程不安全的情况​​
  • ​​正确释放vector的内存(clear(), swap(),shrink_to_fit())​​
  • ​​C++ 清空队列(queue)的几种方法​​
  • ​​方法一:直接用空的队列对象赋值​​
  • ​​方法二:遍历出队列​​
  • ​​方法三:使用swap,这种是最高效的,定义clear,保持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);
}

标签:map,set,迭代,STL,元素,C++,vector,内存,快答
From: https://blog.51cto.com/u_15953444/6036767

相关文章

  • 【c/c++】isdigit()函数
    isdigit函数isdigit是计算机C(C++)语言中的一个函数,主要用于检查其参数是否为十进制数字字符。函数定义:​​intisdigit(intc)​​​函数说明:​​检查参数是否为十进制......
  • C++
    t[i]=s[j];i++;j++;注意与自增运算符的前缀形式区别,如果是:t[++i]=s[++j];则等价于:i++;j++;t[i]=s[j];贴一段课本上的介绍:1.++i:表示在引用变量i之......
  • 「 C++ 11」std::thread “invalid use of non-static member function“问题处理
    文章目录​​......
  • c++学习3 转义字符
    一“/”和某些字符的结合,产生新的字符就叫转义字符。'\0'==ASCII码值的“0”。'\n'==换行符。'\t'==tab缩进符。'\a'==发出警报。'\r'==回到行首符号。 二八进制......
  • C++ Primer 5th 阅读笔记:前言
    机器效率和编程效率Itsfocus,andthatofitsprogrammingcommunity,haswidenedfromlookingmostlyatmachineefficiencytodevotingmoreattentiontoprogram......
  • C++ 交叉编译技巧
    本文是借鉴的有关C相关的文章,由于C与C++有部分相似,此处用C距离,还没有验证过用C语言写一个小程序,在设计时希望该程序在Windows、Linux平台上都能够运行,所以使用宏来......
  • C/C++ 实现循环队列
    #include<iostream>#include<Windows.h>usingnamespacestd;#defineMAXSIZE6typedefintQElemType;typedefstruct{QElemType*base;//基地址intr......
  • C++ 宏
    目录标识符_与__的含义C++内置宏定义1.标准内置宏定义2.公共内置宏定义2.1查看GCC所有内置宏定义2.2查看G++所有内置宏定义3.系统内置宏定义4.内置操作符宏......
  • c++虚拟内存
    可以通过调用vmemalloc类型对象的括号运算符(len,name)分配大小为len,文件名为name的虚拟内存。返回首地址的迭代器。无需delete,程序结束后会自动清空文件,但是保留文件名#......
  • 代码随想录-数组-C++总结
    1.二分查找重点区分左闭右开,左闭右闭两种写法中的差异,理解循环中的不变量,这样在returnr还是l和什么时候l+1r-1什么时候不需要+1-1很重要。35.搜索插入位置-力扣(Leet......