拾疏 C++ 开发面经:类默认成员函数、虚函数实现与进程线程
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
- C++ 编译器会为类默认生成哪些成员函数?
- 拷贝构造函数在什么情况下会被调用?
- 什么情况下需要自己实现拷贝构造函数?
- 虚函数的实现原理是什么?
- 堆和栈有什么区别?
- 重载和重写有什么区别?
- 抽象类和接口有什么区别?
- 进程和线程有什么区别与联系?
- 进程间通信有哪些方式?
- 线程间如何通信?
- 红黑树有哪些优点?有哪些应用场景?
- 平时玩哪些游戏?投入时间比较多的有哪些?
- 你的薪资预期是多少?
《参考解析》
-
默认成员函数要答全,更要答出「什么时候不再生成」:C++11 起编译器可能隐含生成的是八个——默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符,以及两个取地址运算符重载(const 与非 const)。真正拉开差距的是后半句:只要你显式声明了拷贝构造或拷贝赋值,编译器就不会再生成移动操作(原来的拷贝操作也被标记为弃用);声明了析构函数同样会抑制移动操作的生成。所以「我需要移动语义」的类,五个特殊成员函数要么全写、要么用
= default全部显式声明,这就是所谓的三法则 / 五法则。 -
拷贝构造的调用时机要能举出代码场景:用同类型对象初始化一个新对象、对象以值传参、以值返回(可能被编译器 RVO 或 C++17 的保证省略规则跳过)、容器扩容时搬移元素(C++11 之后优先走移动构造)。答的时候把「拷贝构造 / 拷贝赋值 / 移动构造」三者在什么语句下触发分清楚,比背定义有效。需要自己实现的判据是类是否持有资源——裸指针、文件句柄、锁、socket:默认的浅拷贝会让两个对象指向同一块内存,析构时二次释放。这就是 RAII 与智能指针要解决的问题,能顺口带到
unique_ptr/shared_ptr和「能用组合与容器就别手写析构」会加分。 -
虚函数的实现原理:虚表指针加虚表,外加三个常见坑:带虚函数的对象头部会多一个 vptr,指向该类的虚函数表,表里放的是虚函数的实际地址;通过基类指针调用时是运行期按 vptr 找到最终覆盖的版本,所以是动态绑定。常被追问的三点:构造函数里调用虚函数不会走派生类版本(此时 vptr 还指向当前正在构造的类);基类析构必须声明为虚函数,否则
delete基类指针不会调用派生类析构;内联、静态绑定与final会让某些调用在编译期就确定下来。虚表属于实现细节而非标准,面试时说明「主流实现是虚表」比断言「一定是」稳妥。 -
堆栈、重载重写、抽象类与接口:三组高频对比要成对答:堆靠
new/malloc手动申请释放、空间大但可能碎片化且慢,栈由编译器自动分配回收、连续且快但容量有限(递归过深就栈溢出)。重载是同一作用域内同名不同参数、编译期静态决议;重写是派生类覆盖基类虚函数、运行期动态绑定,注意协变返回类型与访问权限可以不同。抽象类是含纯虚函数的类、可以有成员变量和实现,接口是只声明不实现(C++ 用纯虚类模拟),一个类只能继承一个抽象类、但可以实现多个接口。 -
进程与线程这组题要落到「通信方式」收尾:进程有独立地址空间、隔离性好、创建与切换开销大;线程共享地址空间、切换轻、一个线程崩溃可能带走整个进程。进程间通信的常见列举:管道与命名管道、消息队列、共享内存(最快但需要自己做同步)、信号量、信号、socket(可跨主机)。线程间通信不是靠内核对象传数据,而是共享变量加同步原语——互斥锁、条件变量、读写锁、信号量、原子变量与无锁队列。答到一半能补一句「共享内存配信号量才是完整答案,光有共享内存没有同步等于埋雷」,就跳出了背诵。
-
红黑树的优点与应用场景要能对应到具体代码库:红黑树是近似平衡的二叉搜索树,查找、插入、删除都是 O(log n);相比 AVL 它牺牲一点查询效率换取更少的旋转次数,插入删除更稳,所以工程里更常用。应用场景可以点名:
std::map/std::set/std::multimap、Linux 内核的完全公平调度器与虚拟内存区域管理、epoll 的内核事件表、Nginx 的定时器与文件缓存。追问常落在「为什么不用哈希表」——哈希是平均 O(1) 但无序、最坏退化、扩容有抖动,需要有序遍历或范围查询时红黑树更合适。 -
游戏偏好和薪资预期这两问别当成闲聊:被问平时玩什么游戏、投入多少时间,面试官在看你的真实兴趣和表达状态,也在判断你和业务方向的贴合度(问游戏经历的团队往往做游戏或泛娱乐),照实说、能讲出你观察到的设计细节最好,别硬凑「喜欢研究引擎源码」。薪资预期不要说「都可以」,也不要只报一个数字:给一个调研过的区间、说明区间依据(城市、同岗位校招公开区间、自己的项目与实习匹配度),并补一句更看重成长与方向,这样既表达了预期又留出了谈判空间。