目录
面试题:
1. define inline 在编译的哪个阶段
2. const static
3. 子函数返回结构体有什么问题,返回对象调用了哪些函数
4. volatile关键字
5. 编译器基本原理
6. 预处理、编译、汇编、链接以及他们在操作系统上如何运作的
7. 数组和指针(二维)
8. 指针和引用
9. new 和 malloc
10.万能引用和右值引用
11. 解释中断,以及底层发生的操作细节
12. C++多线程在操作系统上如何运作的
13. 进程、线程间通信方式
14. 信号量在操作系统中如何实现
15. 虚函数
16. 构造函数、析构函数
手撕题:
1. 内存池
2. lambda表达式
3. 手写单例模式
4. 遍历二叉树(非递归)
面试题:
- define inline 在编译的哪个阶段
- const static
- 子函数返回结构体有什么问题,返回对象调用了哪些函数
- volatile关键字
- 编译器基本原理
- 预处理、编译、汇编、链接以及他们在操作系统上如何运作的
- 数组和指针(二维)
- 指针和引用
- new 和 malloc
- 万能引用和右值引用
- 解释中断,以及底层发生的操作细节
- C++多线程在操作系统上如何运作的
- 信号量在操作系统中如何实现
- 进程、线程间通信方式
- 虚函数
- 构造函数、析构函数
1. define inline 在编译的哪个阶段
define: 预编译/预处理
inline: 链接阶段
2. const static
经过static修饰的变量会作为类的属性而不是实体属性存在。它的作用是在编译时期确定,程序运行过程中不会改变。
static
关键字用于声明静态成员变量、静态成员函数和局部静态变量,其作用取决于它所修饰的实体。
- 静态成员变量:静态成员变量是属于类的,而不是属于类的各个实例的。它的特点是所有类的实例共享同一份静态成员变量。静态成员变量可以通过类名直接访问,也可以通过对象访问。
- 静态成员函数:静态成员函数是属于类的函数,它不依赖于任何特定的对象。因此,它可以直接通过类名来调用,而不需要创建类的实例
- 局部静态变量:具有静态生存期,即它在程序运行期间只初始化一次,并且在函数调用结束后仍然存在于内存中。
const
const
关键字用于声明常量,它指定了一个变量在初始化后不能被修改的特性。const
可以用于变量、成员函数和指针。
3. 子函数返回结构体有什么问题,返回对象调用了哪些函数
typedef
定义类型,因此返回return mm
类似返回int
这种基础操作,仅需声明函数时也声明结构体的类型。
也可以用指针
4. volatile关键字
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
5. 编译器基本原理
6. 预处理、编译、汇编、链接以及他们在操作系统上如何运作的
1、预处理:
通过gcc -E main.c -o main.i 生成.i文件将进行如下操作:
1、将所有的#define删除,并展开所有的宏定义。
2、处理所有的预编译指令,例如:#if,#elif,#else,#endif等。
3、处理#include预编译指令,将被包含的文件插入到预编译指令的位置。
4、添加行号信息、文件名标识,便于调试。
5、删除所有的注释。
6、保留所有的#pragma编译指令,因为在编写程序的时候,我们经常要用到#pargma指令来设定编译的状态或者是指示编译器完成一些特定的动作。
7、生成.i文件(包括去注释、宏替换、头文件展开、条件编译),编译生成的.i文件不包含任何宏定义,因为宏已经被展开,并且包含的文件已经被插入到.i文件中。
2、编译(C/C++语音 ------> 汇编):
通过gcc -S main.i –o main.s 生成.s文件,需要进行如下操作:
1、扫描、语法分析、语义分析、源代码分析、目标代码生成、目标代码优化。
2、生成汇编代码。
3、汇总符号。
4、生成.s文件。
3、汇编(汇编 ------> 二进制):
通过gcc –c main.s –o main.o 生成.o文件,需要进行如下操作:
1、根据汇编指令和特定平台,把汇编指令翻译成二进制形式。
2、合并各个section,合并符号表。
3、生成.o文件。
4、链接:
链接过程会进行如下操作:
1、合并各个.obj文件的section,合并符号表,进行符号解析。
2、符号地址重定位。
3、生成可执行文件。
7. 数组和指针(二维)
二维数组名是一个二级指针?
8. 指针和引用
指针大小固定,初始化需要分配内存,指针指向的地址可变
引用为变量的别名,需要初始化,初始化后不可改变
存在空值的指针,不存在空值的引用
9. new 和 malloc
new delete 操作符
malloc free 函数
常规八股
要了解new的底层需要熟悉operator操作符
10.万能引用和右值引用
涉及类型推导的引用,才会是万能引用。
对于万能引用,如果采用右值来初始化,得到的是一个右值引用,如果采用左值来初始化万能引用,那么得到的是一个左值引用。
左值引用能引用左值和右值,右值引用只能引用右值
Move的功能是将一个左值引用强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义,从实现原理上讲基本等同一个强制类型转换。
优点:可以将左值变成右值而避免拷贝构造,将对象的状态所有权从一个对象转移到另一个对象,只是转移,没有内存搬迁或者内存拷贝。
11. 解释中断,以及底层发生的操作细节
中断:CPU 终端服务程序
识别中断源、找到中断程序、保存当前任务的各个寄存器状态、进入中断处理程序后的返回等
...
对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期(即中断响应指令的指令周期)。
不太详细...
12. C++多线程在操作系统上如何运作的
- 多线程实现:C++标准库提供了对多线程的支持,开发人员可以使用
std::thread
等类来创建和管理线程。C++11引入了对并发编程的支持,包括原子操作、互斥量、条件变量等,使得线程间的同步和通信更加方便。此外,还有一些其他的第三方库,如Boost.Thread等,也提供了丰富的多线程支持。 - 多进程实现:在C++中,可以使用操作系统提供的系统调用或者一些跨平台的库来创建和管理多个独立的进程。例如,可以使用
fork()
系统调用来创建一个新的进程,或者使用exec()
系列函数来在新的进程中执行其他程序。另外,一些跨平台的库,如Boost.Interprocess等,也提供了一些跨平台的进程间通信的工具,如共享内存、消息队列等。
构造函数作用是创建新线程并指定要执行的函数及其参数。可调用对象可以是以下五个中的任何一个:
- 函数指针
- Lambda 表达式
- 函数对象(仿函数)
- 非静态成员函数
- 静态成员函数
13. 进程、线程间通信方式
管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
14. 信号量在操作系统中如何实现
15. 虚函数
通过基类访问派生类定义的函数。所谓虚函数就是在基类定义一个未实现的函数名,为了提高程序的可读性,建议后代中虚函数都加上virtual关键字。
其子类重新定义父类的做法这种行为成为覆盖(override),或者为重写。
- override:保证在派生类中声明的重载函数,与基类的虚函数有相同的签名;
- final:阻止类的进一步派生 和 虚函数的进一步重写。
在类的继承中,每一个class产生一堆指向virtual function的指针,放在vtbl(虚表)中。对于每一个class object 被添加了一个指针,指向相关的virtual table,这里指针称为vptr(虚指针)。
纯虚函数:
在基类中没有定义,但要求任何派生类都要定义自己的实现方法。作用:提供一个函数接口
virtual void funtion1()=0
含有纯虚函数的类叫做派生类
16. 构造函数、析构函数
析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,而析构函数就是对象在销毁的时候自动调用的的
当我们在类中声明了一些指针变量时,我们一般就在析构函数中进行释放空间,因为系统并不会释放指针变量指向的空间,我们需要自己来delete,而一般这个delete就放在析构函数里面
构造函数:
1.构造函数是一种特殊的成员函数,不需要用户来调用,定义对象时被自动执行。
2.构造函数名字与类名相同,无返回类型。
3.可以由用户自己定义实现,根据需要设计对数据成员进行初始化,依旧可以设置函数的默认参数。
析构函数:清理工作,系统自动调用
1.析构函数没有返回值,没有参数;
2.没有参数,所以不能重载,一个类仅有一个析构函数;
3.析构函数除了释放工作,还可以做一些用户希望它做的一些工作,比如输出一些信息。
手撕题:
1.内存池初始化
释放
用户申请
用户释放
只能用两次malloc, 不能用stl
2.手写一个类实现lambda表达式
3. 手写单例模式
4. 后续遍历二叉树(非递归)
力扣hot100 + 面试150
1. 内存池
内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
经典内存池实现过程
(1)先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象;
(2)每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间;
(3)某个内存节点一旦分配出去,从空闲内存节点链表中去除;
(4)一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表;
(5)如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。
C++ 内存池介绍与经典内存池的实现-CSDN博客
2. lambda表达式
手写一个类实现lambda表达式
3. 手写单例模式
4. 遍历二叉树(非递归)
再补充.....
还有其他常规C++八股总结:史上最全C/C++面试、C++面经八股文,一文带你彻底搞懂C/C++面试、C++面经!_c++八股-CSDN博客
C++八股文(基础面试题)_c++面试八股文-CSDN博客
参考:C/C++ : 函数返回值为结构体,以及返回指针_c++ 返回结构体-CSDN博客 C++基础重点:static和const关键字 - 知乎 (zhihu.com)C++中static、const、static const修饰变量作用详解_const static-CSDN博客编译的四个过程-预处理、编译、汇编、链接-阿里云开发者社区 (aliyun.com)CPU中断的工作原理,从最底层讲起_用逻辑门实现三选一-CSDN博客C++中的线程:完整指南 - 知乎 (zhihu.com)进程间通讯的7种方式_进程间通信的几种方法-CSDN博客 C++虚函数详解-CSDN博客C++ 内存池介绍与经典内存池的实现-CSDN博客 C++构造函数和析构函数详解 - 知乎 (zhihu.com) 析构函数-CSDN博客
大部分内容直接粘贴过来的
侵删