【C++ 面试 - STL】每日 3 题(九)

2024-09-06 02:44
文章标签 c++ 面试 每日 stl

本文主要是介绍【C++ 面试 - STL】每日 3 题(九),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/fYaBd
📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

25. 说一下 STL 每种容器对应的迭代器

暂时无法在飞书文档外展示此内容

26. STL 的两级空间配置器

allocate 包装 malloc,deallocate 包装 free

allocator 就是用来分配内存的,最重要的两个函数是 allocate 和 deallocate,就是用来申请内存和回收内存的,外部(一般指容器)调用的时候只需要知道这些就够了。

1、首先明白为什么需要二级空间配置器?

我们知道动态开辟内存时,要在堆上申请,但若是我们需要频繁的在堆开辟释放内存,则就会在堆上造成很多外部碎片,浪费了内存空间;
每次都要进行调用 malloc、free 函数等操作,使空间就会增加一些附加信息,降低了空间利用率;
随着外部碎片增多,内存分配器在找不到合适内存情况下需要合并空闲块,浪费了时间,大大降低了效率。
于是就设置了二级空间配置器,当开辟内存 <= 128 bytes 时,即视为开辟小块内存,则调用二级空间配置器。
关于 STL 中一级空间配置器和二级空间配置器的选择上,一般默认选择的为二级空间配置器。 如果大于 128 字节再转去一级配置器器。

一级配置器

一级空间配置器中重要的函数就是 allocate、deallocate、reallocate。 一级空间配置器是以 malloc(),free(),realloc() 等 C 函数执行实际的内存配置。大致过程是:
1、直接 allocate 分配内存,其实就是 malloc 来分配内存,成功则直接返回,失败就调用处理函数。
2、如果用户自定义了内存分配失败的处理函数就调用,没有的话就返回异常。
3、如果自定义了处理函数就进行处理,完事再继续分配试试。

在这里插入图片描述
二级配置器

在这里插入图片描述
1、维护 16 条链表,分别是 0-15 号链表,最小 8 字节,以 8 字节逐渐递增,最大 128 字节,你传入一个字节参数,表示你需要多大的内存,会自动帮你校对到第几号链表(如需要 13 bytes 空间,我们会给它分配 16 bytes 大小),在找到第 n 个链表后查看链表是否为空,如果不为空直接从对应的 free_list 中拔出,将已经拨出的指针向后移动一位。

2、对应的 free_list 为空,先看其内存池是不是空时,如果内存池不为空:
(1)先检验它剩余空间是否够 20 个节点大小(即所需内存大小(提升后) * 20),若足够则直接从内存池中拿出 20 个节点大小空间,将其中一个分配给用户使用,另外 19 个当作自由链表中的区块挂在相应的 free_list 下,这样下次再有相同大小的内存需求时,可直接拨出。
(2)如果不够 20 个节点大小,则看它是否能满足 1 个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的 free_list 中。
(3)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的 free_list 中(找到相应的 free_list),然后再给内存池申请内存,转到 3。

3、内存池为空,申请内存。此时二级空间配置器会使用 malloc() 从 heap 上申请内存,(一次所申请的内存大小为 2 * 所需节点内存大小(提升后)* 20 + 一段额外空间),申请 40 块,一半拿来用,一半放内存池中。

4、malloc 没有成功。在第三种情况下,如果 malloc() 失败了,说明 heap 上没有足够空间分配给我们了,这时,二级空间配置器会从比所需节点空间大的 free_list 中一一搜索,从比它所需节点空间大的 free_list 中拔除一个节点来使用。如果这也没找到,说明比其大的 free_list 中都没有自由区块了,那就要调用一级适配器了。而一级配置器其实也是使用 malloc(),但是它有 out-of-memeory 处理机制,或许有机会拿其它的内存拿来此处使用。如果还是申请不成功,就会发出 bad_alloc 异常。
释放时调用 deallocate() 函数,若释放的 n > 128,则调用一级空间配置器,否则就直接将内存块挂上自由链表的合适位置。

