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

news2024/11/13 14:46:02

 ✍个人博客:Pandaconda-CSDN博客

📣专栏地址:http://t.csdnimg.cn/fYaBd

📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

4. STL 中 vector 删除其中的元素,迭代器如何变化?为什么是两倍扩容?释放空间 

增加元素:

size() 函数返回的是已用空间大小,capacity() 返回的是总空间大小,capacity() - size() 则是剩余的可用空间大小。当 size() 和 capacity() 相等,说明 vector 目前的空间已被用完,如果再添加新元素,则会引起 vector 空间的动态增长。

由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。因此,可以使用 reserve(n) 预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当 n > capacity() 时,调用 reserve(n) 才会改变 vector 容量。

resize() 成员函数改变元素的数目,至于空间的的变化需要看具体情况去分析,如下:

void resize(size_type __new_size, const _Tp& __x) {
    if (__new_size < size()) 
        erase(begin() + __new_size, end());
    else
        insert(end(), __new_size - size(), __x);
}

1、空的 vector 对象,size() 和 capacity() 都为 0。

2、当空间大小不足时,新分配的空间大小为原空间大小的 2 倍。

3、使用 reserve() 预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。

4、当 reserve() 分配的空间比原空间小时,是不会引起重新分配的。

5、resize() 函数只改变容器的元素数目,未改变容器大小。

6、用 reserve(size_type) 只是扩大 capacity 值,这些内存空间可能还是 “野” 的,如果此时使用 “[ ]” 来访问,则可能会越界。而 resize(size_type new_size) 会真正使容器具有 new_size 个对象。

  

不同的编译器,vector 有不同的扩容大小。在 vs 下是 1.5 倍,在 GCC 下是 2 倍;

  • Windows 扩容底层(1.5倍)

Windows 中堆管理系统会对释放的堆块进行合并,当堆管理器发现 2 个空闲块彼此相邻的时候,就会对堆块进行合并。因此,vs 下的 vector 扩容机制选择使用 1.5 倍的方式扩容,这样多次扩容之后,就可以使用之前已经释放的空间。

  • Linux的扩容底层(2倍)

Linux 下主要使用 glibc 的 ptmalloc 来进行用户空间申请的,如果 malloc 的空间小于 128KB,其内部通过 brk() 来扩张,如果大于 128KB 时,通过 mmap 将内存映射到进程地址空间。

总的来说:

  1. 扩容倍数为 2 时,时间上占优势,扩容倍数为 1.5 时,空间上占优势。

  2. vector 在 push_back 以成倍增长可以在均摊后达到 O(1) 的事件复杂度,相对于增长指定大小的 O(n) 时间复杂度更好。

  3. 为了防止申请内存的浪费,现在使用较多的有 2 倍与 1.5 倍的增长方式,而 1.5 倍的增长方式可以更好的实现对内存的重复利用。

使用 k = 2 增长因子的问题在于,每次扩展的新尺寸必然刚好大于之前分配的总和,也就是说,之前分配的内存空间不可能被使用。这样对内存不友好,最好把增长因子设为 (1, 2),也就是 1 ~ 2 之间的某个数值。

假如说我们是以 2 倍方式扩容(1,2,4,8,16),则第 i 次扩容期间所需要的空间总量就是 2^i 次方,如果第 4 次扩容时总共需要 8 个元素大小的空间,但是前3次已经释放的空间加起来的总量,刚好是 7,而 7 小于 8,不足以我们第 4 次扩容时所需要的空间,也就是说,如果恰巧以 2 倍方式扩容,那么每次扩容时前面释放的空间它都不足以支持本次的扩容。

  

当 k = 1.5 时,在几次扩展以后,可以重用之前的内存空间了。

对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到 O(n) 的时间复杂度,因此,使用成倍的方式扩容。

时间复杂度:在最坏情况下,插入操作的时间复杂度是 O(n),其中 n 表示当前 std::vector 中的元素数量。这是因为在插入元素时,如果需要移动已有元素,需要将后续的元素逐个后移。如果插入操作位于末尾,它的时间复杂度可以视为 O(1)。

需要注意的是,std::vector 的动态扩容会导致元素的重新分配和复制,这可能在频繁插入大量元素时引起性能问题。为了避免频繁扩容,可以在初始化 std::vector 时估算好元素数量(使用 reserve 函数预留一定容量),以减少扩容的次数。

