本文主要总结了一些常见的C++面试题,主要是面试过程中遇到以及网上收集,全部是以自己理解进行了作答,如有不明确的地方,欢迎大家前来学习指正,会不定期去更新面试内容。

1、回调函数的了解?

回调函数和c++中多态类似,主要功能是,对同一个消息,做出不同的响应,使用的是函数指针实现,通过传递函数指针类型,相同的函数地址实现动态绑定,实现对消息体的不同响应。

2、递归算法解释?

递归的基本思想是某个函数直接或者间接地调用自身,把问题转换成多个相同子问题。 1、比如求n的阶乘:(找到递归终止条件和将问题一步一步缩小的关系式子)。 2、函数f参数和返回值都是int类型, if(n == 1) return 1 else f(n-1)*n。 递归有一个很致命的缺点:栈溢出,每调用一次函数栈就会增加一层栈帧,每次函数返回,栈就会减少一层栈帧,而栈不是无限大的,当递归层数过多时,就会出现溢出。

青蛙跳台阶问题:if (n == 1) {return 1;} else if (n == 2) {return 2;} else {return jump(n-1) + jump(n-2);}

3、内存对齐解释?

内存对齐是一种优化的处理方式,为了让处理器能够更高效地访问内存中的数据。结构体中,类型占用空间最大的,作为对齐数。 修改内存对齐数:#pragma pack 要是修改的对齐数(只能是2的n次方),大于结构体中最大类型,结构体中最大类型,作对齐数。

大端:0x123456 小端:0x563412 大小端:高地址区存放的是高位地址数据,低位存放的是低地址数据,叫做小端对齐,反之是大端对

4、一种排序算法解释(快速排序)

时间复杂度平均情况下:O(n log n)

具体实现步骤是这样的,首先从序列中任意选择一个元素,把该元素作为轴,然后将小于等于轴的所有元素都移到轴的左侧,把大于轴的元素都移到轴的右侧。这样,以枢轴为界,划分出两个子序列,左侧子序列所有元素都小于右侧子序列。枢轴元素不属于任一子序列,并且枢轴元素当前所在位置就是该元素在整个排序完成后的最终位置。这样一个划分左右子序列的过程就叫做快速排序的一趟排序,或称为一次划分。递归此划分过程,直到整个序列有序。

5、什么是多态?

不同继承对象,对同一个消息做出不同的响应, 多态三要素: 1、有继承关系, 2、子类重写父类的virtual 3、父类指针或者引用指向子类对象

6、基类为什么需要虚析构函数?

防止内存泄漏。使用指向子类对象的父类指针去释放子类对象的时候,如果没有重写父类虚析构函数,是不会掉用子类的析构函数。则可能会导致内存泄漏。

7、new和malloc的区别?

new是c++的关键字,malloc是库函数,new会申请内存,后调用构造函数,malloc只会申请内存,不会调用构造函数。

8、指针和引用区别?

其实底层都是指针,只是使用形式不同, 指针可以先声明,再去初始化,引用必须声明时初始化,引用不能new和delete因为他就是一个别名。

9、什么是阻塞socket,指的是什么?

对一个文件描述符来说,是一个状态,当不满足读写条件的时候会进行堵塞,非阻塞表示对文件描述符进行读写操作的时候,当不满足条件的时候,不会阻塞, 会立即返回。

11、用过epoll吗,说一下为什么用epoll,还有其他复用方式吗?区别是什么?

可以同时监听多个文件描述符上的I/O操作,因为epoll相比select、poll最大的好处就是它不会随着监听fd数目的增长降低效率,因为他们是采用的轮询方式来处理,而且最多同时监听1024个文件描述符,epoll采用的是事件驱动的设计,已经就绪的事件添加到队列中。

12、说TCP三次握手和四次挥手?

三次握手:

三次握手表示服务器和客户端建立连接一共需要发送三次数据包,刚开始客户端的状态是close状态服务器的状态是listen状态, 第一个握手:客户端发送一个字段中SYN =1 的包给服务器,客户端状态(SYN_SEND) 第二次握手:服务器接收到客户端发来的包之后,发送一个字段中SYN=1,ACK=1的包给客户端 服务端(SYN_REVD) 第三次握手:客户端发送字段中ACK=1的包给服务器,双方处于(established)

