C++STL常见面试题总结

2023-11-11

1. STL的介绍:

STL = 标准模板库,提高程序的 开发效率 和 复用性。

STL包含的 6大组件:
容器、迭代器、算法、仿函数、适配器、空间配置器。

各组件的作用:
容器: 用于容纳一组相同类型的元素
迭代器:
算法:
仿函数:
适配器:
空间配置器: 负责空间配置和管理

2. 空间配置器:

负责 对象构造前的空间配置对象析构后的空间释放

空间配置器的一个重要作用是解决内存的申请和释放时引入的内存碎片问题,SGI使用的方法是 “双层级配置器”:
(1)第一级直接调用 malloc()、deallocate()、free()等系统调用分配内存;
(2)第二级根据具体情况使用不同的策略:当配置区块大于128bytes时,调用第一级配置器,使用malloc等方式直接分配;
当配置区块小于128bytes时,采用内存池的整理方式:配置维护16个自由链表,负责16种小型区块的配置能力。

3. 各种容器的特点和适用情况:

vector: 底层存储是一个数组,可变大小的数组,支持随机访问(O(1)),在尾部之外的位置插入和删除操作时间复杂度是 O(N);

deque: 双端队列,支持随机访问(O(1)),在头部和尾部之外的位置插入和删除的时间复杂度是O(N);

list: 双向链表,不支持随机访问,只支持顺序访问,在任意位置的插入和删除速度很快;

forward_list: 单向链表;

array: 固定大小的数组(vector是可变大小的数组);

string: 与vector类似,可以理解为特殊的vector,专门用来存储字符,支持随机访问,在尾部之外的位置插入的时间复杂度是O(N);

4. 各种容器的底层机制和常见面试题:

4.1 vector:

4.1.1 vector的底层原理:

vector的底层实现是三个指针:

struct _Vector_impl : public _Tp_alloc_type {
	pointer 	_M_start;
	pointer 	_M_finish;
	pointer		_M_end_of_storage;
};

startfinish 之间是已经被使用的空间范围(即 vector.size() 的大小),
startend_of_stroage 之间是vector底层数组的整个空间(即 vector.capacity() 的大小)。

所以 vector 这个类本身并不存储数据的内容,通过指针 start 指向真正的存储元素的数组的地址。

vector的 内存增长机制:

当现有的内存空间不够装下数据时(push_back(val)触发),vector会自动申请另一块更大的内存(1.5倍 或者 2倍),把原有的数据 拷贝 到新的内存空间,然后释放旧有内存空间。

当 clear清空 vector中的元素时(vector.clear()),其存储空间不释放,仅仅是情况内存上的数据。

例如:

vector<int> v;
for(int i = 0; i < 1000; i++) {
   v.push_back(i);

}
cout << v.size() << "  " << v.capacity() << endl;

v.clear();
cout << v.size() << "  " << v.capacity() << endl;

------
1000  1024
0     1024

循环插入1000个元素到vector中,vector的size为1000,capacity为1024;
调用clear清空vector中的元素后,vector的size变为0,capacity仍为1024

4.1.2 vector的reserve和resize的区别:

reserve 的作用是在vector初始化时,根据预估的所需要的内存大小 提前申请内存,目的是避免程序运行中重复的 申请、释放 内存空间,以提高程序的效率;
reserver修改的是vector的capacity的值,只表示一种尝试,当vector的现有实际size大于reserve(n)的n值时,vector不做任何修改。

resize 改变vector的实际大小,可能导致vector中的多余元素被删除,接受两个参数的resize不仅改变vector大小,也改变元素的默认值。

4.1.3 vector的size和capacity的区别:

size = finish - start; 表示的是vector中实际元素的 个数
capacity = end_of_storage - start; 表示的是vector当前分配的内存可以容纳的最多元素的 个数

注意 size 和 capacity 表示的都是元素个数,不是vector内存的字节数。

4.1.4 vector的元素类型不能是引用:

vector的底层存储要求是连续的对象排列,引用不是对象,没有实际地址,因此vector的元素类型不能是引用。

4.1.5 释放vector的内存的方式:

clear、swap、shrink_to_fit。

  1. clear:
v.clear();		//清空vector的元素,占用的内存不会释放,即size变0,capacity不变
  1. swap:
vector<int> v2;	//先定义个空的vector
v2.swap(v);

