当前位置: 首页 > news >正文

突破编程_C++_面试(STL 编程 vector )

面试题 1 :std::vector 的底层存储机制是什么?

std::vector 的底层存储机制是一个动态数组,它内部通过一片连续的内存空间来存储元素。当这个连续的内存空间不足以容纳新元素时,std::vector 会自动申请一块更大的内存空间,通常是当前容量的 1.5 倍或 2 倍,然后将原有数据拷贝到新的内存空间,并释放原来的内存空间。这个过程称为 reallocation。

std::vector 内部有三个基本的指针,分别是 start、finish 和 end_of_storage。start 指向容器的首元素,finish 指向尾元素的下一个位置,而 end_of_storage 指向容器的最大位置。这三个指针帮助 vector 实现了许多功能,如已存储元素大小、剩余空间大小、总容器空间大小等等。

需要注意的是,std::vector 的扩容机制时间成本较高,因此在实际使用中,如果能提前确定容器空间的大小,最好提前设定好容器空间的大小,以避免频繁的扩容操作。同时,由于扩容可能会导致原有指针、迭代器失效,因此在扩容后需要特别注意指针和迭代器的有效性。

此外,std::vector 还支持随机访问,因此访问某一个元素的时间复杂度是 O(1)。但是,由于其内部是连续的内存空间,所以在插入到非尾结点位置或删除元素时,需要移动的元素数量较多,时间复杂度为 O(n)。这也是 std::vector和std::list 等其他容器在性能上的一个主要区别。

面试题 2 :std::vector 的自增长机制是如何实现的?

std::vector 的自增长机制是通过动态调整其内部存储空间的容量来实现的。当 std::vector 需要插入新元素,而当前的存储空间不足以容纳时,它就会触发自增长机制。

自增长机制的实现过程大致如下:

(1)检查容量: 当需要插入新元素时,std::vector 会首先检查当前的容量(capacity)是否足够。容量是指 vector 在内存中预留的空间大小,它至少应该等于 vector 的大小(size),也就是当前存储的元素数量。

(2)重新分配内存: 如果容量不足,std::vector 会重新分配内存空间。通常情况下,它会分配一个更大的内存块,通常是当前容量的1.5倍或2倍(具体倍数可能因实现而异)。这样做是为了减少未来插入元素时再次触发重新分配的次数。

(3)数据迁移: 然后,std::vector会将原有数据(即所有已存储的元素)从旧的内存空间复制到新的内存空间。这是一个相对耗时的操作,因为它涉及到内存拷贝。

(4)释放旧内存: 在数据迁移完成后,std::vector会释放原来的内存空间,这样旧的内存就可以被操作系统或其他数据结构重新利用了。

(5)更新指针: 最后,std::vector 会更新其内部的指针,使 start 和 finish 指针指向新的内存空间中的正确位置,end_of_storage 指针则指向新的内存空间的末尾。

需要注意的是,std::vector 的自增长机制可能会导致一些性能问题。因为每次重新分配内存和数据迁移都是一个相对耗时的操作,特别是在插入大量元素时。因此,在实际应用中,如果可能的话,最好预先知道或估计 vector 可能需要存储的元素数量,并使用 reserve() 函数提前为其分配足够的空间,以减少重新分配的次数。

面试题 3 :std::vector 和 std::array 有什么区别?

std::vector和std::array在C++中都是容器,用于存储一系列相同类型的对象,但它们之间存在一些关键的区别:

(1)动态与静态:

  • std::vector 是一个动态数组,它可以根据需要动态地增加或减少大小。这意味着你可以在任何时候向 vector 中添加或删除元素,而不需要预先知道它将要存储多少元素。
  • std::array 则是一个固定大小的数组,它在编译时创建,并且大小是固定的。一旦定义了一个 array,就不能改变它的大小。
    内存分配:
  • std::vector 使用动态内存分配,这意味着它会在运行时根据需要分配或释放内存。这种动态分配使得 vector 能够灵活地处理不同大小的数据集。
  • std::array 使用静态内存分配,即在编译时分配固定大小的内存。这意味着 array 的大小在编译时就已经确定,并且存储在栈内存中。

(2)访问方式:

  • std::vector 和 std::array 都支持随机访问,可以通过下标运算符[]快速访问元素。然而,由于 vector 的内存动态分配,其访问速度可能会比 array 慢一些,尤其是在涉及大量数据的情况下。
  • 另一方面,array 由于其内存是连续和静态的,访问速度通常更快。

