前述:本文初衷是为了总结本人在各大平台看到的C++面经,我会在本文持续更新我所遇到的一些C++面试问题,如有错误请一定指正我。新建立了一个收集问答的仓库,欢迎各位小伙伴来更新鸭interview_experience: 本仓库初衷是想为大家提供一个便利,全面,准确的面试题学习场地,大家都可以对仓库进行更新,谢谢大家。
21.介绍一下socket中的多路复用,及其他们的优缺点,epoll的水平和边缘触发模式
select、poll、epoll都是IO多路复用的一种机制,可以监视多个文件描述符,一旦某个文件描述符进入读或写就绪状态,就能够通知系统进行相应的读写操作。
Select优点:可移植性好,因为在某些Unix系统中并不支持poll和epoll
对于超时时间提供了更好的精度:微妙,而poll和epoll都是毫秒级
Select缺点:支持监听的文件描述符fd的数量有限制,最大数量默认是1024个
Select需要维护一个用来存放文件描述符的数据结构,每次调用select都需要把fd集合从用户区拷贝到内核区,而select系统调用后有需要把fd集合从内核区拷贝到用户区,这个系统开销在fd数量很多的时候会很大。
Poll优点(相对于select而言):没有最大文件描述符数量的限制,poll基于链表存储主要解决了这个最大文件描述符数量的限制(当然,他还是有限制的,上限为操作系统能支持的能开启的最大文件描述符数量),优化了编程接口,减少了函数调用参数,并且,每次调用select函数时,都必须重置该函数的三个fd_set类型的参数值,而poll不需要重置。
Poll缺点:poll和select一样同样都需要维护一个用来存放文件描述符的数据结构,当注册的文件描述符无限多时,会使得用户态和内核区之间传递该数据结构的复制开销很大。每次poll系统调用时,需要把文件描述符fd从用户态拷贝到内核区,然后poll系统调用返回前,又需要把文件描述符fd集合从内核区拷贝到用户区,这个内存拷贝的系统开销在fd数量很多的时候会很大。
Epoll优点:和poll一样没有最大文件描述符数量的限制,epoll虽然也需要维护用来存放文件描述符的数据结构(epoll_event),但是它只需要将该数据结构拷贝一次,不需要重复拷贝,并且它只在调用epoll_ctl系统调用时拷贝一次要监听的文件描述符数据结构到内核区,在调用epoll_wait的时候不需要再把所有的要监听的文件描述符重复拷贝进内核区,这就解决了select和poll种内存复制开销的问题。
Epoll缺点:目前只有Linux操作系统支持epoll,不支持跨平台使用,而Unix操作系统上是使用kqueue
Epoll水平触发(LT):对于读操作,只要缓冲区内容不为空,LT模式返回读就绪。
对于写操作,只要缓冲区还不满,LT模式会返回写就绪。
Epoll边缘触发(ET):对于读操作,当缓冲区由不可读变为可读的时候,有新数据到达时,进程修改了EPOLL_CTL_MOD修改EPOLLIN事件时
在ET模式下,缓冲区从不可读变成可读,会唤醒应用进程,缓冲区数据变少的情况,则不会再唤醒应用进程。
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。通常配合将文件描述符设置为非阻塞状态一起使用,这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。
24.类的生命周期
类从被加载到内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中验证,准备,解析三个部分统称为连接
全局对象在main开始前被创建,main退出后被销毁。
静态对象在第一次进行作用域时被创建,在main退出后被销毁。
局部对象在进入作用域时被创建,在退出作用域时被销毁。
New创建的对象直到内存被释放的时候都存在。
25.父类的构造函数和析构函数是否能为虚函数?这样操作导致的结果?
构造函数不能为虚函数,虚函数的调用是通过虚函数表来查找的,而虚函数表由类的实例化对象的vptr指针指向,该指针存放在对象的内部空间之中,需要调用构造函数完成初始化,如果构造函数为虚函数,那么调用构造函数就需要去寻找vptr,但此时vptr还没有完成初始化,导致无法构造对象。
析构函数可以且经常为虚函数:当我们使用父类指针指向子类时,只会调用父类的析构函数,子类的析构函数不会被调用,容易造成内存泄漏。
26.多线程为什么会发生死锁,死锁是什么?死锁产生的条件,如何解决死锁?
因为在多进程中易发生多进程对资源进行竞争,如果一个进程集合里面的每一个进程都在等待这个集合中的其他一个进程才能继续往下执行,若无外力他们将无法推进,这种情况就是死锁。产生死锁的四个条件:互斥条件、请求和保持条件、不可剥夺条件、环路等待条件。解决死锁的方法就是破坏上述任意一种条件。
27.描述一下面向过程和面向对象
面向对象:就是将问题分解为各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为,相比面向过程,代码更易维护和复用。但是代码效率相对较低。
面向过程:就是将问题分析出解决问题的步骤,然后将这些步骤一步一步的实现,使用的时候一个一个调用就好。代码效率更高但是代码复用率低,不易维护。
28.C++中左值和右值是什么?++i是左值还是右值,++i和i++哪个效率更高?
第一小问结合本文第七和第八问,++i是左值,因为++i返回的是一个左值没有发生拷贝,所以效率更高。
29.介绍一下vector、list的底层实现原理和优缺点
Vector优点:可使用下标随机访问,尾插尾删效率高。
缺点:前面部分的插入删除效率低,扩容有消耗,可能存在一定的空间浪费。
底层是由一块连续的内存空间组成,由三个指针实现的分别是头指针(表示目前使用空间的头),尾指针(表示目前使用空间的尾)和可用空间尾指针实现
List优点:按需申请内存,不需要扩容,不会造成内存空间浪费。在任意位置的插入删除下效率高。
缺点:不支持下标随机访问
底层是由双向链表实现的
30.静态变量在哪里初始化?在哪一个阶段初始化?(都存放在全局区域)
静态变量,全局变量,常量都在编译阶段完成初始化和内存分配。其他变量都是在编译阶段进行初始化,运行阶段内存分配.。
31.如何实现多进程?
在Linux中C++使用fork函数来创建进程
而windows中C++使用createprocess来创建进程
32.空对象指针为什么能调用函数?
在类的初始化的时候,编译器会将它的函数分配到类的外部,这也包括静态成员函数,这样做主要是为了节省内存,如果我们在调用类中的的成员函数时没有使用类中的任何成员变量,它不会使用到this指针所以可以正常调用这个函数。
33.shared_ptr线程安全吗?
智能指针中的引用计数是线程安全的,但是智能指针所指向的对象的线程安全问题,智能指针没有做任何保障线程不安全。也就是说它所管理的资源可以线程安全的释放,只保证线程安全的管理资源的生命期,不保证其资源可以线程安全地被访问。
34.push_back()左值和右值的区别是什么?
如果push_back()的参数是左值,则使用它拷贝构造新对象,如果是右值,则使用它移动构造新对象.。
35.move底层是怎么实现的?
Move的功能是将一个左值引用强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义,从实现原理上讲基本等同一个强制类型转换。
优点:可以将左值变成右值而避免拷贝构造,将对象的状态所有权从一个对象转移到另一个对象,只是转移,没有内存搬迁或者内存拷贝。
36.完美转发的原理是什么?
完美转发是指函数模板可以将自己的参数完美的转发给内部调用的其他函数,完美是指不仅能够准确的转发参数的值,还能保证被转发参数的左、右值属性不变,使用引用折叠的规则,将传递进来的左值以左值传递出来,将传递进来的右值以右值的方式传出。
37.空类中有什么函数?
默认构造函数、默认拷贝构造函数、默认析构函数、默认赋值运算符
取值运算符、const取值运算符
38.explicit用在哪里?有什么作用?
只能用于修饰只有一个参数的类构造函数(有一个例外就是,当除了第一个参数以外的其他参数都有默认值的时候此关键字依然有效),它的作用是表明该构造函数是显示的,而非隐式的,跟它对应的另一个关键字是implicit,意思是隐藏的,类构造函数默认情况下声明为implicit。作用是防止类构造函数的隐式自动转换。
39.成员变量初始化的顺序是什么?
成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。类中const成员常量必须在构造函数初始化列表中初始化。类中static成员变量,只能在类外初始化。
顺序:基类的静态变量或全局变量,派生类的静态变量或者全局变量,基类的成员变量,派生类的成员变量。
40.指针占用的大小是多少?
64位电脑上占8字节,32位的占4字节,我们平时所说的计算机多少位是指计算机CPU中通用寄存器一次性处理、传输、暂时保存的信息的最大长度。即CPU在单位时间内能一次处理的二进制的位数,因此CPU所能访问的内存所有地址由多少位组成,而8比特位表示1字节,就可以得出在不同位数的机器中指针的大小。