前述:本文初衷是为了总结本人在各大平台看到的C++面经,我会在本文持续更新我所遇到的一些C++面试问题,如有错误请一定指正我。新建立了一个收集问答的仓库,欢迎各位小伙伴来更新鸭interview_experience: 本仓库初衷是想为大家提供一个便利,全面,准确的面试题学习场地,大家都可以对仓库进行更新,谢谢大家。
41.野指针和内存泄漏是什么?如何避免?
内存泄漏:是指程序中以动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果
避免:使用智能指针管理资源,在释放对象数组时使用delete[],尽量避免在堆上分配内存
野指针:指向一个已删除的对象或未申请访问受限内存区域的指针。
避免:对指针进行初始化,用已合法的可访问内存地址对指针初始化,指针用完释放内存,将指针赋值nullptr。
42.malloc和new的区别是什么?
Malloc/free是标准库函数,new/delete是C++运算符
Malloc分配内存失败返回空,new失败抛异常
New/delete会调用构造析构函数,malloc/free不会,所以他们无法满足动态对象的要求。
New返回有类型的指针,malloc返回无类型的指针
分配内存的位置:malloc从堆上动态分配内存,new是从自由存储区为对象动态分配内存(取决于operator new的实现,可以为堆还可以是静态存储区)
New申请内存的步骤:调用operator new函数,分配一块足够大,且原始的,未命名的内存空间来存储特定类型的对象。运行相应的构造函数来构造对象,并为其传入初值,返回一个指向该对象的指针。
Delete:先调用对象的析构函数,再调用operator delete函数释放内存空间
43.多线程会发生什么问题?线程同步有哪些手段?
会引发资源竞争的问题,频繁上锁会导致程序运行效率低下,甚至会导致发生死锁。
线程同步手段:使用atomic原子变量,使用互斥量也就是上锁,使用条件变量或信号量制约对共享资源的并发访问。
44.什么是STL?
它是C++标准库的重要组成部分,不仅是一个可复用的组件库也是一个包含了数据结构与算法的软件架构,它拥有六大组件分别是:仿函数,算法,迭代器,空间配置器,容器,配接器
45.对比迭代器和指针的区别
迭代器不是指针,是一个模板类,通过重载了指针的一些操作符模拟了指针的一些功能,迭代器返回的是对象引用而不是对象的值。
指针能够指向函数而迭代器不行迭代器只能指向容器
46.线程有哪些状态,线程锁有哪些?
五种状态:创建,就绪,运行,阻塞,死亡
线程锁的种类:互斥锁,条件锁,自旋锁,读写锁,递归锁
47.解释说明一下map和unordered_map
Map内部实现是一个红黑树,内部所有的元素都是有序的,而hashmap则是内部实现了一个哈希表,内部存储元素是无序的
Map优点:有序性,其次是内部实现的是一个红黑树,使得很多操作都可以在logn的复杂度下可以实现效率较高。
Map缺点:空间占用率高
Unorderedmap优点:查找效率非常高。缺点:哈希表的建立比较费时间
48.vector中的push_back()和emplace_back()的区别、以及使用场景
当使用Push_back时会先调用类的有参构造函数创建一个临时变量,再将这个元素拷贝或者移动到容器之中,而emplace_back则是直接在容器尾部进行构造比push_back少进行一次构造函数调用。在大部分场景中emplace_back可以替换push_back,但是push_back会比emplace_back更加安全,emplace_back只能用于直接在容器中构造新元素的情况,如果要将现有的对象添加到容器中则需要使用push_back
49.如何实现线程安全,除了加锁还有没有其他的方式?
除了锁之外还可以使用互斥量(防止多个线程来同时访问共享资源,从而避免数据竞争的问题),原子操作(原子操作是不可分割的,使用原子操作可以确保在多线程环境中操作是安全的),条件变量(协调线程之间的协作,用来在线程之间传递信号,从而控制线程的执行流程)等方式
50.vector扩容,resize和reserve的区别
使用resize改变的是vector的大小(size),可能会添加或删除元素。
使用reserve改变的是vector的容量(capacity),不会改变当前元素的数量,仅仅是为了优化内存使用和性能。
51.vector扩容为了避免重复扩容做了哪些机制?
当vector内存不够时本身内存会以1.5或者2倍的增长,以减少扩容次数
引入了reserve,自定义vector最大容量
52.C++中空类的大小是多少?
1字节
53.weak_ptr是怎么实现的?
实现依赖于计数器和寄存器实现的,计数器用来记录弱引用的数量,寄存器用来存储shared_ptr
54.虚函数的底层原理是什么?
虚函数表和虚表指针,详细看本文第四问。
55.一个函数f(int a,int b),其中a和b的地址关系是什么?
a和b的地址是相邻的。
56.移动构造和拷贝构造的区别是什么?
移动构造函数本质上是基于指针的拷贝,实现对堆区内存所有权的移交,在一些特定场景下,可以减少不必要的拷贝。比如用一个临时对象或者右值对象初始化类实例时。我们可以使用move()函数,将一个左值对象转变为右值对象。而拷贝构造则是将传入的对象复制一份然后放进新的内存中
57.lamda表达式捕获列表捕获的方式有哪些?如果是引用捕获要注意什么?
按值捕获和引用捕获,默认的引用捕获可能会导致悬挂引用,引用捕获会导致闭包包含一个局部变量的引用或者形参的引用,如果一个由lambda创建的闭包的生命周期超过了局部变量或者形参的生命期,那么闭包的引用将会空悬。解决方法是对个别参数使用值捕获
58.哈希碰撞的处理方法
开放定址法:当遇到哈希冲突时,去寻找一个新的空闲的哈希地址。
再哈希法:同时构造多个哈希函数,等发生哈希冲突时就使用其他哈希函数知道不发生冲突为止,虽然不易发生聚集,但是增加了计算时间
链地址法:将所有的哈希地址相同的记录都链接在同一链表中
建立公共溢出区:将哈希表分为基本表和溢出表,将发生冲突的都存放在溢出表中
59.unordered_map的扩容过程
当unordered_map中的元素数量达到桶的负载因子(0.75)时,会重新分配桶的数量(通常会按照原有桶的数量*2的方式进行扩容,但是具体的增长策略也可以通过修改容器中的max_load_factor成员变量来进行调整),并将所有的元素重新哈希到新的桶中。
60.vector如何判断应该扩容?(size和capacity)
由当前容器内元素数量的大小和容器最大大小进行比较如果二者相等就会进行扩容,一般是1.5倍,部分的有两倍