(3)初始化和使用:

  • std::vector 可以使用默认构造函数创建一个空的 vector,然后使用 push_back() 等方法来添加元素。它也可以在创建时指定初始大小。
  • std::array 在定义时必须指定其大小,并且可以使用初始化列表、默认构造函数或复制构造函数来初始化。一旦定义,就不能改变 array 的大小。

(4)适用场景:

  • std::vector 适用于需要动态调整大小的情况,例如当你不知道将要存储多少数据,或者数据的大小可能会在程序执行期间改变时。
  • std::array 适用于大小已知且不会改变的情况,例如当你有一个固定大小的数据集,并且希望获得更好的性能时。

(5)异常安全性:

  • std::vector 在重新分配内存时可能会抛出异常,特别是当内存不足时。
  • std::array 由于其大小是固定的,所以在使用期间不会抛出异常(除了可能的构造函数异常)。

总的来说,std::vector 和 std::array 都有各自的优点和适用场景,选择使用哪一个取决于你的具体需求,比如是否需要动态调整大小、对性能的要求、以及是否已知数组的大小等。

面试题 4 :std::vector的迭代器失效问题是什么?举一个迭代器失效例子。

在 std::vector 容器中,迭代器失效主要发生在以下情况:

  • 当 std::vector 容器进行扩容时,即当现有容量不足以容纳新元素时,std::vector 会重新分配内存空间,并将原有元素复制到新的内存空间中。这个过程可能会导致原有迭代器、引用和指针失效。

  • 当 std::vector 容器的元素被删除时,指向被删除元素的迭代器也会失效。

下面是一个迭代器失效的例子:

#include <iostream>  
#include <vector>  int main() 
{std::vector<int> vec = { 1, 2, 3, 4, 5 };// 获取指向vector第一个元素的迭代器  std::vector<int>::iterator it = vec.begin();// 在迭代器指向的元素之前插入一个新元素  vec.insert(it, 0);// 此时,it指向的元素已经被新插入的元素覆盖,it已经失效  // 如果继续使用it,可能会导致未定义的行为,如程序崩溃或数据错误  // 尝试打印it指向的元素,这将导致迭代器失效的错误  std::cout << "The element at iterator position is: " << *it << std::endl;return 0;
}

在上面代码中,首先创建了一个包含 5 个元素的 std::vector。然后获取了一个指向 vector 第一个元素的迭代器it。接下来,在 it 指向的元素之前插入了一个新的元素。由于这个插入操作导致 vector 扩容(如果原容量不足以容纳新元素),vector 重新分配了内存,并将原有元素复制到新内存空间。这时,原有的迭代器 it 已经失效,因为它指向的内存位置现在包含了一个新插入的元素。

如果尝试使用失效的迭代器 it 来访问元素,比如通过 *it 来解引用它,将会导致未定义的行为。在实际应用中,这可能会导致程序崩溃,或者更难以追踪的数据错误。

因此,在编程时必须格外注意避免在迭代器失效后继续使用它。当对 std::vector 容器进行修改操作(如插入、删除、扩容等)时,任何指向容器中元素的迭代器、引用和指针都可能失效。在这种情况下,应该重新获取迭代器,或者确保在修改操作之前或之后不使用这些迭代器。

面试题 5 :std::vector 和 std::list 有什么区别?

std::vector 和 std::list 是 C++ 标准模板库 (STL) 中的两种非常重要的序列式容器,它们有着显著的区别。以下是它们之间的主要区别:

(1)底层实现与内存管理:

  • std::vector:底层使用动态数组实现,元素存储在连续的内存空间中。这意味着 vector 的元素访问速度很快,因为可以通过下标直接访问。然而,当 vector 需要增加大小时,它可能需要重新分配更大的内存块并移动所有元素,这可能会导致较高的时间复杂度。
  • std::list:底层使用双向链表实现,每个元素可以分布在内存中的任意位置。这意味着 list 的随机访问速度较慢,因为它需要从头或尾开始遍历链表,但插入和删除元素的速度很快,因为只需要调整节点的指针。