STL 二级空间配置器虽然解决了外部碎片与提高了效率,但它同时增加了一些缺点:

  1. 因为自由链表的管理问题,它会把我们需求的内存块自动提升为 8 的倍数,这时若你需要 1 个字节,它会给你 8 个字节,即浪费了 7 个字节,所以它又引入了内部碎片的问题,若相似情况出现很多次,就会造成很多内部碎片;
  2. 二级空间配置器是在堆上申请大块的狭义内存池,然后用自由链表管理,供现在使用,在程序执行过程中,它将申请的内存一块一块都挂在自由链表上,即不会还给操作系统,并且它的实现中所有成员全是静态的,所以它申请的所有内存只有在进程结束才会释放内存,还给操作系统,由此带来的问题有:
  3. 即我不断的开辟小块内存,最后整个堆上的空间都被挂在自由链表上,若我想开辟大块内存就会失败;
  4. 若自由链表上挂很多内存块没有被使用,当前进程又占着内存不释放,这时别的进程在堆上申请不到空间,也不可以使用当前进程的空闲内存,由此就会引发多种问题。

一级分配器

GC4.9 之后就没有第一级了,只有第二级

二级分配器

—— default_alloc_template 剖析
有个自动调整的函数:你传入一个字节参数,表示你需要多大的内存,会自动帮你校对到第几号链表(0-15 号链表,最小 8 字节,最大 128 字节)
allocate 函数:如果要分配的内存大于 128 字节,就转用第一级分配器,否则也就是小于 128 字节。那么首先判断落在第几号链表,定位到了,先判断链表是不是空,如果是空就需要充值,(调节到 8 的倍数,默认一次申请 20 个区块,当然了也要判断 20 个是不是能够申请到,如果只申请到一个那就直接返回好了,不止一个的话,把第 2 到第 n 个挨个挂到当前链表上,第一个返回回去给容器用,n 是不大于 20 的,当然了如果不在 1 - 20 之间,那就是内存碎片了,那就先把碎片挂到某一条链表上,然后再重新 malloc 了,malloc 2*20 个块)去内存池去拿或者重新分配。

27. sort 的底层实现

std::sort 主要是三种算法的结合体:插入排序,快速排序,堆排序。

在这里插入图片描述

std::sort 根据上文提到的几种算法的优缺点,对排序算法进行整合。

  1. 快速排序,递归排序到一定深度后,数据已经被分为多个子区域,子区域里面的数据可能是无序的,但是子区域之间已经是有序了。
  2. 在这多个子区域里,如果某个子区域数据个数大于阈值(16),采用堆排序,使得某个子区域内部有序。
  3. 剩下的没有被堆排序的小区域,数据量都是小于阈值的,最后整个数据区域采用插入排序。
    在这里插入图片描述
  4. std::sort 采用的是分治思维,先采用快速排序,将整个区域分成多个子区域,每个子区域内部根据数据量采用不同算法。
  5. 分治后,各个子区域局部有序后再通过整个区域进行排序。

哪些容器可以用 sort 进行排序?

STL 的所有关联型容器都有自动排序的功能(底层结构采取 RB-tree),所以不需要用到这个 sort 算法。
至于序列式容器中的 stack、queue 和 priority-queue 都有特定的出入口,不允许用户对元素进行排序。
剩下的 vector、deque 和 list,前两者的迭代器都属于 RandomAccessIterators,适合使用 sort 算法;list 的迭代器则属于 BidirectionalIterators;不在 STL 标准之列的 slist,其迭代器更属于 ForwardIterator,都不适合使用 sort 算法,如果是要对 list 或者 slist 中的元素进行排序,应该使用他们自己的 member function sort()。

这篇关于【C++ 面试 - STL】每日 3 题(九)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140777

相关文章

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现