删除元素:

由于 vector 的内存占用空间只增不减,比如你首先分配了 10,000 个字节,然后 erase 掉后面 9,999 个,留下一个有效元素,但是内存占用仍为 10,000 个。所有内存空间是在 vector 析构时候才能被系统回收。empty() 用来检测容器是否为空的,clear() 可以清空所有元素。但是即使 clear(),vector 所占用的内存空间依然如故,无法保证内存的回收。

如果需要空间动态缩小,可以考虑使用 deque。

如果使用 vector,可以用 swap() 来帮助你释放多余内存或者清空全部内存。

vector(Vec).swap(Vec); //将Vec中多余内存清除; 
vector().swap(Vec); //清空Vec的全部内存;

实例:

#include <iostream>
#include <vector>
using namespace std;

int main ()
{
    vector<int> vec (100,100);   // three ints with a value of 100
    vec.push_back(1);
    vec.push_back(2);
    cout <<"vec.size(): " << vec.size() << endl;
    cout <<"vec.capasity(): " << vec.capacity() << endl;

    vector<int>(vec).swap(vec); //清空vec中多余的空间,相当于vec.shrink_to_fit();

    cout <<"vec.size(): " << vec.size() << endl;
    cout <<"vec.capasity(): " << vec.capacity() << endl;

    vector<int>().swap(vec); //清空vec的全部空间

    cout <<"vec.size(): " << vec.size() << endl;
    cout <<"vec.capasity(): " << vec.capacity() << endl;

    return 0;
}
/*
    运行结果:
    vec.size(): 102
    vec.capasity(): 200
    vec.size(): 102
    vec.capasity(): 102
    vec.size(): 0
    vec.capasity(): 0
 */

5. vector 动态扩展时,编译器为什么不先判断一下原有空间后面的 内存是否空闲,如果空闲,直接在后面的内存空间继续分配空间?

编译器在处理 vector 动态扩展时,并不会先行判断原有空间后面的内存是否空闲,而是在需要的时候重新申请更大的一块内存空间,然后将原空间数据拷贝到新空间,释放旧空间数据,最后在新的空间中插入新的元素。这种做法的背后,涉及到内存池的设计目标与 vector 的扩展策略的冲突。

实际上,内存池的设计是为了更有效地管理和利用内存资源,它预先在内存中分配一定数量的内存块,当程序需要分配内存时,首先会去内存池中查找是否有合适的空闲内存块可以使用。如果有,就从中分配内存并返回;如果没有,再向操作系统申请新的内存。这种方式可以显著提高内存分配和释放的效率。

6. ST L 中 slist 的实现

list 是双向链表,而 slist(single linked list)是单向链表,它们的主要区别在于:前者的迭代器是双向的 Bidirectional iterator,后者的迭代器属于单向的 Forward iterator。虽然 slist 的很多功能不如 list 灵活,但是其所耗用的空间更小,操作更快。

根据 STL 的习惯,插入操作会将新元素插入到指定位置之前,而非之后,然而 slist 是不能回头的,只能往后走,因此在 slist 的其他位置插入或者移除元素是十分不明智的,但是在 slist 开头却是可取的,slist 特别提供了 insert_after() 和 erase_after() 供灵活应用。考虑到效率问题,slist 只提供 push_front() 操作,元素插入到 slist 后,存储的次序和输入的次序是相反的。

slist 的单向迭代器如下图所示:

  

slist 默认采用 alloc 空间配置器配置节点的空间,其数据结构主要代码如下: 

template <class T, class Allco = alloc>
class slist
{
    ...
private:
    ...
    static list_node* create_node(const value_type& x){}//配置空间、构造元素
    static void destroy_node(list_node* node){}//析构函数、释放空间
private:
    list_node_base head; //头部
public:
    iterator begin(){}
    iterator end(){}
    size_type size(){}
    bool empty(){}
    void swap(slist& L){}//交换两个slist,只需要换head即可
    reference front(){} //取头部元素
    void push_front(const value& x){}//头部插入元素
    void pop_front(){}//从头部取走元素
    ...
}

举个例子:

#include <forward_list>
#include <algorithm>
#include <iostream>
using namespace std;