swap 函数的效果是将 v1和v2 “整体交换”, 不仅交换两个vector的元素内容,并且还会交换vector的size、capacity,相当于是两个vector中的是 三个指针 都进行调换

  1. shrink_to_fit:
//原型:
v.shrink_to_fit();		//shrink_to_fit 将vector的capacity修改为与size一致。作用是释放多余的内存空间
						//注意 shrink_to_fit() 函数不接受参数
						
//使用shrink_to_fit 释放vector内存的写法:
v.clear();
v.shrink_to_fit();		//先调用clear将vector的size变为0,再使用shrink_to_fit将capacity也变为0

4.2 list:

4.2.1 list的底层原理:

list的底层是一个 “双向链表”
list不支持随机访问;插入或删除一个元素时,就对应配置或释放一个结点的空间。

4.3 deque:

4.3.1 deque的底层原理:

deque的底层是一个 “双端队列”
deque在头尾两端插入和删除的时间复杂度都是 O(1)。

4.3.2 什么情况使用vector,什么情况使用list,什么情况使用deque:

随机访问频繁,元素数量变化不大(不会频繁的插入、删除)的场景,优先选用vector;
相反的场景,使用list,即元素频繁的插入删除,但不会经常进行随机反问。

除非必要,应尽可能的选择使用vector而非deque,因为 deque的迭代器比vector的迭代器要复杂的多

除非真的需要频繁的在容器的首尾两端进行频繁的插入和删除元素的操作。

4.4 priority_queue:

4.4.1 priority_queue的底层原理:

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

4.5 map、set、multimap、multiset:

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

请添加图片描述

红黑树的特性:
(1)每个节点是 红色 或者 黑色;
(2)根节点 是 黑色;
(3)所有叶子节点 都是 黑色;
(4)如果有一个节点是红色,则它的两个子结点都是 黑色;(因此红色节点不能相邻,黑色的节点可以相邻)
(5)对每个节点,从该节点到其子孙节点(即叶子节点)的所有途径上,包含相同数据的黑色节点。(所以红黑树平衡的是黑色节点的高度,红色节点主要是用来做区分的)

红黑树平衡的是 黑色节点的高度,红色节点主要是用来做区分的。

以红色树实现的map的 查找、插入、删除 的时间复杂度都是 O(logN)。(平均、最坏、最好都是)

在map中判断一个key是否存在,可以使用find或count:

map.find(key) != map.end();
map.count(key) != 0;

map和set会对插入的元素自动排序,set中元素不允许重复,multiset中的元素可以重复

4.6 unordered_map、unordered_set:

4.6.1 unordered_map、unordered_set的底层原理:

unordered_map 的底层数据结构是一个 哈希表
哈希表的特点是 查找、插入、删除 的时间复杂度是 O(1),缺点是耗费一定的内存(哈希表数组需要预留空间)。

哈希表的实现方法是 使用一个下标范围较大的数组来存储元素,使用 哈希函数(或称“散列函数”)对每个元素的key进行计算,将经过哈希计算得到的值作为数组下标。
当不同的key经过哈希函数计算后得出的结果发生冲突时,使用链表解决。

请添加图片描述

map的底层数据结构是 红黑树,unordered_map的底层数据结构是 哈希表(数组+链表)。

五、迭代器的底层级制和失效的问题:

5.1 迭代器底层原理:

STL的 算法 和 容器 是分离的,两者通过迭代器连接。
算法的实现并不知道自己被传进来的参数是什么类型的容器。

萃取器(Traits) 相当于在接口和实现之间加一层封装,来隐藏一些细节并协助调用合适的方法,这需要一些技巧(例如,偏特化)。

例如STL中的 distance() 函数可用于计算容器中两个迭代器之间的距离,对于vector容器来说,由于内存是连续分配的,因此指针直接相减即可获得二者举例,而如果传入 distance函数的容器类型是list,则distance需要遍历list链表中的元素,才能求得两个迭代器之间的距离。

5.2 迭代器的种类:(5种)

  1. 输入迭代器: 只读迭代器,在每个被遍历的位置上只能读取一次,例如find函数的参数就是输入迭代器(类似于 cbegin()、cend() 只读迭代器);
  2. 输出迭代器: 只写迭代器,在每个被遍历的位置上只能被写一次;
  3. 前向迭代器: 具输入和输出迭代器的能力,但是它可以对同一个位置重复进行读和写。但它 **不支持operator–-*,所以只能向前移动;
  4. 双向迭代器: 可以前、后移动;
  5. 随机访问迭代器: 有双向迭代器的所有功能,并且提供 “迭代器算数”,支持 iter+n、iter-n、iter1-iter2 等操作。