四次挥手:

1、双方都可以主动断开连接 第一次挥手:客户端发送FIN=1的包给服务器,此时客户端状态处于FIN_WAIT-1 第二次挥手:服务器收到客户端的包之后,发送ACK=1的包给客户端,(半关闭状态)此时服务器状态改变成CLOSE-WAIT,客户端收到ACK应答报文之后状态变为FIN_WAIT-2 第三次挥手:服务器发送FIN=1的包给客户端,此时服务器状态处于LAST_ACK 第四次挥手:客户端发送ACK=1的包给服务器,此时客户端进入TIME_WAIT状态,服务器进入COLSE状态, 需要等待2MSL: MSL表示是TCP报文在网络中最大生存时间,保证最后一个ACK包能顺利到达。

13、四次挥手中为什么要有CLOSE-WAIT状态和TIME-WAIT状态?

COLSE_WAIT 因为服务器收到断开请求时,可能数据还没有传完 TIME_WAIT状态,假设客户端第四次挥手直接发送完毕之后,直接close,但是这个ack包因为网络原因,服务器没有收到,服务器认为没有关闭,会重新发送FIN包,但是客户端已经close了。

14、主线程先退出对子线程影响?

如果主线程退出是通过return或者是exit退出,表示进程退出,就会造成子线程也退出,如果使用pthread_exit退出当前线程。

15、进程和线程的区别?

进程是程序分配资源管理的基本单位,线程是进程内一个执行单元,线程占用资源更小,不需要向进程那样上下文切换,但是线程是共享资源所以线程之间安全性更难保证,进程的资源是独立的,相对安全。

16、线程池实现?

线程池主要包含4个接口: 1、创建线程池中的线程(保证创建固定数量的线程个数) 2、线程入口函数(先进入while循环中,先去拿锁,拿完锁往下走,又是一个while,条件是当前队列中是否为空,并且一个布尔类型的标识符是否为false,如果两个条件都成立则进入 进入循环中,调用pthread_cond_wait函数,这个函数就是关键,首先会释放锁,处于未激发状态,初始化状态会保证所有线程都卡在这里) 3、唤醒线程函数(像队列中添加任务,通过pthread_cond_signal函数唤醒等待的线程) 4、释放线程函数(将退出线程池变量值为,然后使用pthread_cond_broadcast唤醒所有线程,使用pthread_json进行回收)

17、说一说信号量?

它是一种特殊的变量,它可以被增加或减少是原子操作,sem_post,信号量会++,sem_Wait--

18、说一下Linux下常用命令?

pwd mkdir cp mv cat grep 在文件中查找指定字符串 top 查看系统状态 ps -ef 查看系统中当前运行的进程信息 kill 停止或终止进程 ifconfig tar -zxvf -zcvf chmod

19、说一说互斥锁?

保护共享数据,再多线程程序中,会同时访问一块数据,为了保证数据不被污染,可以通过加锁,保证同一时间,只能有一个线程操作。

20、说一说条件变量?

条件变量是一种线程同步机制,通常与互斥锁配合使用。

21、描述一下http请求?

http请求是一种web中传输数据的协议,请求主要包含:请求行,请求头,空行,请求体。 200 OK:客户端请求被正常处理。 301 永久重定向 302 Found:临时重定向 403 Forbidden:请求被服务器拒绝。 404 Not Found:请求不存在,服务器上找不到请求的资源。 500 Internal Server Error:服务器在执行请求时出现错误。

22、C++程序编译过程?

预处理(宏定义展开,替换条件编译,删除注释和空行),编译(检查语法,将原文件转换成汇编代码),汇编(将汇编代码转换成二进制文件),链接(链接所有动态库和静态库)

23、深拷贝与浅拷贝?

浅拷贝可能会造成内存重复释放,当类中成员有指针变量是,浅拷贝会造成两个指针指向同一块内存,深拷贝是在堆上面开辟内存,两个对象指向不是同一块内存,所以不会出现这个问题。

24、数组和链表的区别?

数组的内存是连续的,并且空间占用比链表小,支持随机访问,链表的删除插入比较方便,拓展性强

25、const char* p 和 char* const p区别?