(2)随机访问性能:
std::vector:支持随机访问,即可以通过下标直接访问任何位置的元素,时间复杂度为 O(1)。
std::list:不支持随机访问,访问元素需要从头或尾开始遍历,时间复杂度为 O(n)。

(3)插入与删除操作:
std::vector:在任意位置插入或删除元素时,可能需要移动后续的所有元素来填补空缺,时间复杂度为 O(n)。
std::list:插入或删除元素只需要改变节点指针,时间复杂度为 O(1)。

(4)空间利用率与缓存效率:
std::vector:由于其元素是连续存储的,不容易造成内存碎片,空间利用率和缓存效率较高。
std::list:由于其使用链表结构,每个节点可能不是连续存储的,容易造成内存碎片,导致空间利用率和缓存效率较低。

(5)迭代器:
std::vector:迭代器是原生态的指针。
std::list:迭代器是对原生态指针进行了封装。

(6)迭代器失效问题:
std::vector:在插入元素时,如果导致扩容,原有迭代器可能会失效。删除元素时,被删除元素及其之后的迭代器也会失效。
std::list:插入元素不会导致迭代器失效,而删除元素只会导致当前迭代器失效,其他迭代器不受影响。

(7)使用场景:
std::vector:适用于需要高效存储和随机访问的场景,不关心插入效率。
std::list:适用于大量插入和删除操作的场景,不关心随机访问。

总结,std::vector 和 std::list 的选择取决于具体需求。如果需要高效的随机访问和连续的内存存储,std::vector 是更好的选择。如果需要频繁地插入和删除元素,而不太关心随机访问,那么 std::list 可能更适合。

面试题 6 :如何在 std::vector 中插入和删除元素?

在C++的std::vector中插入和删除元素有多种方法,下面是一些常用的方法:

插入元素

(1)使用 insert 成员函数

insert 函数可以在指定位置插入一个或多个元素。