5.3 迭代器失效的问题:

所谓的“迭代器失效”,指的是 在对容器进行某些操作,导致容器上的旧有迭代器在这些操作后失去了本来的效果。

这些操作一般包括 对容器进行 insert插入、erase删除 等操作,
迭代器失效的本质与其所作用的容器的 底层内存实现方式 有关。

5.3.1 对于vector、string、deque这些 连续存储 的容器:

1. 插入操作:

当对容器进行 insert插入 操作时,可能 会发生现有内存空间不足,需要重新分配内存的情况,此时会导致原容器上的所有迭代器全部失效;

如果在insert插入后,内存未重新分配,则 插入点之前的 迭代器仍然有效,插入点之后的迭代器失效;

对于deque,如果插入点在front和back,则所有迭代器失效,指针和引用仍有效;
如果插入点在非front和back的其他位置,则所有迭代器、指针、引用 都失效。

2. 删除操作:

对于vector和string,删除点之前的引用、指针、迭代器仍有效,删除点之后的失效,尾迭代器(off-the-end)总是失效的;

5.3.2 对于list、forward_list、map这些 非连续存储 的容器:

1. 插入操作:

由于容器中的元素的结点都是离散存储的,每次insert插入新节点时都是要构造一个元素并挂到链表中,所以 不存在内存空间不足需要重新分配内存的情况,因此 插入操作不会引起任何迭代器的失效

2. 删除操作:

与插入操作的原理相同,map、list这类容器的各个节点都是离散存储不连续,所以删除操作除了会导致 被删除的元素上的迭代器失效 外,其余迭代器都仍然有效。

5.3.3 迭代器失效的处理方式:

map<int, int> m; 	//<fd, time()>

void proxy_timer_callback() {
	
	for(map<int, int>::iterator it = m.begin(); m != end(); ) {
		map<int, int>::iterator old_it = it;
		it++;
		
		if(old_it->second > cur_time) {
			m.erase(old_it);
		}
	}
}

因为erase()会将迭代器删除,所以如果将 it++ 的操作放在for循环条件里面的话,由于it迭代器已经被删除了,再对其进行 ++ 操作会导致程序 Segment Fault 段错误。
正确的处理方式是在for循环体内保存it当前指向,然后先对it++,再删除 old_it指向的元素节点。

六、STL容器的线程安全性:

同一容器的多线程写操作是 线程不安全的,需要进行加锁。

参考内容:
https://www.cnblogs.com/mangoyuan/p/6446046.html
https://blog.csdn.net/daaikuaichuan/article/details/80717222

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++STL常见面试题总结 的相关文章

