目录
3. C++的虚函数是如何实现的?它被放在内存的什么位置?是什么时候生成的?
9. weak_ptr真的不计数吗?是否有计数方式,在哪里分配的空间?
11. free传入一个指针,它如何确定具体要清理多少空间呢?
25. C++的左值和右值是什么?++i是左值还是右值?++i和i++哪个效率高?
26. 介绍一下vector、list的底层实现原理和优缺点
31. push_back()中的参数是左值和右值的区别是什么?
39. new / delete和malloc / free有什么区别?
44. 介绍一下vector容器的push_back()和emplace_back()
47. vector容器的resize和reserve有什么区别?
1. 讲一讲面向对象的三大特性,并展开说说
面向对象的三大特性为:封装,继承和多态。
封装:将具体的实现过程和数据封装成一个类,使用设置权限的成员函数接口进行访问。这样做可以防止类内部数据被外部直接访问和修改。
继承:子类继承父类的全部成员,只是private是不可以见的。继承权限分为private、public和protected三种。父类的构造、析构、友元和静态成员是不可以被继承的。这样做可以提高代码的复用率,提高程序开发效率。
多态:不同对象对统一消息做出不同相应。多态分为静态多态和动态多态。
静态多态通过函数重载和运算符重载来实现,他是编译期多态,在编译期间就可以确定使用哪个函数。
动态多态通过虚函数来实现,它是运行期多态,系统在运行期间才知道程序具体要调用哪个函数。
2. final关键字的作用是什么?
final放在类后,表示该类无法被继承。
final放在虚函数后,表示这个函数无法被重写。
3. C++的虚函数是如何实现的?它被放在内存的什么位置?是什么时候生成的?
C++的虚函数是通过虚函数表和虚函数指针实现的。每一个包含虚函数类的都有一个虚函数指针,指向虚函数表。而虚函数表是一个指针数组,每一个指针对应指向每一个虚函数的实现代码。
当调用虚函数时,编译器会通过虚函数指针找到对应的虚函数表,并根据函数在表中的位置引用正确的虚函数。
虚函数和普通函数一样,在编译阶段生成,存放在代码区,只是将他的指针保存在虚表中。
4. 智能指针的本质是什么?他的实现原理是什么?
智能指针auto是一个封装了C++指针的类模板,为了确保动态内存安全性。
实现原理:通过一个对象存储需要释放的资源,然后通过对象的析构函数来释放。
5. 匿名函数的本质是什么?他的优点是什么?
匿名函数本质上是一个重载()运算符的栈对象。
优点:在调用时才创建对象,调用结束后立即释放,节约空间。
6. 右值引用是什么?为什么要引入右值引用?
右值引用是为临时变量起一个别名,可以通过右值引用修改右值。
为什么:
1. 移动语义:绑定临时变量,可以节约复制操作的资源。
2. 完美转发:将右值引用作为形参,可以在函数内部将其转发给其他函数。
7. 左值引用和指针的区别是什么?
是否初始化:指针不用初始化,引用必须初始化
性质不同:指针是一个变量,引用是一个别名,本质上引用保存的是引用对象的首地址
占用内存不同:指针有自己的存储空间,引用与引用的对象占用同一空间。
8. 指针是什么?
指针是指内存中存储空间的地址,它分为指针常量和指针变量。
9. weak_ptr真的不计数吗?是否有计数方式,在哪里分配的空间?
控制块有强引用计数和弱引用计数。weak_ptr内部会有一些额外的计数信息,用于帮助管理生命周期,但这并不会引起实际计数的变化。
如果使用make_shared()函数,则是静态分配,空间在栈上。
如果使用new,则是动态分配,空间在堆上。
10. malloc内存分配方式有什么缺点?
malloc是一个用于动态分配的库函数,在申请空间时候有两种方式:
方式1:当用户分配的内存 < 128KB 时,通过brk()从堆申请空间。堆顶指针向高地址移动,如果使用free释放空间,并不会将内存返还给操作系统,而是会缓存在malloc的内存池中,待下次使用
方式2:当用户分配的内存 > 128KB 时,通过mmap()在文件映射区域申请空间。使用私有匿名映射的方式,在文件映射区域分配一块空间,free时释放内存返还给操作系统。
缺点:动态申请的方式容易产生许多内存碎片。操作不当容易内存泄漏。
那为什么不全部都用brk()来分配内存?
如果频繁调用malloc和free,堆空间会产生许多内存碎片。
那为什么不全部都用mmap()来分配内存?
因为向操作系统申请空间,系统调用要先从用户态进入内核态,调用后再转回用户态,频繁地系统调用浪费太多时间。此外,因为mmap()每次释放都会将空间返还给操作系统,因此每次mmap分配的虚拟地址都是缺页状态,第一次访问次虚拟地址时候会触发缺页中断。
11. free传入一个指针,它如何确定具体要清理多少空间呢?
在动态申请内存时,会在存储空间的前面多申请一些空间存储头部信息,其中包括内存空间的大小。free会对传递的地址偏移出头部信息的大小,之后就可以释放空间了。
12. #define 和 const 的区别是什么?
阶段不同:#define是在预处理阶段进行替换,const实在编译阶段确定其值。
安全性不同:#define只是替换,不做安全性检查。const要做类型判断。
内存占用不同:#define在程序中有多少次替换,在内存中就有多少次备份。
const在静态存储区域只有一份。
调试不同:#define不可以调试,因为在预处理阶段已经替换过了。
const常量可以调试。
13. 程序执行的步骤是什么?
预编译:将头文件编译,宏替换,生成.i文件
编译:检查错误,无错后将其转换为汇编语言文件,生成.s文件
汇编:汇编器将汇编语言文件翻译成机器语言文件,生成.o文件
链接:将目标文件和库链接到一起,生成.exe文件
14. 锁的底层原理是什么?
锁的底层是通过CAS机制,atomic机制实现的。
CAS机制:全称为Compare And Swap(比较转换),可以将比较和交换操作转换为原子操作。
CAS操作依赖三个值:内存中的值V,旧的预估值X,要修改的新值B,如果旧的预估值 X等于内存中的值V,就将新的值B保存在内存中。
atomic机制:原子操作指不会被线程调度机制打断的操作,一旦开始,直接运行到结束。
它在X86中的原理是:如果在某一条汇编语言的前面加上LOCK关键字,在执行时CPU会将LOCK引线的电平持续拉低,保证别的CPU无法访问内存。
15. struct和class的区别是什么?
class的成员默认权限是private,struct的默认权限是public。
class支持模板参数,struct不支持模板参数。
16. 什么是内存对齐原则?它的优势是什么?
内存对齐原则指的是将数据结构尽可能地在自然边界上对齐。比如在结构体,类的存储上,都会使用内存对其原则,以整体中,最长的那个类型作为最小单位进行存储。
优点:内存对其原则可以使内存的存取访问更加高效。
17. 进程之间的通信方式有哪些?
1.管道:管道分为匿名管道和命名管道,本质上是一个内核的一个缓存。当进程创建管道后会返回两个文件描述符,一个是写入端,一个是输出端。缺点:它是半双工通信,不适合频繁交换数据。
2.消息队列:可以边发边收,但每个消息有最大长度限制,队列所包含的消息体也有上限,并且通信过程中存在用户态和内核态之间的数据拷贝问题。
3.共享给存:解决了消息队列存在的内核态和用户态之间数据拷贝的问题。
4.信号量:通过同步互斥的PV操作实现
5.信号
6.socket套接字
18. 线程之间的通信方式有哪些?
信号量、条件变量、互斥量
19. 介绍一下socket中的多路复用,及其它们的优缺点
多路复用指的是允许多个输入或者输出共用一个通道。
常用的多路复用机制有:select机制、poll机制、epoll机制,他们可以监视多个文件描述符,一旦某个文件描述符进入就绪状态,就能通知系统进行相应的读写操作。
select优点:可移植性好,对于超时时间精度更高:微秒级,poll和epoll都是毫秒级
select缺点:支持监听的最多文件描述符为1024个。此外,select会维护一个保存文件描述符的数据结构,每次调用时都需要把这个集合从用户区拷贝到内核区,调用后又从内核区拷贝到用户区,增大了系统开销。
poll优点:poll对于文件描述符的数量没有最大限制,用链表存储。
poll缺点:和select机制一样,每次调用需要将文件描述符集合在用户区和内核区之间拷贝,
增大了系统开销。
Epoll优点:epoll对于文件描述符的数量没有最大限制。在调用时,只需要将存放文件描述符的集合从用户区向内核区拷贝一次,不需要重复拷贝。
Epoll缺点:目前epoll机制只适用于linux系统,不支持跨平台使用。
20. 介绍一下epoll的水平和边缘触发模式
epoll水平触发(LT):对于读操作,只要缓冲区不为空,LT模式返回读就绪。
对于写操作,只要缓冲区不满,LT模式会返回写就绪。
epoll边缘出发(ET):ET模式要求应用程序在每次新的就绪事件到来时立即处理,否则epoll不会重复通知同一个事件。
与水平触发不同,边缘触发只在状态变化时通知应用程序,即只有当文件描述符从未就绪变为就绪时才通知。如果应用程序没有处理完所有就绪的事件,epoll
不会重复通知同一个事件,而是等待下一个状态变化。
21. 类的生命周期
类从加载到内存中开始,卸载出内存结束,包括加载、验证、准备、解析、初始化、使用、卸载这七个阶段。其中验证+准备+初始化三部分被称为连接。
全局对象在主函数main开始前被创建,main退出后被销毁。
静态对象在第一次进行作用域时被创建,main退出后被销毁。
局部对象在进入作用域时被创建,在退出作用域后被销毁。
动态申请的对象从申请时被创建,在delete或者程序执行完毕后被销毁。
22. 构造函数和析构函数可以为虚函数吗?
构造函数不可以为虚函数。析构函数可以为虚函数。当父类指针指向子类对象时,将父类的析构函数设置为虚函数,可以避免因“子类析构函数未执行”导致的内存泄漏。
23. 多线程为什么会发生死锁?如何解决死锁?
死锁指:多个线程共享一个区域的资源,某个状态每一个线程都在等待其他线程访问完,才能继续执行下去,互相阻塞无法推进。
解决死锁可以通过破坏互斥条件、请求保持条件、不可剥夺条件和环路等待条件的任意一个
24. 介绍一下面向过程和面向对象
面向过程:指的是将问题解析成步骤,一步一步地执行。代码效率高,但复用率低。
面向对象:指的是将问题分解为对象,来描述事物在解决问题中的行为。虽然代码效率低,但是代码的复用率高。
25. C++的左值和右值是什么?++i是左值还是右值?++i和i++哪个效率高?
C++的左值指的是赋值语句左边的值,不能被赋值的就是右值。
++i是左值,它的效率更高,因为++i返回的是一个左值,没有发生拷贝。
26. 介绍一下vector、list的底层实现原理和优缺点
vector底层是由一块连续的存储空间组成,有三个指针分别为:头指针、尾指针和可用空间尾指针。优点:支持通过下标随机访问,因此遍历的效率高。缺点:删除和插入都需要移动多个元素
list底层一个双向链表。优点:不存在空间浪费。用链表存储,插入和删除效率高。缺点:不支持随机访问,因此遍历效率低。
27. 静态变量在什么阶段初始化?在哪里初始化(赋初值)?
静态变量、全局变量和常量均在编译阶段完成初始化和内存分配。
其他变量在编译阶段初始化,在运行阶段内存分配。
静态变量在类内定义,在类外赋初值。
28. 如何实现多进程
linux系统下采用fork()函数,windows系统下采用createprocess()函数
29. 为什么空对象指针nullptr能调用函数
类在初始化时,编译器会将成员函数分配在类外,可以节省内存。如果函数没有涉及到成员变量,则不会使用this指针,所以空对象指针也可以调用函数。
30. shared_ptr线程安全吗?
智能指针中的引用计数是线程安全的,但智能指针所示对象的线程不安全。也就是它只保证管理资源的生命周期线程安全,不保证资源本身的线程安全。
31. push_back()中的参数是左值和右值的区别是什么?
如果是左值,则直接构造新对象加入容器。
如果是右值,则尝试采用移动语义的方式,避免不必要的复制。
32. move()的功能是什么?
move可以强制将一个左值转换为右值引用,类似于强制类型转换。
优点:可以避免拷贝构造,节约资源。
33. 介绍一下完美转发是什么?
完美转发是指,函数模板可以将自己的参数完美地转发给其他函数,转发的左值也就是左值,右值依旧是右值。
34. 空类中有哪些函数?
无参构造函数、析构函数、拷贝函数、默认赋值运算符 =
35. explicit有什么作用?
explicit用于修饰只有一个参数的构造函数 或 第一个参数无默认值,后边所有参数都有默认值。作用:它表示构造函数是显示的,禁止隐式转换。
36. 成员变量初始化顺序是什么?
顺序:1. 父类的 静态变量 或 全局变量
2. 子类的 静态变量 或 全局变量
3. 父类的 成员变量
4. 子类的 成员变量
37. 指针的大小是多少?
这要看操作系统的地址总线位数,32位系统指针是4字节,64位系统是8字节。
38. 野指针和内存泄漏是什么?如何避免?
野指针:指向一个权限不允许访问 或 已释放的空间的指针。
避免:初始化赋初值nullptr,释放空间后及时赋值nullptr。
内存泄漏:丢失了动态申请的空间指针的控制,导致空间未及时释放,造成系统资源的浪费。
避免:使用智能指针管理资源,及时使用delete释放资源。
39. new / delete和malloc / free有什么区别?
new和delete为运算符,malloc和free为库函数。
new申请空间失败抛异常,malloc申请失败返回空指针NULL。
delete释放会调用析构函数,free不调用析构函数。
new返回有类型的指针,malloc返回无类型的指针。
40. 什么是STL?
它是可复用的组件库,包括容器、算法、迭代器、仿函数、空间配置器、配接器六大组件。
41. 迭代器是指针吗?
迭代器不是指针,迭代器是一个类模板,通过重载了指针的一些操作符模拟了指针的功能。
迭代器所返回的是对象引用而不是对象的值。
指针可以指向迭代器,迭代器不可以指向指针。
42. 线程有哪些状态?线程锁有哪些?
线程有五种状态:创建态、就绪态、运行态、阻塞态、死亡态
线程锁种类:互斥锁、条件锁、自旋锁、读写锁、递归锁
43. 介绍一下map和unordered_map
map底层实现是一个红黑树,内部所有元素是有序的。
优点:内部元素有序
缺点:空间占用率高
unordered_map内部实现了一个哈希表,内部元素是无序的。
优点:查找效率高
缺点:建立哈希表浪费时间
44. 介绍一下vector容器的push_back()和emplace_back()
使用push_back()时,会首先调用有参构造函数构造一个对象,再通过拷贝函数移动到容器中
使用emplace_back()时,会直接在容器尾部构造对象。它比push_back()少执行一次构造函数
但emplace_back()只能用于构造新对象,如果要将已有对象放入vector,需要使用push_back()。
45. 为了保证线程安全,除了“加锁”,还有什么别的方式?
可以使用互斥量:防止多个线程进行数据竞争
原子操作:原子操作不可分割,可以保证线程安全
条件变量:在线程间传递信号,从而控制线程的执行流程
47. vector容器的resize和reserve有什么区别?
vector的resize()是改变vector的大小,可能会添加或删除元素
vector的reserve()是改变vector的容量,不改变元素数量,只为了优化
48. vector如何避免重复扩容?
当vector容量不足时,会以自身1.5或2倍进行扩容。
可以提前使用reserve()自定义最大容量,以减少自动扩容的次数。
49. C++类的大小是多少?
空类的大小为1字节;
非空类:1.算上成员变量的空间大小。
2. 如果有虚函数,则虚函数指针多增加4字节;
(成员函数不计入大小)(计算方式按照内存对齐原则计算)
50. C++的weak_ptr是如何实现的?
weak_ptr的实现基于计数器和寄存器。
计数器记录弱引用数量,寄存器存储shared_ptr。
51. C11引入了哪些新特性?
1. 引入了智能指针,shared_ptr、weak_ptr和unique_ptr以保证线程安全。
2. 引入了右值引用,实现完美转发和移动语义
3. 引入了类型推导,delctype()
4. 引入了空指针,nullptr
5. 引入了范围for遍历,for(Elem T : 集合名称 ) { /*对当前元素T的操作*/ }
6. 引入了初始化列表,用于高效初始化类成员
7. 引入了匿名函数,lambda表达式,[](参数列表) -> 返回值类型
8. 引入了并发机制,thread、lock_guard和unique_lock