int main()
{
    forward_list<int> fl;
    fl.push_front(1);
    fl.push_front(3);
    fl.push_front(2);
    fl.push_front(6);
    fl.push_front(5);

    forward_list<int>::iterator ite1 = fl.begin();
    forward_list<int>::iterator ite2 = fl.end();
    for(;ite1 != ite2; ++ite1)
    {
        cout << *ite1 <<" "; // 5 6 2 3 1
    }
    cout << endl;

    ite1 = find(fl.begin(), fl.end(), 2); //寻找2的位置

    if (ite1 != ite2)
        fl.insert_after(ite1, 99);
    for (auto it : fl)
    {
        cout << it << " ";  //5 6 2 99 3 1
    }
    cout << endl;

    ite1 = find(fl.begin(), fl.end(), 6); //寻找6的位置
    if (ite1 != ite2)
        fl.erase_after(ite1);
    for (auto it : fl)
    {
        cout << it << " ";  //5 6 99 3 1
    }
    cout << endl;   
    return 0;
}

需要注意的是 C++ 标准委员会没有采用 slist 的名称,forward_list 在 C++11 中出现,它与 slist 的区别是没有 size() 方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2091366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Code Practice Journal | Day58_Graph08 Topological Sorting

1. 概念 在一个有向无环图(DAG)中&#xff0c;根据节点的依赖关系&#xff0c;对所有的节点进行线性排序的算法 拓扑排序的结果不一定是唯一的 2. 实现 2.1 BFS&#xff08;卡恩算法&#xff09; 1、步骤 2、代码实现 以KamaCoder 117.软体构建 题目&#xff1a;117. 软件…

Stable Diffusion绘画 | 插件-宽高比调整助手:让计算器毕业

在调整图片宽高时&#xff0c;如果每次都需要用计算器根据比例算好&#xff0c;再手工输入&#xff0c;非常影响效率。 推荐使用以下的插件&#xff0c;来实现高效准确地调整图片宽高比例。 Aspect Ratio Helper 安装地址&#xff1a;https://github.com/thomasasfk/sd-webui…

80、k8s概念及组件介绍

一、k8s kubernetes:k8s----希腊语&#xff0c;舵手&#xff0c;飞行员 1.1、k8s作用&#xff1a; ​ 用于自动部署&#xff0c;扩展&#xff0c;管理容器化部署的应用程序。开源&#xff08;半开源。&#xff09; ​ k8s的底层语言是由go语言。 ​ k8s理解成负责自动化运…

Jetson Orin Nano GPIO 舵机

jetson orin nano 40针引脚扩展接头&#xff1a; 图源 Jetson Orin Nano Developer Kit User Guide - | NVIDIA Developer 引脚配置 使用jetson-io tool配置引脚&#xff1a; sudo /opt/nvidia/jetson-io/jetson-io.py 选择“Configure Jetson 40pin Header”: "Confi…

啥是纳米微纤维?咋制作?有啥用?

大家好&#xff0c;今天我们来聊聊纳米/微纤维——《Tailoring micro/nano-fibers for biomedical applications》发表于《Bioactive Materials》。这些纤维近年来备受关注&#xff0c;因为它们具有独特的功能和性质&#xff0c;在生物医学等领域有广泛应用。它们可以通过多种技…

滴滴出行:分布式数据库的架构演进之路|OceanBase案例

本文作者&#xff1a;吴其朋&#xff0c;滴滴分布式存储运维负责人 滴滴出行&#xff0c;作为一个集网约车、出租车、顺风车、代驾等多种出行方式于一体的综合性出行服务平台&#xff0c;其用户遍布全球&#xff0c;总数已突破6.5亿。面对如此多样化的出行需求及庞大的用户群体…

Python优化算法18——教与学优化算法(TLBO)

科研里面优化算法都用的多&#xff0c;尤其是各种动物园里面的智能仿生优化算法&#xff0c;但是目前都是MATLAB的代码多&#xff0c;python几乎没有什么包&#xff0c;这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考&#xff1a;Python优化算…

800G和1.6T以太网:创新与挑战

随着大数据、5G网络、云计算和物联网&#xff08;IoT&#xff09;技术的广泛应用&#xff0c;市场对带宽和数据传输速率的更高需求日益增长&#xff0c;800G和1.6T网络速率的升级备受期待。本文将从800G以太网和1.6T网络在升级方面的所做出的创新举措及其所面临的主要挑战两个方…

小柴冲刺软考中级嵌入式系统设计师系列一、计算机系统基础知识(4)计算机硬件组成及主要部件