随机推荐

  • git使用socks5代理提示:Unsupported proxy syntax in 127.0.0.1:1080

    提示说是语法错误 打开 gitconfig文件查看代理 http proxy socks5 127 0 0 1 1080 https proxy socks5 127 0 0 1 1080 去掉前后的单引号 错误消失 http proxy
  • sql server 2008 r2各个版本的区别与选择

    windows server 2012 r2 standard安装sql server 2008 R2 https blog csdn net maoiur article details 78322175https blog csdn n
  • UniCode编码表

    Unicode编码则是采用双字节16位来进行编号 可编65536字符 基本上包含了世界上所有的语言字符 它也就成为了全世界一种通用的编码 而且用十六进制4位表示一个编码 非常简结直观 为大多数开发者所接受 特别是十六进制编码后 可以解决汉字
  • java泛型的简介说明

    转自 java泛型的简介说明 下文笔者讲述java泛型的简介说明 如下所示 在JDK5中 Java语言引入了泛型机制 但是这种泛型机制其实是通过类型擦除来实现 即Java中的泛型只在程序源代码中有效 源代码阶段提供类型检查 在编译后的字节码
  • CentOS7 下的配置FTP服务器增强版~(零基础学会FTP配置)

    ps 原文不知出处 但是原文也不能正常启动 这里做了一些修改 如果能正常配置请在下方留言让更多的人看到 因为之前我本人照着网上的教程安装卸载了十多次也无法正常使用 不希望后面的兄弟继续浪费时间 如果不能使用 也请劳烦贴出相应的错误 参考 h
  • go 环境变量存储在哪里?

    Q golang环境变量存储在哪里 A 保存在go env GOENV所示的文件里 C Users Administrator gt go env GOENV C Users Administrator AppData Roaming go
  • SQL Server 数据库的创建、删除、修改

    1 创建数据库CREATE 创建数据库语句的基本格式 CREATE DATABASE database name ON PRIAMRY
  • 使用Mulesoft建立webservice, simple方式,POJO

    Mulesoft是使用CXF来支持web service 有三种方式 1 JAX WS 2 Simple POJO 3 Proxy pass throught 本文介绍POJO 最简单的方式 1 首先创建接口跟实现类 接口可以不用 pack
  • ORACLE 分组函数COUNT使用

    COUNT 返回表中记录总数 适用于任意数据类型 两种使用方式 1 SELECT COUNT 1 FROM sh mp fksq where create id 50101311 运行效果 2 SELECT COUNT FROM sh mp
  • Python学习 -- 正则表达式(re模块)

    正则表达式是一种强大的模式匹配工具 用于在文本中查找和匹配特定模式的字符串 在Python中 我们可以使用re模块来操作和处理正则表达式 本篇技术博客将介绍正则表达式的基础语法和re模块的详细使用方法 并通过具体的代码案例来帮助初学者快速掌
  • sql server2005的死锁

    select request session id spid OBJECT NAME resource associated entity id tableName from sys dm tran locks where resource
  • 解决cannot resolve symbol “xxxx”的问题(亲测有效)

    今天做项目的时候导入了一个api接口 并且把附带的jar包也拷贝到了Maven项目中 但是有个方法一直报cannot resolve symbol xxxx 百思不得其解 在网上搜了各种各样的方法也没有解决 这个问题其实就是无法解析某方法
  • Java 实现简单邮件发送(带附件)

    目录 前言 一 添加pom依赖 二 完整发邮件代码 前言 最近写发邮件的功能时 需要把excel文件和邮件内容一起发送 简单记录 一 添加pom依赖
  • C# SuperSocket 手把手教你入门 傻瓜教程---3(Telnet服务器和客户端请求处理)

    C SuperSocket 手把手教你入门 傻瓜教程系列教程 C SuperSocket 手把手教你入门 傻瓜教程 1 服务器单向接收客户端发送数据 C SuperSocket 手把手教你入门 傻瓜教程 2 服务器和客户端双向通信 C Su
  • ICP算法(Iterative Closest Point迭代最近点算法)

    最近在做点云匹配 需要用c 实现ICP算法 下面是简单理解 期待高手指正 ICP算法能够使不同的坐标下的点云数据合并到同一个坐标系统中 首先是找到一个可用的变换 配准操作实际是要找到从坐标系1到坐标系2的一个刚性变换 ICP算法本质上是基于
  • JMeter快速入门知识系列(7)----JMeter断言之响应断言

    7 1 断言的定义 断言用于验证取样器请求或对应的响应数据是否返回了期望的结果 可以是看成验证测试是否预期的方法 对于接口测试与性能测试来说 就是测试Request Response 断言即可以针对Request进行 也可以针对Respon
  • C语言scanf()函数使用的注意事项

    scanf 函数相信就算刚学C语言的朋友也知道 这是一个标准输入函数 它是从标准输入流stdin中读内容的 它的第一个参数是格式化字符串 后面跟着的存储内容的地址列表 如果在全段代码中 只调用一次 且只获取一个变量内容的话 一般不会出现什么
  • POJ 275 Drainage Ditches|网络流|dinic模版

    问题描述 总时间限制 1000ms内存限制 65536kB 描述 Every time it rains on Farmer John s fields a pond forms over Bessie s favorite clover
  • Metis异常检测样本管理源码分析

    Metis异常检测样本管理源码分析 1 表说明 2 样本来源 2 1 样本导入 2 2 异常样本生成 2 3 异常样本打标 1 表说明 Metis一共三张表 anomaly sample dataset train task sample
  • C++STL常见面试题总结

    1 STL的介绍 STL 标准模板库 提高程序的 开发效率 和 复用性 STL包含的 6大组件 容器 迭代器 算法 仿函数 适配器 空间配置器 各组件的作用 容器 用于容纳一组相同类型的元素 迭代器 算法 仿函数 适配器 空间配置器 负责空