#include <vector>
#include <iostream>int main() 
{std::vector<int> vec = {1, 2, 4, 5};// 在位置2插入元素3vec.insert(vec.begin() + 2, 3);// 输出vector的内容for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}

上面代码的输出为:

1 2 3 4 5

insert 也可以接受一个迭代器范围来插入多个元素。

std::vector<int> to_insert = {3, 3, 3};
vec.insert(vec.begin() + 2, to_insert.begin(), to_insert.end());

(2)使用 push_back 成员函数

push_back 在vector的末尾添加一个新元素。

vec.push_back(1);

(3)使用 emplace 或 emplace_back 成员函数

emplace 和 emplace_back 可以在指定位置就地构造一个新元素,这通常比先构造元素再插入更高效。

vec.emplace(vec.begin() + 2, 3); // 在位置2就地构造并插入元素3
vec.emplace_back(7); // 在vector末尾就地构造并插入元素7

删除元素

(1)使用 erase 成员函数

erase 可以删除一个或多个元素。

// 删除位置2的元素
vec.erase(vec.begin() + 2);// 删除从位置1到位置3的元素(不包括位置3)
vec.erase(vec.begin() + 1, vec.begin() + 3);

(2)使用 pop_back 成员函数
pop_back 删除vector的最后一个元素。

if (!vec.empty()) {vec.pop_back();
}

(3)使用 clear 成员函数
clear 删除vector中的所有元素。

vec.clear(); // vector现在为空

注意:当使用 insert 或 erase 时,指向被修改部分的迭代器、引用和指针可能会失效。因此,在插入或删除元素后,应该避免使用这些失效的迭代器、引用和指针。

此外,频繁地在 std::vector 中间位置插入或删除元素可能会导致性能下降,因为可能需要移动大量的元素来保持连续性。如果这种操作很频繁,考虑使用 std::list 或 std::deque 等容器可能会更有效。

相关文章:

突破编程_C++_面试(STL 编程 vector )

面试题 1 &#xff1a;std::vector 的底层存储机制是什么&#xff1f; std::vector 的底层存储机制是一个动态数组&#xff0c;它内部通过一片连续的内存空间来存储元素。当这个连续的内存空间不足以容纳新元素时&#xff0c;std::vector 会自动申请一块更大的内存空间&#x…...

【报名指南】2024年第九届数维杯数学建模挑战赛报名全流程图解

1.官方报名链接&#xff1a; 2024年第九届数维杯大学生数学建模挑战赛http://www.nmmcm.org.cn/match_detail/32 2.报名流程&#xff08;电脑与手机报名操作流程一致&#xff09; 参赛对象为在校专科生、本科生、研究生&#xff0c;每组参赛人数为1-3人&#xff08;指导老师不…...

C#,哈夫曼编码(Huffman Code)压缩(Compress )与解压缩(Decompress)算法与源代码

David A. Huffman 1 哈夫曼编码简史&#xff08;Huffman code&#xff09; 1951年&#xff0c;哈夫曼和他在MIT信息论的同学需要选择是完成学期报告还是期末考试。导师Robert M. Fano给他们的学期报告的题目是&#xff0c;寻找最有效的二进制编码。由于无法证明哪个已有编码是…...

JS 对象数组排序方法测试

输出 一.Array.prototype.sort() 1.默认排序 sort() sort() 方法就地对数组的元素进行排序&#xff0c;并返回对相同数组的引用。默认排序是将元素转换为字符串&#xff0c;然后按照它们的 UTF-16 码元值升序排序。 由于它取决于具体实现&#xff0c;因此无法保证排序的时…...

【计算机考研】408学到什么程度才能考130?

408考130要比考研数学考130难的多 我想大部分考过408的考生都是这么认为的。408的难点在于他涉及的范围太广了&#xff0c;首先如果你要备考408&#xff0c;你要准备四门课程&#xff0c;分别是数据结构&#xff0c;计算机组成原理&#xff0c;操作系统和计算机网络。 这四门…...

“智农”-农业物联网可视化

大棚可视化|设施农业可视化|农业元宇宙|农业数字孪生|大棚物联网|大棚数字孪生|农业一体化管控平台|智慧农业可视化|智农|农业物联网可视化|农业物联网数字孪生|智慧农业|大棚三维可视化|智慧大棚可视化|智慧大棚|农业智慧园区|数字农业|数字大棚|农业大脑|智慧牧业数字孪生|智…...

day03-网络编程

1>TCP机械臂测试 #include<myhead.h> #define SER_IP "10.211.55.11" #define SER_PORT 8888 #define CLI_IP "10.211.55.9" #define CLI_PORT 6666 //客户端 int main(int argc, const char *argv[]) {//1、创建用于通信的套接字文件描述符int …...

Java反射,动态代理。笔记

1.pathClass Loader 和 Dex ClassLoader 在Android 5.0以下的版本中,两者之间的区别为: DexClassLoader:可加载jar、apk和dex」可以从SD卡中加载PathClassLoader:只能加载已安裝到系統中(即/data/app目录下)的apk文件但是随着Android版本的升级,到Android …...

作为团队开发组长你需要做的:

当你需要开始团队开发时&#xff0c;以下是一些你可能需要知道和使用的工具、实践和原则&#xff1a; 1. 版本控制系统 (VCS): 使用版本控制系统&#xff08;如Git&#xff09;来管理代码。这能确保团队成员协同工作时能够跟踪和管理代码的变更。创建分支进行开发&#xff0c…...

Windows安装Neo4j数据库教程(3.X版本)

安装java的jdk&#xff08;jdk1.8仅支持Neo4j 3.X版本&#xff09;去 Index of /doc/neo4j/ 下载目标版本的Windows zip安装包将安装包解压到任意目录&#xff0c;并记住解压后带版本号的文件夹路径添加系统环境变量&#xff0c;变量名&#xff1a;NEO4J_HOME&#xff0c;变量值…...

无人机飞行控制系统技术,四旋翼无人机控制系统建模技术详解

物理建模是四旋翼无人机控制系统建模的基础&#xff0c;主要涉及到无人机的物理特性和运动学特性。物理建模的目的是将无人机的运动与输入信号&#xff08;如控制电压&#xff09;之间的关系进行数学描述。 四旋翼无人直升机是具有四个输入力和六个坐标输出的欠驱动动力学旋翼…...

程序员的金三银四求职宝典:如何在关键时期脱颖而出?

个人主页&#xff1a;17_Kevin-CSDN博客 随着春天的脚步渐近&#xff0c;程序员们的求职热潮也随之而来。在这个被称为“金三银四”的招聘季&#xff0c;如何从众多求职者中脱颖而出&#xff0c;成为了许多程序员关注的焦点。本文将为你提供一份全面的求职宝典&#xff0c;助你…...

分享经典、现代和前沿软件工程课程

随着信息技术的发展&#xff0c;软件已经深入到人类社会生产和生活的各个方面。软件工程是将工程化的方法运用到软件的开发、运行和维护之中&#xff0c;以达到提高软件质量&#xff0c;降低开发成本的目的。软件工程已经成为当今最活跃、最热门的学科之一。 本次软件工程MOOC课…...

网络工程师笔记3

IP地址类型 A类 255.0.0.0B类 255.255.0.0C类 255.255.255.0D类 E类 子网掩码&#xff1a;从左到右连续的确定网络位 2-4-8-16-32-64-128-256 128 &#xff1a; 1000 0000 64 &#xff1a; 0100 0000 32 &#xff1a; 0010 0000 16 &#xff1a; 0001 0000 8 &am…...

【菜鸟入门!】Matlab零基础快速入门教程

数学建模竞赛中&#xff0c;编程软件是必不可缺少的&#xff0c;比如大家都熟知的MATLAB多数同学们都会经常用到&#xff0c;今天给大家介绍一些MATLAB的基本元素&#xff0c;希望帮助大家更好的掌握编写基本的函数&#xff01; 变量和数组 MATLAB 程序的基本数据单元是数组。一…...

数据中心GPU集群高性能组网技术分析

数据中心GPU集群组网技术是指将多个GPU设备连接在一起&#xff0c;形成一个高性能计算的集群系统。通过集群组网技术&#xff0c;可以实现多个GPU设备之间的协同计算&#xff0c;提供更大规模的计算能力&#xff0c;适用于需要大规模并行计算的应用场景。 常用的组网技术&…...

go垃圾回收

1 go 垃圾回收变更 Go 语言的垃圾回收器&#xff08;GC&#xff09;自其诞生以来一直在不断演进和优化&#xff0c;以提高性能、减少暂停时间和对程序执行的影响。以下是一些关键的改进和变更点&#xff1a; 并发标记周期&#xff1a; Go 语言从一开始就采用了并发标记&#xf…...

如何做代币分析:以 LEO 币为例

作者&#xff1a; lesleyfootprint.network 编译&#xff1a;cicifootprint.network 数据源&#xff1a;LEO 代币仪表板 &#xff08;仅包括以太坊数据&#xff09; 在加密货币和数字资产领域&#xff0c;代币分析起着至关重要的作用。代币分析指的是深入研究与代币相关的数…...

数制和码制

目录 几种常见的数制 数制 基数 位权 常见的四种数制 十进制数 二进制数 八进制数 十六进制数 不同进制数的相互转换 例如 例如 编码 二-十进制码 例如 格雷码 例如 原码、反码和补码 几种常见的数制 关键术语 数制&#xff1a;以一组固定的符号和统一的规则来表示数值…...

Git Bash中安装tree

文章目录 问题描述解决办法A备选办法BRef 问题描述 在Git Bash中使用tree报错&#xff1a; tree # bash: tree: command not found解决办法A 下载二进制文件&#xff1a; https://gnuwin32.sourceforge.net/packages/tree.htm -> 选binary。下载后解压.zip 把解压后的tre…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

土建施工员考试:建筑施工技术重点知识有哪些?

《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目&#xff0c;核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容&#xff0c;附学习方向和应试技巧&#xff1a; 一、施工组织与进度管理 核心目标&#xff1a; 规…...

Vue3 PC端 UI组件库我更推荐Naive UI

一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用&#xff0c;前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率&#xff0c;还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库&#xff08;Naive UI、Element …...

Android Framework预装traceroute执行文件到system/bin下

文章目录 Android SDK中寻找traceroute代码内置traceroute到SDK中traceroute参数说明-I 参数&#xff08;使用 ICMP Echo 请求&#xff09;-T 参数&#xff08;使用 TCP SYN 包&#xff09; 相关文章 Android SDK中寻找traceroute代码 设备使用的是Android 11&#xff0c;在/s…...

HTML版英语学习系统

HTML版英语学习系统 这是一个完全免费、无需安装、功能完整的英语学习工具&#xff0c;使用HTML CSS JavaScript实现。 功能 文本朗读练习 - 输入英文文章&#xff0c;系统朗读帮助练习听力和发音&#xff0c;适合跟读练习&#xff0c;模仿学习&#xff1b;实时词典查询 - 双…...