目录 计算机系统的基本硬件组成包括 一、中央处理单元 1、CPU的功能 2、CPU的组成 运算器&#xff1a;是数据加工和处理的部件&#xff0c;是执行部件 控制器&#xff1a;运算器只能完成运算&#xff0c;而控制器用于控制整个CPU的工作&#xff0c;它决定了计算机的运行过…

ChatGPT、Claude 和 Gemini 在数据分析方面的合作(第 3 部分):机器学习的最佳 AI 助手

人工智能如何加速你的机器学习项目从特征工程到模型训练 人工智能如何加速你的机器学习项目从特征工程到模型训练 欢迎来到雲闪世界。 在本文中&#xff0c;我们将重点介绍这些 AI 工具如何协助机器学习项目。机器学习是数据科学的基石。虽然使用 LLM 模型完全自动化建模过程具…

代码随想录算法训练营_day32

题目信息 509. 斐波那契数 题目链接: https://leetcode.cn/problems/fibonacci-number/description/题目描述: 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也…

【0-1背包hard】力扣3181. 执行操作可获得的最大总奖励 II

给你一个整数数组 rewardValues&#xff0c;长度为 n&#xff0c;代表奖励的值。 最初&#xff0c;你的总奖励 x 为 0&#xff0c;所有下标都是 未标记 的。你可以执行以下操作 任意次 &#xff1a; 从区间 [0, n - 1] 中选择一个 未标记 的下标 i。 如果 rewardValues[i] 大…

【C++11及其特性】左值和右值

左值和右值目录 一.左值和右值的报错1.简单定义2.函数返回值作左值3.表达式作左值 二.存储的层次结构1.CPU2.内存3.磁盘4.三者联系5.寄存器 三.左值和右值的概念1.左值2.右值3.转换 一.左值和右值的报错 1.简单定义 赋值号’左边的为左值,右边的为右值. 2.函数返回值作左值 …

html2Canvas和jspdf导出长pdf

续使用html2canvas和jspdf导出pdf包含跨页以及页脚_jspdf.umd.min.js-CSDN博客我的这篇文章再写一种情况因为最近我也使用到了 具体的html2Canvas和jspdf的我就不说了&#xff0c;直接开始了&#xff0c; 在公共方法的文件夹中建立一个新的文件htmlToPdf.js用来写咱们得方法然…

亦菲喊你来学机器学习(17) --DBSCAN聚类算法

文章目录 DBSCAN聚类算法基本概念算法步骤特点构建模型模型参数训练模型完整代码展示 总结 DBSCAN聚类算法 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的空间聚类算法&#xff0c;它能够将具有足够高密度的区…

宿舍|基于SprinBoot+vue的宿舍管理系统(源码+数据库+文档)

宿舍管理系统 基于SprinBootvue的私人诊所管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 后台模块实现 管理员功能实现 学生功能实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&…

10 个最佳网络爬虫工具和软件,零基础入门到精通,收藏这一篇就够了

据 Strait Research 称&#xff0c;数据提取的需求正在不断增加&#xff0c;预计到 2031 年将达到 18 亿美元。 使用最好的网络爬行工具启动您的数据提取项目&#xff0c;并告别烦人的爬行头痛。我们研究和测试了数百种免费和付费软件&#xff0c;然后为您提出了十种最佳网络爬…

重大内幕!揭秘数据“零丢失”,全靠它

2017年&#xff0c;某运营商设备扩容&#xff0c;误删80万用户数据… 2020年初疫情期间&#xff0c;某电商公司恶意删库事件&#xff0c;导致业务停机3天&#xff0c;公司赔付1.5亿元人民币 “链家程序员删库”事件&#xff0c;恶意删除公司 9TB 数据&#xff0c;造成公司财务…

LeetCode题练习与总结:单词搜索Ⅱ--212

一、题目描述 给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元格 内的字母构成&#xff0c;其中“相邻”单元格是那些水平相邻或垂直相邻…

使用 scikit-learn 实战感知机算法

一 引言 感知机&#xff08;Perceptron&#xff09;是最早的人工神经网络模型之一&#xff0c;由 Frank Rosenblatt 在 1957 年提出。虽然它相对简单&#xff0c;但在处理线性可分问题时却非常有效。本文将介绍如何使用 Python 的 scikit-learn 库来实现感知机&#xff0c;并通…