const是一个修饰符,可以定义常量保证值不被修改,const char* 表示指针指向的值不能发生变化,char* const p 表示指针指向不能发生变化。

26、delete和delete []区别?

delete只会调用一次析构函数,delete [] 还会调用每个成员的析构函数。

27、线程有几种状态?

5种状态,新建、就绪、运行、阻塞、死亡(线程启动以后不可能一直霸占cpu,所以线程的状态就在允许和阻塞种进行切换)

28、epoll两种工作模式?

LT模式也叫水平触发,这种工作模式是,低速模式,来一个事件,不处理的话,就会一直触发。 ET模式也叫边沿触发,来一个事件,内核只通知一次(不管是否处理,内核都不在通知你 epoll内部维护着两个结构,一个是红黑树管理被监听的文件描述符,另一个就是绪事件队列,用于存储有时间的文件描述符

29、说一说static静态变量作用?

1、全局静态变量,作用域是当前文件可见,生命周期是程序开始到结束。 2、局部静态变量,作用域还是当前局部可见,当时生命周期发生了改变。 3、静态函数,只能在声明他的文件中可见。 4、类的静态成员,属于类,不是属于当前对象的,(可以实现多个对象共享数据) 5、类的静态函数,也是属于当前类,不是属于当前对象的,只能访问静态成员。

30、什么是STL?

STL 是 C++ 标准模板库的缩写。它是 C++ 标准库的一部分,提供了一组通用的模板类和函数,实现了常用的数据结构和算法,以及一些函数对象和迭代器概念,使得 C++ 程序员可以更加高效地进行编程。

31、数组和vector的区别?

大小:数组的大小在创建时是固定的,而vector的大小是动态可变的。 内存分配:数组的内存分配在栈上(静态数组)或堆上(动态数组),而vector的内存总是动态分配的,通常在堆上。 功能:vector提供了许多内置的操作,如动态扩展、插入和删除等,而数组只能直接访问元素,不支持动态调整大小。 性能:vector在需要扩展时可能会重新分配内存,而数组的内存分配则不会改变。

32、list 和 vector 的主要区别是什么?

存储方式: list 是一个双向链表,而 vector 是一个动态数组。 访问时间: list 不支持常数时间的随机访问,vector 支持常数时间的随机访问。 插入和删除: 在 list 中,在任意位置插入或删除元素。而在 vector 中,不太适合在中间插入或删除元素。

33、在什么情况下使用 list 而不是 vector?

当你需要在容器的中间频繁插入和删除元素,而不是在末尾插入或删除时,list 更适合。 当你不需要随机访问元素,而是需要频繁进行插入和删除操作时,list 是更好的选择,因为它的这些操作在常数时间内完成。

34、set 的底层数据结构是什么?

set 通常使用红黑树或其他自平衡的二叉搜索树实现。它确保所有元素是唯一的,并且保持排序状态,因此插入、删除和查找操作的时间复杂度是对数时间 (O(log n))。

35、set 和 multiset 有什么不同?

set 中的元素是唯一的,不能有重复的元素。 multiset 允许有重复的元素。

36、map 和 unordered_map 的主要区别是什么?

底层数据结构: map 通常使用红黑树或其他自平衡的二叉搜索树实现,而 unordered_map 使用哈希表实现。 访问时间: map 提供对数时间 (O(log n)) 的查找、插入和删除操作,保持元素的排序。unordered_map 提供平均常数时间 (O(1)) 的查找、插入和删除操作,但不保持元素的排序。 排序: map 中的元素是按照键的顺序排序的,而 unordered_map 中的元素没有特定的顺序。

38、红黑树实现原理?

红黑树是一种特殊的自平衡二叉树,在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能,并且增删改查效率都非常高o(logn) 1、每个节点要么是红色,要么是黑色,根节点必须是黑色。 2、任何相邻节点(上下关系)不能同时为红色。 3、根节点到叶子节点,所经历黑色节点数量相同--黑高相同

优点(增删改查都很快) 插入情况: 1、父为黑,直接插入 2、父叔为红,颜色调换(父和爷颜色调换,叔也变为黑色) 3、父红叔黑,颜色调换,在移动(父和爷进行颜色调换,爷和父调换位置) 4、父红叔黑,颜色调换,在移动(父子爷不在同一条直线上)(父子位子调换,然后执行上一步父和爷进行颜色调换,爷和父调换位置)

39、RAII是什么?

RAII 的核心思想是:资源的获取(如内存、文件句柄、网络连接等)与对象的初始化绑定在一起。具体来说,资源在对象的构造函数中获取,并在对象的析构函数中释放。

40、C++的内存框架分布情况?

C++ 内存分布框架

-------------------------------------------------------
|HighAddress|
--------------------------------------------------------
|Heap|
|(动态分配)|
|-----------------------------------------------------|
|堆栈|
|(局部变量、函数参数等)|
|-----------------------------------------------------|
|全局/静态存储区|
|(全局变量、静态变量)|
|------------------------------------------------------|
|常量存储区|
|(常量值)|
|------------------------------------------------------|
|代码区|
|(执行代码)|
-------------------------------------------------------
|LowAddress|
--------------------------------------------------------

栈(Stack):用于存储局部变量和函数调用信息。栈内存分配速度快,但空间有限。 堆(Heap):用于动态分配内存。程序员可以控制分配和释放内存,但需要显式管理。内存的生命周期由程序员控制。 静态存储区(Static Storage Area):用于存储静态变量和全局变量。这些变量在程序运行期间一直存在。 常量区(Constant Data Area):用于存储常量数据,如字符串常量。

41、讲讲C++的三大特性(封装、继承、多态)?

封装:将数据(成员变量)和对数据的操作(成员函数)捆绑在一起,并对外部隐藏数据的具体实现。 继承:通过继承,子类可以继承父类的属性和行为,从而实现代码的用和扩展。 多态:允许不同的对象以相同的接口进行操作,即同一操作可以作用于不同的对象上,表现出不同的行为。

42、 什么是虚函数和纯虚函数?以及区别?

虚函数:

定义:在基类中声明为 virtual 的成员函数,允许派生类重写(override)该函数。虚函数实现运行时多态。 作用:通过基类指针或引用调用虚函数时,实际调用的是派生类中的重写函数。

纯虚函数

定义:在基类中声明为 virtual 并且等于 0 的虚函数,例如 virtual void func() = 0;。 作用:定义接口,要求所有派生类必须实现纯虚函数。使得基类成为抽象类,不能实例化。

43、虚函数是怎么实现的?虚函数在那个阶段生成?

虚函数表(vtable):每个类(如果包含虚函数)会有一个虚函数表,它是一个指向虚函数实现的指针数组。每个对象会有一个指向虚函数表的指针(vptr)。

动态绑定:通过 vptr 指针,运行时可以查找虚函数表中的具体函数地址,决定调用哪个函数实现。

生成阶段: 编译阶段:编译器生成虚函数表和虚指针。虚函数表在类定义时生成,虚指针在对象实例化时设置。

44、构造函数和析构函数的作用?

构造函数:

作用:在创建对象时初始化对象的状态。构造函数可以用于分配资源、初始化数据成员等。 特性:构造函数与类名相同,无返回值,可以被重载。

析构函数:

作用:在对象生命周期结束时清理对象的资源。析构函数负责释放构造函数分配的资源,如内存、文件句柄等。 特性:析构函数的名字前加 ~,无返回值,不能被重载。

45、构造函数可以被声明为虚函数吗?

构造函数不能被声明为虚函数:构造函数在对象创建时调用,此时对象还未完全构造,无法设置虚函数表,因此构造函数不能是虚函数。

46、构造函数和析构函数那个可以被重载,为什么?

析构函数不可以被重载:每个类只能有一个析构函数,负责对象生命周期结束时的清理操作。如果允许重载,会导致析构时不确定性。

构造函数可以被重载:允许创建对象时通过不同的参数进行初始化。通过重载构造函数,可以提供多种初始化方式。

47、this 指针,为什么会存在this指针?

定义:this 指针是指向当前对象的指针。在类的成员函数内部,this 指针指向调用该函数的对象。 访问对象成员:允许成员函数访问对象的属性和其他成员函数。 区分成员变量和参数:在成员函数中,this 指针可以用来区分同名的成员变量和函数参数。 实现链式调用:可以返回 *this 实现链式调用。