📋 摘要

1. 值传递和引用传递的区别?

📋 回答重点
  • 值传递:函数调用时触发一次参数拷贝,修改参数不影响原始值。大对象拷贝开销大,会复制整个对象,效率低。
  • 引用传递:函数调用时传递的是参数别名,不触发拷贝,效率高,但修改直接影响原始值。

什么场景用引用?① 避免大对象拷贝(std::vector、std::string) ② 需要修改实参原始值。 什么场景用值传递?① 基础类型(int、char、double) ② 拷贝成本低,值传递更简洁。

扩展知识

  • 只读大对象参数,优先 const T&:既避免拷贝开销,又防止意外修改原始值。
  • 底层机制对比与代码示例:C++ 基础-初级

2. C 和 C++ 的区别?

📋 回答重点
  • 编程范式:C 是面向过程语言,通过函数组织逻辑;C++ 是多范式语言,核心是面向对象(类与对象),也支持泛型、过程式编程。
  • 继承与多态:C++ 支持继承、虚函数、多态;C 语言没有这些概念。
  • 函数重载:C++ 支持同名函数按参数类型/个数重载;C 不支持,函数名必须唯一。
  • 模板:C++ 支持模板(泛型编程),可实现编译期多态;C 语言无模板。
  • 内存管理:C++ 用 new/delete,还支持智能指针;C 用 malloc/free,手动管理且无 RAII。
  • 标准库:C++ STL 丰富得多(容器、算法、std::string 等),C 只有少量基础库。

扩展知识

  • C++ 特有机制:RAII(资源获取即初始化)、移动语义(std::move)、lambda 表达式、右值引用。
  • 详细对比表与代码示例:C++ 基础-初级

3. C++ 中 sizeof 和 strlen 的区别?

📋 回答重点
  • sizeof 是运算符(编译期求值),strlen 是函数(<cstring>,运行期求值)。
  • sizeof 返回字节数(数组含 \0),任何类型/表达式都可用;strlen 返回字符数(不含 \0,遇到 \0 停止),只用于 C 风格字符串。

经典陷阱:sizeof 对数组返回整个数组字节数,对指针只返回指针大小(4/8 字节)——这是高频考点。

扩展知识

  • sizeof 的参数不会被求值(sizeof(a+b) 不真正执行)。
  • 数组退化为指针时 sizeof 的变化、strlen 遇 \0 提前停止:C++ 基础-初级

4. C++ 中 extern 有什么作用?extern “C” 有什么作用?

📋 回答重点
  • extern:声明变量/函数在其他文件定义,当前文件只是引用,用于跨文件共享(避免重复定义,违反 ODR)。
  • extern “C”:让 C++ 编译器按 C 的链接规则处理,不做名称修饰(name mangling)。

为什么需要?C++ 为支持函数重载会修饰函数名(add(int,int) → _Z3addii),而 C 不修饰(_add),所以 C++ 调用 C 库时链接不上。extern "C" 就是告诉编译器”按 C 的方式链接”。

扩展知识

  • 跨平台写法:用 __cplusplus 宏包裹,让同一头文件可被 C 和 C++ 同时包含。
  • 典型场景:C++ 调用 C 库(OpenSSL、SQLite)、提供 C 兼容 API(FFI)。
  • 名称修饰详解:C++ 基础-初级

5. 什么是左值和右值?有什么区别?

📋 回答重点

一般可以从两个方向来区分:

方向一:能不能放在等号左边

  • 左值:可以放到赋值运算符左边的东西
  • 右值:不可以放到赋值运算符左边的东西

方向二:能不能取地址、有没有名字

  • 左值:可以取地址,并且有名字的东西(通常是变量)
  • 右值:不能取地址,没有名字的东西(通常是常量、临时变量)

扩展知识

  • C++11 引入了右值引用(T&&),是移动语义的基础,核心目的是窃取临时对象资源,避免无意义深拷贝。
  • const 左值引用可以绑定右值(延长临时对象生命周期)。
  • 详细辨析与 std::move 用法:C++ 基础-中级

6. 什么是移动语义和完美转发?

📋 回答重点

移动语义和完美转发都是 C++11 引入的新特性,核心是优化资源管理:

移动语义:

  • 常规的资源管理是拷贝别人的资源(深拷贝,开销大)
  • 移动语义是转移资源所有权,而不是拷贝资源,性能更高
  • 通常用于大对象(std::string、std::vector 等),通过移动构造函数或移动赋值运算符实现
  • std::move 的作用是把左值转成右值引用,从而触发移动语义

完美转发:

  • 在模板函数中,把参数以原始的值类别(左值还是右值)原封不动地转发给另一个函数
  • 使用 std::forward 配合转发引用(T&&)实现,避免转发过程中丢失右值属性

扩展知识

  • 移动语义的基础是右值引用——没有右值引用就无法区分”可以偷资源的临时对象”
  • 转发引用在日常中也可以叫万能引用
  • 移动语义 vs 拷贝语义的性能对比、std::move 注意事项:C++ 基础-中级

7. 什么是 C++ 的列表初始化?

📋 回答重点

C++11 引入了列表初始化,核心语法是用花括号 {} 来初始化变量或对象:

  • 语法简洁,用 {} 替代传统的 () 或 = 初始化
  • 适用范围广:内置类型、用户自定义类型(类/结构体)、容器(vector、map 等)都能用
  • 防止类型窄化:编译期就能拦截精度丢失的隐式转换(如 double 转 int),括号初始化做不到
  • 可以接受任意长度的初始化列表,配合 std::initializer_list 实现灵活的构造函数

扩展知识


8. C++ 中三种智能指针的使用场景?

📋 回答重点

C++ 智能指针主要用于管理动态分配的内存,避免内存泄漏。C++11 引入了三种:

std::unique_ptr(独占所有权):

  • 同一时间只能有一个 unique_ptr 指向对象,不支持拷贝,只支持移动
  • 销毁时自动释放所管理的对象
  • 使用场景:① 对象只有一个所有者时;② 自动管理非堆资源(文件句柄、互斥锁等);③ 替代裸 new/delete 的首选方案

std::shared_ptr(共享所有权):

  • 多个 shared_ptr 可指向同一对象,内部用引用计数管理生命周期,最后一个释放时才真正销毁对象
  • 使用场景:① 多个所有者共享同一个对象;② 需要通过复制或赋值传递所有权

std::weak_ptr(弱引用):

  • 不拥有对象所有权,指向由 shared_ptr 管理的对象,不增加引用计数
  • 使用场景:① 解决 shared_ptr 的循环引用问题;② 观察某对象但不需要拥有它
  • 注意:weak_ptr 必须和 shared_ptr 搭配使用,访问前需要先升级为 shared_ptr(lock())

扩展知识

  • 引用计数原理、控制块(Control Block)结构、循环引用问题详解:C++ STL
  • make_shared 为什么比 new 高效(一次内存分配 + 异常安全)

9. C++11 中有哪些常用的新特性?

📋 回答重点

C++11 新特性几乎是面试必问话题,主要可以从以下几个方面回答:

  • auto 类型推导:编译器自动推导变量类型,简化代码,尤其配合模板、lambda 使用
  • 智能指针:unique_ptr、shared_ptr、weak_ptr,取代裸指针,自动管理内存,避免泄漏
  • RAII(Resource Acquisition Is Initialization):用对象生命周期管理资源(文件、锁、内存等),结合智能指针实现异常安全
  • std::thread:原生线程库,不再需要依赖平台 API(如 pthread),简化多线程编程
  • 左值右值与移动语义:右值引用(T&&)、std::move、移动构造函数,避免不必要的深拷贝
  • std::function 和 Lambda 表达式:支持闭包语法,配合 std::function 实现函数封装和回调

扩展知识

  • auto 的限制、lambda 捕获列表的完整用法、std::function 底层开销等:C++ 基础-中级

10. C++ 中 static 的作用?什么场景下用到 static?

📋 回答重点

C++ 中 static 有三种主要用法,分别对应不同场景:

1. 修饰局部变量:

  • 变量存储在静态存储区,生命周期贯穿整个程序运行期间
  • 只在第一次执行到声明处时初始化一次,后续函数调用不会重新初始化
  • 使用场景:需要保留函数调用之间的状态(如计数器、缓存等)

2. 修饰全局变量或函数:

  • 限制作用域为当前文件内部(内部链接属性),其他文件不可见
  • 使用场景:避免不同文件之间的命名冲突,封装实现细节

3. 修饰类的成员变量或函数:

  • 属于类本身而非某个对象,所有实例共享同一份 static 成员变量
  • static 成员函数没有 this 指针,可以在没有类实例的情况下调用
  • 使用场景:① 统计类的实例数量;② 实现单例模式;③ 工具方法(不依赖对象状态)

扩展知识

  • static 局部变量的线程安全问题、static 成员的类外初始化、单例模式实现:C++ 基础-中级

11. C++ 中 const 的作用?谈谈你对 const 的理解?

📋 回答重点

const 的核心作用是声明不可变性——告诉编译器和阅读代码的人”这个值不该被修改”。但 const 的用法远不止”常量”这么简单,它贯穿了 C++ 的类型系统:

  • 常变量:声明变量为常量,初始化后不可修改,编译期进行安全检查
  • const 成员函数:承诺不修改对象状态,只读操作,允许 const 对象调用
  • const 指针和引用:区分”指向常量的指针”和”常量指针”,控制访问权限
  • const 参数/返回值:防止函数修改传入数据,表达 API 契约

面试时建议从”常变量 → const 指针 → const 引用 → const 成员函数”由浅入深展开,最后总结 const 的本质是编译期的只读承诺。

扩展知识

  • const 指针的四种形态辨析(谁不能改 vs 指向谁不能改)
  • const 成员函数与 mutable 成员变量
  • const 的正确理解:不是”不变”,而是”不通过此接口修改”
  • constexpr 与 const 的区别(C++11 编译期常量)
  • 详细讲解与代码示例:C++ 基础-中级

12. #define 和 const 的区别?

📋 回答重点

从定义常量的角度来看,C++ 中优先使用 const,原因如下:

  • #define 是预处理指令:在预处理阶段进行纯文本替换,不进行类型检查,编译器看不到宏名,调试困难
  • const 是 C++ 关键字:在编译阶段确定类型和值,具有类型安全的特点,编译器可以进行类型检查

除此之外,const 还具有作用域控制(遵循 C++ 作用域规则)、可被调试器查看等优势,而 #define 宏全局可见直到 #undef,容易产生命名污染。

但 #define 仍有使用场景:条件编译(#ifdef / #ifndef)、函数式宏(带参数的宏),这些 const 无法替代。

扩展知识

  • #define 宏的陷阱:无括号导致的运算优先级问题、函数式宏的多次求值问题
  • constexpr 作为 const 的升级版(真正的编译期常量)
  • 现代 C++ 替代方案:inline 函数替代函数式宏、enum class 替代数值宏
  • 详细对比表与代码示例:[[C++ 基础-中级##define 和 const 的区别?]]

13. C++ 中 inline 的作用?它有什么优缺点?

📋 回答重点

inline 的作用是建议编译器将函数调用替换为函数体本身,以减少函数调用的开销,与宏的效果类似,但具有类型安全。核心目的是提高频繁调用的小型函数的执行效率。

优点:

  • 降低函数调用开销——省去了参数压栈、跳转、返回等指令
  • 函数体较小时,可以显著提升执行效率

缺点:

  • 容易导致代码膨胀——每次调用都内联展开,可执行程序体积变大,尤其当 inline 函数体较大且被多次调用时
  • inline 只是对编译器的建议,编译器可以选择忽略(递归函数、虚函数等场景编译器通常不会内联)

扩展知识

  • inline 函数的编译原理(调用点展开、链接器优化)
  • inline 解决头文件重复定义问题的妙用(C++17 inline 变量)
  • 现代编译器自动内联优化(-O2 / -O3 下的 LTO)
  • 详细讲解与代码示例:C++ 基础-中级

14. C++ 中数组和指针的区别?

📋 回答重点

数组和指针虽然都能用于访问一组数据,但本质上是完全不同的概念,主要区别体现在四个方面:

1)内存分配:

  • 数组:编译器在栈上为所有元素分配连续的内存空间,数组名代表这块内存的起始地址
  • 指针:指针本身只占用一个内存单元(4/8 字节),存储的是一个地址,可通过 new/malloc 动态分配内存

2)固定与动态大小:

  • 数组:大小在声明时就确定,必须是常量,无法在运行时改变
  • 指针:指向的内存可以在运行时动态分配和释放,灵活性更好

3)类型安全性:

  • 数组:声明时绑定具体类型,编译器在访问时可以进行类型检查
  • 指针:可以重新赋值指向不同类型地址,频繁的指针类型转换(如 int* 转 void*)容易引起类型不匹配

4)运算操作:

  • 数组:数组名是首元素的常量指针,不能直接进行 ++ / -- 等算术运算
  • 指针:可以直接进行递增、递减等算术运算,灵活访问不同位置

扩展知识

  • 数组名退化为指针的机制(函数参数中的 T[] 等价于 T*)
  • sizeof 对数组和指针的不同结果
  • std::array 和 std::vector 作为现代 C++ 的替代方案
  • 详细讲解与代码示例:C++ 基础-初级

15. C++ 中野指针和悬挂指针的区别?

📋 回答重点

野指针和悬挂指针都是”指向非法内存”的指针,但产生原因不同:

  • 野指针(Wild Pointer):指针从未被初始化,指向随机/未知地址。如 int* p; 后直接 *p = 5;。预防:声明即初始化(int* p = nullptr;)。
  • 悬挂指针(Dangling Pointer):指针曾经有效,但指向的内存已失效。典型来源:① delete/free 后未置空继续使用;② 返回局部变量的地址;③ 容器扩容导致指针/迭代器失效。预防:delete 后置 nullptr,或直接用智能指针。

扩展知识

  • 一句话区分:野指针是「没初始化」,悬挂指针是「指向的内存被释放了」。
  • 三个悬挂指针常见来源的代码示例与预防:C++ 基础-中级

16. C++ 中 volatile 关键字的作用?

📋 回答重点

volatile 告诉编译器:这个变量的值可能在程序控制之外被改变,因此每次访问都必须直接从内存读取,禁止编译器用寄存器缓存值做优化。

  • 与 const 相反:const 承诺”程序不改”,volatile 警告”编译器别假设它不变”。
  • 使用场景:硬件寄存器(内存映射 I/O)、信号处理函数共享的标志位、中断服务程序(ISR)共享变量。

扩展知识

  • 关键区分:volatile ≠ 线程安全。它只保证单个读写不被优化,但不保证原子性、不提供内存屏障,不能替代 std::atomic/std::mutex。
  • volatile 与 std::atomic 的定位差异:C++ 基础-中级

17. C++ 中 explicit 和 final 的作用?

📋 回答重点

两个关键字,分别在禁止隐式转换和禁止继承/重写上发挥作用:

explicit:修饰构造函数(或转换运算符),禁止隐式调用,只允许显式构造。防止 int 被悄悄转成自定义类型这类意外的隐式转换。经验法则:除非明确需要隐式转换,单参数构造函数一律加 explicit。

final(C++11):① 修饰类,禁止该类被继承;② 修饰虚函数,禁止该函数被子类重写。还能帮助编译器做去虚化(devirtualization)优化。

扩展知识

  • explicit 修饰转换运算符(explicit operator bool())
  • final 与 override 的配合使用
  • 代码示例与使用场景:C++ 基础-高级

18. 什么是内存对齐?为什么要内存对齐?

📋 回答重点

内存对齐:数据在内存中的存放地址必须是其大小的整数倍,编译器会在结构体成员之间/之后插入**填充字节(padding)**来满足对齐要求。

为什么要对齐:

  1. 硬件要求:很多 CPU 架构要求数据必须对齐,否则异常或无法读取
  2. 性能:CPU 按”字”(4/8 字节)读写内存,未对齐的 int 横跨两个字边界时需两次内存访问,效率减半
  3. 原子性:很多平台的原子操作要求数据对齐

本质是空间换时间——插入 padding 浪费少量内存,换取更快的访问速度。

扩展知识

  • 合理排列成员顺序(大的在前)能减少 padding
  • alignas(C++11)、#pragma pack 改变默认对齐
  • 内存布局示意与示例:C++ 基础-高级

19. C++ 中四种类型转换的使用场景?

📋 回答重点

C++ 四种命名的类型转换,各有分工:

  • static_cast:编译期常规转换——基本类型转换、类层次向上转换、void* 转具体指针、枚举转换。最常用、较安全。
  • dynamic_cast:类层次中安全的向下转换,依赖 RTTI(需要虚函数),转换失败返回 nullptr(指针)或抛异常(引用)。
  • const_cast:唯一能移除/添加 const、volatile 的转换,常用于适配不带 const 的老接口。
  • reinterpret_cast:位模式重解释,不改变二进制位(指针 ↔ 整数、不相关指针类型),最危险、平台相关。

扩展知识

  • 使用原则:首选最小权力的转换,能用 static_cast 就不用 reinterpret_cast
  • dynamic_cast 失败返回 nullptr、四种转换对比表:C++ 基础-高级

20. 什么是多态?简单介绍下 C++ 的多态?

📋 回答重点

多态指同一个接口,针对不同对象类型表现出不同行为。C++ 有两类:

编译期多态(静态):函数重载、模板、运算符重载,编译期确定,无运行时开销。

运行期多态(动态):虚函数 + 继承 + 基类指针/引用,运行期通过虚函数表动态绑定到实际对象的实现。

运行期多态的三个必要条件:① 继承 ② 虚函数(virtual + override)③ 通过指针或引用调用(值传递会对象切片,丢失多态)。

扩展知识

  • 值传递切片问题、虚析构函数的重要性
  • 代码示例与必要条件详解:C++ 基础-高级

21. C++ 中虚函数的原理?

📋 回答重点

虚函数运行期多态依赖两个机制:

  • 虚函数表(vtable):每个含虚函数的类有一张虚表,存储该类所有虚函数的地址。派生类重写虚函数 = 用派生类版本替换虚表中的对应条目。虚表每个类一份。
  • 虚函数指针(vptr):每个对象内部隐藏一个 vptr,指向它所属类的虚表。每个对象一份,构造函数自动初始化。

动态绑定过程:通过基类指针调用虚函数时,运行期 对象 → vptr → vtable → 函数地址,从而调到实际对象的实现。

扩展知识

  • 虚析构函数(delete 基类指针不泄漏派生类资源)、纯虚函数(抽象类)、虚继承(菱形继承)
  • 虚表结构示意图与代码演示:C++ 基础-高级

22. C++ 中构造函数可以是虚函数吗?

📋 回答重点

不能,编译器直接报错。核心原因:

  1. 虚函数依赖 vptr,而 vptr 在构造函数期间才建立——对象还没构造完,vptr 没就绪,无法虚分发(“先有鸡还是先有蛋”)。
  2. 构造时对象类型编译期已知——new Dog() 一定调 Dog 的构造,不存在”运行期才知道调用哪个”的不确定性,而虚函数正是解决运行期不确定性的。

对比析构函数:析构发生在对象生命末期、vptr 已就绪,且 delete 基类指针 需要运行期知道实际类型,所以析构函数可以是(而且应该是)虚函数。

扩展知识

  • 一正一反对比:构造函数不能虚 vs 析构函数应该虚
  • 需要”多态构造”时用工厂模式 / 虚克隆(virtual clone)
  • 详细讲解:C++ 基础-高级

23. C++ 的函数重载?它的优点是什么?和重写有什么区别?

📋 回答重点
  • 函数重载:同一作用域内,函数名相同但参数列表不同(个数/类型/顺序),编译器编译期根据实参决定调用哪个。
  • 优点:① 提高可读性(同一操作同一名字)② 接口统一 ③ 编译期多态(无运行时开销)。
  • 易错点:仅返回类型不同不能构成重载。

与重写(override)的区别:重载是同作用域、参数不同、编译期绑定;重写是派生类重写基类虚函数、参数相同、运行期绑定(需 virtual + override)。

扩展知识

  • 重载 vs 重写的完整对比表(作用域/参数/绑定时机):C++ 基础-初级

24. C++ 中 struct 和 class 的区别?

📋 回答重点

唯一本质区别是默认访问权限:

  • struct 默认 public(成员和继承都是)
  • class 默认 private(成员和继承都是)

除此之外两者能力完全等价(都能有成员函数、构造析构、继承、多态、模板)。习惯上 struct 用于纯数据结构(POD、配置项),class 用于封装对象(有行为和不变量)。

扩展知识

  • 默认继承权限也随之不同(struct public 继承 / class private 继承)
  • 详细讲解:C++ 基础-初级

25. C++ 中 struct 和 union 的区别?如何使用 union 做优化?

📋 回答重点
  • struct:各成员各自独立占内存,所有成员同时有效,大小 = 各成员之和 + padding。
  • union:各成员共享同一块内存,同一时刻只有一个成员有效,大小 = 最大成员的大小。

union 做优化:利用”多个字段不会同时使用”的特性,让互斥字段共享内存来节省空间(如网络消息、嵌入式受限内存)。

扩展知识

  • union 陷阱:写入新成员会覆盖旧成员、读取非激活成员是 UB
  • 现代替代:std::variant(C++17 类型安全)
  • 消息结构优化示例:C++ 基础-初级

26. C++ 中 new 和 malloc 的区别?delete 和 free 的区别?

📋 回答重点

一条主线记住全部:new = malloc + 构造函数,delete = 析构函数 + free。

new vs malloc:

  • new 是运算符,malloc 是库函数
  • new 返回类型安全的 T*,malloc 返回 void*(需强转)
  • new 自动算大小,malloc 手动 sizeof
  • new 失败抛 std::bad_alloc 异常,malloc 失败返回 NULL
  • new 自动调构造函数,malloc 不调;new 可重载,malloc 不可

delete vs free: delete 自动调析构函数、delete[] 释放数组;free 只释放内存不调析构。不能混用(new 配 delete,malloc 配 free)。

扩展知识

  • 混用是未定义行为
  • 现代 C++ 推荐智能指针而非裸 new/delete
  • 详细对比表与代码:C++ 基础-初级

27. C++ 什么场景下需要用到移动构造函数和移动赋值运算符?

📋 回答重点

类管理堆资源时深拷贝昂贵,移动构造/赋值只需转移资源指针(原对象置空),几乎零成本。典型场景:

  1. 不可拷贝只能移动的对象:std::unique_ptr、std::thread、std::mutex
  2. 容器扩容/插入临时对象:vector::push_back(临时对象) 触发移动
  3. std::move 显式转移所有权:明确不再使用原对象时
  4. 函数返回局部大对象:配合 RVO/移动避免拷贝

引出 Rule of Three/Five/Zero:Rule of Zero 说明”不手动管理资源就不需要手写移动”。

扩展知识

  • Rule of Three / Five / Zero 的完整含义
  • 移动构造代码示例:C++ 基础-中级

28. 什么是 C++ 的运算符重载?

📋 回答重点

为自定义类型定义运算符(+、<、<<、[] 等)的行为,让对象像内置类型一样使用,语法是 operator@。典型:复数 +、流 <<、容器 []。

规则限制:

  • 不能重载不存在的运算符(**)、不能改变操作数个数
  • 至少一个操作数是自定义类型
  • 不能重载:.、::、.*、?:、sizeof、typeid
  • 只能做成员函数:=、[]、()、->

扩展知识

  • 流插入运算符 operator<< 的友元写法
  • 详细讲解与规则:C++ 基础-中级

29. C++ 中 using 和 typedef 的区别?

📋 回答重点

两者都定义类型别名,但 using(C++11)更强:

  • 语法更直观:using 别名 = 类型;(赋值风格)vs typedef 类型 别名;(反直觉)
  • 支持模板别名:template<typename T> using Vec = std::vector<T>; ✅,而 typedef 做不到(需 struct 包装)

现代 C++ 推荐用 using。

扩展知识

  • typedef 只能用 struct 包装模拟模板别名
  • 函数指针等复杂类型的可读性对比:C++ 基础-中级

30. C++ 中 enum 和 enum class 的区别?

📋 回答重点

enum class(C++11 强类型枚举)对传统 enum 有三大改进:

  1. 作用域隔离:枚举值在枚举内部,需 Color::Red,防命名冲突(enum 会泄漏到外层作用域)
  2. 类型安全:不隐式转 int,需 static_cast(enum 会隐式转)
  3. 可指定底层类型:enum class Status : uint8_t {...},节省内存

现代 C++ 推荐用 enum class。

扩展知识

  • 命名冲突、隐式转换的代码对比
  • 底层类型指定示例:C++ 基础-中级

31. C++ 中析构函数一定要是虚函数吗?

📋 回答重点

不一定,取决于是否作为多态基类:

  • 必须是虚函数:当类会被”基类指针 delete 派生类对象”时。否则 delete 基类指针时静态绑定只调基类析构,派生类资源泄漏(未定义行为)。
  • 不必是虚函数:类不是基类(final 类、纯数据类),或不会通过基类指针 delete 派生对象——加 virtual 还引入 vptr 开销。

经验法则:有虚函数的类,析构也一定要虚;可用 protected 非虚析构防止基类指针 delete。

扩展知识

  • 与”构造函数不能虚”形成对比(构造不能虚、析构该虚)
  • 资源泄漏场景代码:C++ 基础-高级

32. 什么是 C++ 中的虚继承?

📋 回答重点

用于解决菱形继承(B、C 继承 A,D 继承 B、C)的数据冗余和二义性问题:

  • 问题:普通继承下,D 中有两份 A 的成员,访问 d.x 二义性
  • 解法:B、C 用 virtual 继承 A,D 中只保留一份 A 的成员

代价:① vbptr 额外间接开销 ② 对象布局更复杂 ③ 虚基类由最派生类负责初始化。 实践上优先用设计避免菱形继承(组合/接口),除非确实需要。

扩展知识

  • 菱形结构示意、最派生类初始化虚基类
  • 详细讲解:C++ 基础-高级

33. C++ 中 this 指针的作用?

📋 回答重点

this 是成员函数内部的隐含指针,指向调用该成员函数的当前对象。三大作用:

  1. 区分同名局部变量和成员变量:this->age = age;(左边成员、右边形参)
  2. 返回对象自身实现链式调用:return *this;
  3. 隐式访问成员:成员函数内访问成员实际是 this->member

特性:this 是常量指针(T* const),不能在静态成员函数中使用(静态函数无 this)。

扩展知识


34. C++ 中 memcpy 和 memmove 有什么区别?

📋 回答重点

两者都是内存拷贝函数,核心区别是是否处理内存重叠:

  • memcpy:不处理重叠,src/dest 重叠时是未定义行为;但更快
  • memmove:保证重叠时也正确(判断拷贝方向或用临时缓冲区);略慢

选择:确定不重叠用 memcpy,可能重叠(字符串插入、数组移位)用 memmove。

扩展知识

  • memmove 通过判断 dest/srce 相对位置选拷贝方向
  • 重叠场景示例:C++ 基础-初级

35. C++ 的 strcpy 和 memcpy 有什么区别?

📋 回答重点
  • 用途:strcpy 拷贝字符串,memcpy 拷贝任意内存(含二进制)
  • 结束条件:strcpy 遇 \0 停止,memcpy 按指定字节数拷贝
  • 长度要求:strcpy 自动找 \0(不需传长度),memcpy 必须手动传字节数
  • \0 处理:strcpy 会拷贝 \0,memcpy 不关心 \0

安全:两者都有溢出风险,strcpy 更危险(无长度限制),现代用 std::string 替代。

扩展知识


36. C++ 中类定义里 delete 关键字和 default 关键字的作用?

📋 回答重点
  • = default:显式要求编译器生成默认实现(默认构造/拷贝/析构等)。常用于自定义了某个构造函数后,把被抑制的默认构造”找回来”。
  • = delete:显式禁止某个函数的使用。常用于:① 禁止拷贝(= delete 拷贝构造/赋值)② 禁止特定隐式转换 ③ 禁止特定模板实例化。

扩展知识

  • 禁止拷贝的类(Mutex)、delete 掉 double 构造函数防隐式转换
  • 详细讲解:C++ 基础-中级

37. 什么是 C++ 中的 RAII?它的使用场景?

📋 回答重点

RAII(资源获取即初始化):把资源生命周期绑定到对象生命周期——构造获取资源、析构释放资源,对象销毁即自动释放。

优势:① 自动释放防泄漏 ② 异常安全(栈展开调析构)③ 代码简洁。

使用场景:智能指针(内存)、文件流(文件句柄)、lock_guard/unique_lock(互斥锁)、jthread(线程)、数据库连接等。

扩展知识


38. C++ 中 vector 的原理?resize 和 reserve 的区别?size 和 capacity 的区别?

📋 回答重点
  • vector 原理:堆上连续内存的动态数组,容量不够时倍数扩容(重新分配 + 移动旧元素,导致迭代器失效)。
  • size vs capacity:size 是当前元素个数,capacity 是已分配内存能容纳的个数(size ≤ capacity)。
  • resize vs reserve:resize(n) 改变元素个数(会构造新元素);reserve(n) 只预留容量(不构造元素,size 不变)。

建议:已知数量先 reserve 避免反复扩容。

扩展知识

  • 三指针内部结构、扩容迭代器失效
  • 详细讲解:C++ STL

39. C++ 中 deque 的原理?它内部是如何实现的?

📋 回答重点

deque 是双端队列,内部是分段连续:一块中控 map(指针数组) + 多个固定大小缓冲区。数据分散在缓冲区里,逻辑连续。

  • 头尾插入 O(1):直接操作对应缓冲区,满了加新缓冲区(不像 vector 头插要移动所有元素)
  • 随机访问 O(1):先算缓冲区(i / 缓冲区大小)、再算偏移(i % 缓冲区大小),两次寻址
  • 扩容只加缓冲区,不移动旧元素、迭代器更稳定

扩展知识

  • map + 缓冲区示意图、与 vector 对比表
  • 详细讲解:C++ STL

40. C++ 中 map 和 unordered_map 的区别?分别在什么场景下使用?

📋 回答重点
  • map:底层红黑树,有序,查找/插入/删除 O(log n),键只需 < 比较。
  • unordered_map:底层哈希表,无序,平均 O(1)(最坏 O(n)),键需哈希函数 + ==。

场景:需要有序遍历/范围查询(lower_bound)用 map;只做快速查找不关心顺序用 unordered_map。

扩展知识

  • 自定义类型做 unordered_map 键需提供哈希函数
  • 详细对比:C++ STL

41. C++ 中 list 的使用场景?

📋 回答重点

std::list 是双向链表:任意位置插入/删除 O(1)(已知迭代器),随机访问 O(n)。

使用场景:① 频繁在中间插入/删除(且已知迭代器)② 插入/删除不使其他迭代器失效(区别于 vector)。

注意:节点分散、缓存局部性差,遍历慢——很多场景下 vector 反而更快。需随机访问用 vector,头尾操作用 deque。

扩展知识

  • list 实际性能(缓存不友好)
  • 详细讲解:C++ STL

42. C++ 的 function、bind、lambda 都在什么场景下会用到?

📋 回答重点

三者都是”把可调用对象当一等公民”:

  • lambda:就地定义匿名函数(闭包),最常用——算法回调(sort 比较器)、捕获局部变量。
  • std::function:类型擦除的可调用对象包装器,统一存储函数指针/lambda/函数对象——回调函数、策略模式、事件处理、任务队列。
  • std::bind:绑定/重排参数(预绑定、换序)——但现代已被 lambda 取代。

扩展知识

  • 三者配合(function 存 lambda、bind 适配签名)
  • 详细讲解:C++ 基础-中级

43. 介绍一下 C++ 的模板,使用模板有哪些优缺点?

📋 回答重点

模板是泛型编程机制:写一份代码,编译器针对不同类型编译期生成对应版本(函数模板 + 类模板)。

优点:① 代码复用 ② 类型安全 ③ 零运行时开销(编译期多态,非虚函数动态分发)④ 泛型算法(STL 基于模板)。

缺点:① 编译时间增加 ② 代码膨胀(每类型一份代码)③ 错误信息晦涩 ④ 调试困难。

扩展知识

  • 模板 vs 虚函数(编译期 vs 运行期多态)
  • 详细讲解:C++ 基础-中级

44. C++ 中函数模板和类模板有什么区别?

📋 回答重点

两点核心区别:

  1. 实例化方式:函数模板根据实参自动推导模板参数(add(1,2) 无需写 <int>);类模板通常需显式指定(Box<int>,C++17 CTAD 后也可自动推导)。
  2. 特化能力:函数模板只能全特化,类模板支持偏特化(如 Pair<T,T> 特化两参数相同的情况)。

扩展知识

  • 函数模板无偏特化(需重载模拟)、类模板偏特化示例
  • 详细讲解:C++ 基础-中级

45. C++ 中可以使用 delete this 吗?

📋 回答重点

可以,但非常危险、有严格前提。合法前提(必须全部满足):

  1. 对象必须是 new 分配的(堆上)——栈对象/成员对象 delete this 是 UB
  2. delete this 后不能再访问任何成员(this 已悬空)
  3. 不能二次 delete

典型场景:引用计数归零自动释放(shared_ptr 内部、COM 的 Release())。现代 C++ 用智能指针替代手写 delete this。

扩展知识


46. C++ 中堆内存和栈内存的区别?

📋 回答重点
  • 栈:编译器自动分配释放,速度快,容量小(默认约 1~8MB),无碎片
  • 堆:程序员手动 new/delete,速度慢,容量大(受虚拟内存限制),有碎片
  • 栈存局部变量、函数参数;堆存大对象、动态大小、需跨作用域存活的数据
  • 栈作用域结束自动回收;堆忘记 delete 会内存泄漏
  • 典型问题:栈溢出、内存泄漏

扩展知识

  • 栈分配只需移动栈指针,因此比堆查找空闲块快得多
  • 详细讲解:C++ 基础-初级

47. C++ 的栈溢出是什么?

📋 回答重点
  • 定义:栈上分配的内存超过栈最大容量,导致崩溃(段错误)
  • 常见原因一:函数内声明超大局部数组
  • 常见原因二:递归过深或无限递归,栈帧不断累积
  • 解决方案:大对象改堆分配(new / std::vector)、递归转迭代
  • 栈溢出是运行时错误,编译能通过

扩展知识

  • 编译器通常支持调整栈大小(如 -Wl,—stack)
  • 详细讲解:C++ 基础-初级

48. C++ 中为什么要使用 nullptr 而不是 NULL?

📋 回答重点
  • NULL 是宏,本质是 0(或 (void*)0),会被当成整数
  • nullptr 是 C++11 关键字,类型是 std::nullptr_t,类型安全
  • 函数重载时 NULL 可能误匹配 int 版本,nullptr 精确匹配指针版本
  • 模板推导时 NULL 被推导为 int,nullptr 推导为 std::nullptr_t
  • 结论:C++ 中一律用 nullptr

扩展知识


49. C++ 中 include <a.h> 和 include "a.h" 有什么区别?

📋 回答重点
  • 两者都是预处理指令,把文件内容原样插入当前文件
  • 区别在查找路径顺序:尖括号只查系统/标准库目录
  • 双引号先查当前源文件目录,再查系统目录
  • 习惯:标准库用尖括号,自定义头文件用双引号

扩展知识

  • 编译器 -I 参数可添加额外的头文件搜索目录
  • 详细讲解:[[C++ 基础-初级#C++ 中 include <a.h> 和 include "a.h" 有什么区别?]]

50. C++ 是否可以 include 源文件?

📋 回答重点
  • 技术上可以,include 是文本替换,不区分扩展名
  • 但工程上强烈不推荐,是坏实践
  • 危害:源文件同时被编译又包含时,导致重复定义(链接错误)
  • 正确做法:头文件放声明,源文件放定义
  • 特例:模板和 inline 函数因需完整定义,可放头文件

扩展知识


51. C++ 中命名空间有什么作用?如何使用?

📋 回答重点
  • 作用:避免命名冲突,把全局标识符按逻辑分组
  • 定义:namespace X { … },访问用 X::name
  • 使用方式:限定访问 X::name、using 声明、using 指令
  • 头文件里避免 using namespace std,防止污染包含者的命名空间
  • 匿名命名空间内部符号有内部链接,可替代 static

扩展知识

  • 命名空间支持嵌套和别名(namespace A::B、namespace AB = A::B)
  • 详细讲解:C++ 基础-初级

52. C++ 如何调用 C 语言的库?

📋 回答重点
  • 核心障碍:C++ 名称修饰(name mangling)导致符号不匹配
  • 第一步:头文件用 extern “C” 包裹,让 C++ 按 C 规则链接
  • 第二步:用 gcc 编译 C 源码为 .o,再用 g++ 链接进 C++ 程序
  • __cplusplus 宏让同一头文件同时被 C/C++ 包含
  • 不做 extern “C” 包裹会报未定义引用(undefined reference)

扩展知识


53. 指针和引用的区别是什么?

📋 回答重点
  • 指针是存地址的变量,引用是变量的别名
  • 引用必须初始化、不能为空、绑定后不可改指向
  • 指针可不初始化(野指针风险)、可为 nullptr、可改指向
  • 引用解引用无需 *,指针需要 *
  • 指针支持多级(int**),引用无多级
  • sizeof 引用得对象大小,sizeof 指针得指针大小

扩展知识

  • 引用底层常由指针实现(编译器行为,标准未强制)
  • 详细讲解:C++ 基础-初级

54. C++ 中为什么 new 和 delete 一定要配对使用?

📋 回答重点
  • new = 分配内存 + 调构造函数,delete = 调析构函数 + 释放内存
  • 配对才能保证对象生命周期完整,构造/析构正确执行
  • new 配 delete,new[] 必须配 delete[]
  • 数组不用 delete[] 是未定义行为,通常只析构第一个元素
  • 不能与 malloc/free 混用

扩展知识


55. C++ 中 malloc 申请的内存,可以用 delete 释放吗?

📋 回答重点
  • 不可以,malloc 配 free,new 配 delete,不能混用
  • malloc 分配的内存用 delete 释放是未定义行为(UB)
  • 原因:malloc 不调构造函数、delete 会调析构,语义不匹配
  • 同样,new 分配的内存也不能用 free 释放
  • 混用可能崩溃、资源泄漏或内存损坏

扩展知识

  • delete 会调用析构函数,而 malloc 分配的对象未经过构造
  • 详细讲解:C++ 基础-初级


56. 什么是 C++ 的回调函数?为什么需要回调函数?

📋 回答重点
  • 回调是把一个函数(或可调用对象)作为参数传给另一个函数,由后者在合适时机反过来调用它
  • C++ 实现方式:函数指针、std::function、lambda、函数对象(现代推荐后两者)
  • 动机一:解耦调用方与被调用方,库不知道业务逻辑
  • 动机二:事件驱动 / 异步通知(定时器、网络、信号)
  • 动机三:框架扩展点,让库代码调用用户代码(如 std::sort 比较器)

扩展知识

  • 经典示例:std::sort 传 lambda 作比较器、std::find_if 传谓词
  • 详细讲解:C++ 基础-中级

57. 什么是大端序?什么是小端序?

📋 回答重点
  • 字节序是多字节数据在内存中的存储顺序
  • 大端(Big-Endian):高字节存低地址(符合书写习惯,如 12 34 56 78)
  • 小端(Little-Endian):低字节存低地址(如 78 56 34 12)
  • 网络字节序是大端;x86/ARM 通常是小端
  • 跨端传输要 htonl/htons/ntohl/ntohs 转换

扩展知识

  • 可用 union 或 char* 读首字节判断本机大小端
  • 详细讲解:C++ 基础-中级

58. C++ 中什么是深拷贝?什么是浅拷贝?写一个标准的拷贝构造函数?

📋 回答重点
  • 浅拷贝只复制指针值(地址),多个对象共享同一份资源,可能 double free
  • 深拷贝重新分配内存并复制内容,每个对象拥有独立资源
  • 编译器默认拷贝构造/赋值是浅拷贝(成员逐一复制)
  • 标准深拷贝构造四要素:重新分配 → 复制内容 →(赋值时)释放旧资源 + 防自赋值
  • 引出 Rule of Three:析构/拷贝构造/拷贝赋值要写就一起写

扩展知识

  • 现代 C++ 更推荐 Rule of Zero(智能指针/容器)或 Rule of Five(补移动语义)
  • 详细讲解:C++ 基础-中级

59. C++ 中友元类和友元函数有什么作用?

📋 回答重点
  • 友元(friend)允许外部函数/类访问本类的 private/protected 成员,是封装的”受控例外”
  • 三类:友元函数、友元类、友元成员函数
  • 最典型场景:operator<< 流插入重载(需访问私有成员)
  • 三条性质:不传递、不继承、不是成员函数(无 this)
  • 破坏封装,应尽量少用

扩展知识

  • 其他场景:类间紧密协作(迭代器访问容器)、单元测试访问私有状态
  • 详细讲解:C++ 基础-中级

60. 介绍下 C++ 程序从编写到可执行的整个过程?

📋 回答重点
  • 四阶段:预处理 → 编译 → 汇编 → 链接
  • 预处理:宏展开、#include 插入、条件编译、删注释(产出 .i)
  • 编译:语法/语义分析,生成汇编代码(产出 .s)
  • 汇编:汇编转机器码,生成目标文件(产出 .o)
  • 链接:符号解析 + 地址重定位,合并目标文件和库,生成可执行文件

扩展知识

  • 语法错误发生在编译期;未定义引用/重复定义发生在链接期
  • g++ 分步:-E → -S → -c → 链接
  • 详细讲解:C++ 基础-中级

61. 什么是 C++ 中的 auto 和 decltype?

📋 回答重点
  • 两者都是 C++11 类型推导关键字
  • auto:根据初始化表达式推导变量类型,会丢弃引用和顶层 const
  • decltype:推导表达式类型(不求值),完整保留引用和 const
  • auto 常用于简化变量声明、范围 for;decltype 常用于模板尾置返回类型
  • 注意 decltype((x)) 得引用、auto 丢 const 的细节

扩展知识

  • 尾置返回类型:auto add(T t, U u) -> decltype(t + u)
  • 详细讲解:C++ 基础-中级

62. 什么情况下会出现内存泄漏?如何避免内存泄漏?

📋 回答重点
  • 内存泄漏:动态分配的内存没释放且没有指针再指向它,无法回收
  • 典型场景:new 后忘 delete、异常路径提前退出、容器存裸指针、shared_ptr 循环引用、指针被覆盖
  • 避免一:RAII + 智能指针(unique_ptr/shared_ptr/容器),根本方案
  • 避免二:shared_ptr 循环引用用 weak_ptr 打破
  • 检测工具:ASan(-fsanitize=address)、valgrind

扩展知识

  • 异常路径导致的泄漏最隐蔽:资源获取放构造函数、释放放析构(RAII)才能异常安全
  • 详细讲解:C++ 基础-中级

63. 平时开发 C++ 程序处理错误是使用 try-catch 还是错误码方式?

📋 回答重点
  • 不是二选一,按错误性质分工
  • 预期内、可恢复的错误 → 错误码/返回值(现代用 std::optional、std::expected)
  • 意外、难以就地处理的错误 → 异常(自动传播、异常安全)
  • 构造函数没有返回值,只能靠异常报构造失败
  • 析构函数禁止抛异常(否则可能 std::terminate)

扩展知识

  • 异常正常路径零开销、抛出路径有开销;错误码每次调用都要判断
  • 详细讲解:C++ 基础-中级

64. C++ 中模板的实现一定要写在头文件中吗?

📋 回答重点
  • 结论:不一定要写头文件,但绝大多数情况推荐写在头文件
  • 根本原因:模板是编译期实例化,编译器在实例化处必须看到完整定义
  • 分离到 .cpp 会导致使用处无法实例化 → 链接时找不到符号
  • 解决方案:定义放头文件(常用);或 .cpp 里显式实例化(需枚举所有类型);C++20 模块
  • 显式实例化局限:不适用于任意类型的泛型库(如 STL)

扩展知识

  • 显式实例化语法:template int add<int>(int, int);
  • 详细讲解:C++ 基础-中级

65. 请介绍一下 C++ 的返回值优化?

📋 回答重点
  • RVO 是编译器优化:函数返回局部对象时,直接在调用者内存上构造,省掉拷贝/移动
  • RVO(返回临时对象)vs NRVO(返回命名局部变量)
  • C++17 起对”返回未命名纯右值”强制拷贝消除(guaranteed copy elision)
  • 返回局部对象时不要写 std::move,可能阻碍 NRVO
  • 与移动语义共同解决”返回大对象”的拷贝开销

扩展知识

  • 强制拷贝消除下,拷贝/移动被 delete 的类也能 return T();
  • 详细讲解:C++ 基础-中级

66. C++ 动态库和静态库的区别?

📋 回答重点
  • 静态库:链接期把库代码复制进可执行文件,体积大、无外部依赖、升级需重编译
  • 动态库:运行期加载,体积小、可多进程共享、替换库即可升级
  • Linux 后缀:静态 .a、动态 .so;Windows:.lib / .dll
  • 动态库编译需 -fPIC(位置无关代码)
  • 动态库运行期存在找不到库的风险(LD_LIBRARY_PATH/rpath/版本冲突)

扩展知识

  • 生成:ar rcs libmath.a ... 与 g++ -shared -o libmath.so ...
  • 详细讲解:C++ 基础-中级

67. C++ 中如何实现一个单例模式?

📋 回答重点
  • 单例保证类只有一个实例并提供全局访问点
  • 硬性要求:私有化构造函数、禁止拷贝/赋值、提供静态获取方法
  • 饿汉式:启动即创建,天然线程安全但可能浪费
  • 懒汉式:按需创建,但要处理线程安全
  • 推荐 Meyers 单例:局部 static 变量 + C++11 magic static 保证线程安全初始化

扩展知识

  • 双重检查锁是 C++11 之前的老写法,现已不推荐
  • 详细讲解:C++ 基础-中级

68. C++ 成员变量的初始化顺序是固定的吗?

📋 回答重点
  • 是固定的,但顺序是声明顺序,而不是初始化列表书写顺序
  • 基类先于派生类成员初始化
  • 同一类内成员按声明顺序初始化
  • 陷阱:用声明靠后的成员初始化声明靠前的成员,会读到未初始化值
  • 编译器 -Wreorder 可对顺序不一致发出警告

扩展知识

  • 反例:Bad() : b(10), a(b) 中 a 先声明,a(b) 先执行但 b 尚未初始化
  • 详细讲解:C++ 基础-中级

69. C++ 中未初始化和已初始化的全局变量放在哪里?全局变量定义在头文件中有什么问题?

📋 回答重点
  • 已初始化全局变量在 .data(数据段),初值存入可执行文件
  • 未初始化(或零初始化)全局变量在 .bss,不占文件空间、运行时清零
  • 头文件直接定义全局变量 → 每个翻译单元一份 → 链接期重复定义错误
  • 正确做法:头文件 extern 声明 + 单处 .cpp 定义
  • C++17 可用 inline 变量直接在头文件定义

扩展知识


70. C++ 函数调用的原理是什么?什么是栈帧?

📋 回答重点
  • 每次函数调用在调用栈上分配一块内存,即栈帧(Stack Frame)
  • 栈帧内容:参数、返回地址、保存的寄存器、局部变量
  • 调用过程:压参数 → call 压返回地址 → 建立栈帧分配局部变量 → ret 弹返回地址
  • 栈向下增长,rsp 指向栈顶、rbp 指向栈帧底
  • 局部变量随栈帧销毁而失效 → 不能返回局部变量地址;递归过深 → 栈溢出

扩展知识

  • 嵌套调用是先进后出的栈结构,对应调用链
  • 详细讲解:C++ 基础-中级

71. C++ new 操作的背后都经历了什么?

📋 回答重点
  • new 两步:① 调 operator new 分配原始内存(默认封装 malloc)→ ② 调用构造函数初始化
  • new 失败抛 std::bad_alloc;new (std::nothrow) 失败返回 nullptr
  • 构造函数抛异常时,operator new 会回滚调用 operator delete 回收内存
  • new[] 额外记录元素个数,故必须配 delete[]
  • operator new 可被重载(自定义内存池),malloc 不可

扩展知识



72. 请介绍 C++ 多态的实现原理?

📋 回答重点
  • 多态实现分两类:编译期多态靠重载/模板静态决议,运行期多态靠虚函数动态绑定。
  • 运行期多态底层是 vtable + vptr:每个类一张虚表存虚函数地址,每个对象一个 vptr 指向虚表。
  • 重写虚函数 = 用派生类版本替换虚表条目;调用走 vptr → 虚表 → 函数地址的间接寻址。
  • 编译期多态零运行期开销,运行期多态多一次间接查表开销。

扩展知识

  • 编译期多态:函数重载、模板、运算符重载;运行期多态:虚函数 + 继承 + 基类指针/引用。
  • 值传递会切片丢失多态,必须用指针/引用触发动态绑定。
  • 详细讲解:C++ 基础-高级

73. C++ 如何进行性能优化?

📋 回答重点
  • 方法论先行:先 profile 定位热点,再优化,别盲目。
  • 算法/数据结构优先:降复杂度(哈希替代有序表)、缓存友好(连续内存)。
  • 内存:引用传参避免拷贝、移动语义转移资源、reserve 预留容量、emplace_back 原地构造。
  • 语言特性:constexpr 编译期求值、final 去虚化、模板。
  • 编译选项:-O2/-O3、-march=native、LTO、PGO;并发:多线程/原子/减小锁粒度。

扩展知识

  • 拷贝是 C++ 最大隐形开销;并发注意伪共享与锁竞争。
  • 过早优化是万恶之源,每次改动后重新测量验证。
  • 详细讲解:C++ 基础-高级

74. C++ QT 中信号和槽的原理是什么?

📋 回答重点
  • 本质是观察者模式:对象间松耦合通信,发送者与接收者互不知晓。
  • 原理:moc 预编译 + 元对象系统(QMetaObject),记录信号/槽名字与索引。
  • connect 建立”信号 → 接收者 → 槽”映射,发射信号时查表调用。
  • 连接类型分 Direct(同线程同步)与 Queued(跨线程异步,走事件循环)。
  • 对比回调:更松耦合、支持多对多、天然跨线程,但比直接调用有开销。

扩展知识

  • 信号只有声明无实现(moc 生成);Q_OBJECT 宏必须存在。
  • 用函数指针式 connect(&Sender::signal)可在编译期校验签名。
  • 详细讲解:C++ 基础-高级

75. 什么场景下用继承?什么场景下使用组合?

📋 回答重点
  • 原则:优先组合而非继承(Composition over Inheritance)。
  • 判据 is-a / has-a:is-a 用继承,has-a 用组合。
  • 继承 = 白盒复用、编译期静态、耦合强;组合 = 黑盒复用、运行期可替换、耦合弱。
  • 需要多态 + 稳定层次用继承;功能装配/易变场景用组合。

扩展知识

  • 错误示范:仅为复用功能而继承(Car 继承 Engine)导致语义错乱、破坏封装。
  • 组合可运行期替换内部组件(如注入不同 Engine 实现)。
  • 详细讲解:C++ 基础-高级


76. C++ 中为什么要使用 std::array?它有什么优点?

📋 回答重点
  • std::array 是固定大小的数组容器(C++11),封装 C 风格数组,大小编译期确定
  • 不会退化为指针,保留数组语义,可用 .size() 拿到大小
  • 支持标准库算法(begin/end)与 C++17 结构化绑定,可整体拷贝赋值
  • 局部 std::array 在栈上分配,零动态内存开销,性能等同原生数组
  • 相比 vector:固定大小、无堆分配、更轻量;相比 C 数组:更安全、接口更丰富

扩展知识

  • at() 有边界检查(越界抛 std::out_of_range),operator[] 无检查
  • 详细讲解:C++ STL

77. 请介绍 C++ 中 shared_ptr 的原理?shared_ptr 线程安全吗?

📋 回答重点
  • 原理:shared_ptr 通过控制块里的强引用计数管理生命周期,计数归零时释放对象
  • 引用计数是原子操作,多个线程各自拷贝/析构不同的 shared_ptr 副本是安全的
  • shared_ptr 对象本身不是线程安全的:多线程同时读写同一个 shared_ptr 对象是数据竞争
  • 多线程读写同一个 shared_ptr 需加锁,或用 std::atomic_load / std::atomic_store
  • 一句话:引用计数安全,对象本身不安全

扩展知识

  • 控制块、强/弱引用计数细节见:C++ STL
  • 线程安全深入讲解:C++ STL

78. 请介绍 C++ 中 weak_ptr 的原理?

📋 回答重点
  • weak_ptr 是弱引用,指向 shared_ptr 管理的对象但不增加强引用计数
  • 不影响对象生命周期:对象可被正常释放,weak_ptr 只观察对象是否存活
  • 使用前必须 lock() 升级为 shared_ptr;对象已销毁时 lock() 返回空 shared_ptr
  • 核心用途是打破 shared_ptr 循环引用,防止内存泄漏
  • 弱引用计数归零后控制块才释放,weak_ptr 依赖控制块判断对象存活

扩展知识

  • lock() 原子地检查强引用计数是否归零,非零则安全返回 shared_ptr
  • 详细讲解:C++ STL

79. C++ 中 shared_from_this 的作用是什么?它有什么优点?

📋 回答重点
  • 作用:让类在成员函数内安全获得指向自身 this 的 shared_ptr
  • 通过继承 std::enable_shared_from_this,调用 shared_from_this()
  • 解决痛点:直接用 shared_ptr(this) 会新建第二个控制块,导致 double free
  • 优点:返回的 shared_ptr 与已有 shared_ptr 共享同一控制块,引用计数一致
  • 前提:对象必须已被 shared_ptr 管理,否则抛 std::bad_weak_ptr

扩展知识

  • enable_shared_from_this 内部通常靠一个 weak_ptr 记住控制块
  • 详细讲解:C++ STL

80. C++ 的 std::string 内部使用的是堆内存还是栈内存?

📋 回答重点
  • 答案取决于长度:短字符串用 SSO(小字符串优化)存对象内部,长字符串才在堆上
  • SSO:string 对象内部预留小缓冲区(约 15 字节,实现相关),短串零堆分配
  • 超过 SSO 阈值才 new 一块堆内存存数据
  • 好处:大部分字符串都很短,SSO 避免堆分配、提升缓存局部性
  • SSO 是实现约定非标准强制,但主流实现都支持

扩展知识

  • .data() 返回连续缓冲区是 C++11 起标准保证的
  • 详细讲解:C++ STL

81. C++ 中 vector 的 push_back 和 emplace_back 有什么区别?

📋 回答重点
  • push_back 接受已构造好的对象,拷贝/移动到末尾
  • emplace_back 接受构造参数,在 vector 内存中原地构造
  • emplace_back 基于完美转发,省去一次临时对象的拷贝/移动,性能更优
  • 传入现成对象时两者等价,emplace_back 优势仅在传构造参数时
  • 注意 emplace_back 参数不能引用 vector 自身元素(扩容时可能悬空)

扩展知识

  • 复杂/昂贵类型(如 string、自定义类)差距最明显
  • 详细讲解:C++ STL

82. C++ 的迭代器和指针有什么区别?

📋 回答重点
  • 指针是内建类型、本质是内存地址;迭代器是”泛化的指针”
  • 指针本身是随机访问迭代器的一种,vector 迭代器常就是指针
  • 迭代器用统一接口屏蔽不同容器实现差异(连续/链表/树)
  • 不同迭代器类别能力不同:list 迭代器只能 ++,不能 +n
  • 价值:解耦算法与容器,同一算法作用于多种容器

扩展知识

  • 迭代器分五类:输入/输出/前向/双向/随机访问
  • 详细讲解:C++ STL

83. 请介绍下 std::sort 的原理?底层是快速排序吗?

📋 回答重点
  • 不是纯快排,而是内省排序 introsort(快排 + 堆排 + 插入排序的混合)
  • 主力快排(平均 O(n log n)),递归过深切堆排兜底(避免最坏 O(n²))
  • 小区间用插入排序(常数小、缓存友好)
  • 保证平均和最坏都是 O(n log n)
  • 不稳定排序、要求随机访问迭代器;stable_sort 用归并、稳定但可能多占内存

扩展知识

  • 快排选 pivot 常三数取中,进一步降低退化概率
  • 详细讲解:C++ STL

84. C++17 有哪些新特性?

📋 回答重点
  • 定位是 C++11 之后的”补强版”,不颠覆范式、专注实用
  • 结构化绑定:auto [x, y] = pair/tuple/struct 一次解包
  • if constexpr:编译期分支,未选中分支不实例化,取代部分 enable_if
  • 三个值语义容器:std::optional(可有值)、std::variant(类型安全 union)、std::any(任意类型)
  • std::string_view:不拥有内存的只读视图,避免拷贝
  • CTAD(类模板实参推导)+ inline 变量 + 折叠表达式 (args + ...)

扩展知识

  • 还有 [[nodiscard]]、std::filesystem、并行算法、RVO 强制拷贝消除
  • 详细讲解:C++ 基础-中级

85. C++20 有哪些新特性?

📋 回答重点
  • 定位是继 C++11 之后最大的一次升级,核心是”语言四大件”
  • concepts:编译期谓词约束模板参数,报错友好
  • ranges:管道式(|)组合算法,重写 STL 接口
  • coroutines:协程,co_await/co_yield/co_return 挂起/恢复
  • modules:import 取代 #include,解决头文件/ODR/编译慢
  • 实用特性:std::span、三路比较 <=>、std::format

扩展知识

  • 还有 std::jthread、std::source_location、[[likely]]/[[unlikely]]、constexpr 大幅增强
  • 详细讲解:C++ 基础-中级

86. std::move 和 std::forward 的本质区别?

📋 回答重点
  • 两者都只是 cast(类型转换),运行时零开销,本身不移动/转发任何东西
  • std::move:无条件转右值,本质 static_cast<T&&>
  • std::forward:有条件转换,左值保持左值、右值保持右值
  • forward 的”条件”来自模板参数 T 的引用折叠(左值→T=int&,右值→T=int)
  • move 要 remove_reference 避免引用折叠成左值引用

扩展知识

  • 转发函数里错用 std::move 会把左值误判为右值、调用错误的移动/拷贝
  • 详细讲解:C++ 基础-中级

87. 模板的特化和偏特化?SFINAE 是什么?如何用 enable_if 做重载约束?

📋 回答重点
  • 全特化:所有参数指定具体类型;偏特化:只约束部分参数或形态
  • 函数模板只能全特化(偏特化用重载模拟),类模板两者皆可
  • SFINAE:替换失败不是错误——候选替换失败时剔除候选、继续尝试,全部失败才报错
  • enable_if:条件为真暴露 type,否则无 type 触发 SFINAE
  • enable_if 三种位置:返回值类型 / 模板参数 / 函数参数

扩展知识

  • C++20 concepts 是对 enable_if 的更友好替代;void_t 可探测成员
  • 详细讲解:C++ 基础-中级

88. constexpr、consteval、constinit 的区别?

📋 回答重点
  • 共同点是编译期求值,区别在”强制性”
  • constexpr:变量/函数可以编译期求值,函数可在运行期退化普通函数
  • consteval:立即函数,必须编译期求值,传运行期值直接编译错误
  • constinit:变量必须编译期初始化,但之后可变(不是 const)
  • constinit 解决 static 初始化顺序问题

扩展知识

  • constexpr 变量是 const,constinit 变量不是 const;consteval 只能用于函数
  • 详细讲解:C++ 基础-中级

89. type_traits 常用工具?如何实现一个编译期类型判断?

📋 回答重点
  • 三类工具:判断 is_integral/is_pointer/is_same、变换 remove_reference/add_pointer/decay、使能 enable_if/conditional
  • C++17 加 _v(值)和 _t(类型)别名
  • 实现编译期类型判断:通用模板 false + 偏特化 true
  • 现场写 is_pointer:template<typename U> struct is_pointer<U*> 偏特化为 true
  • 探测成员用 std::void_t + decltype + declval

扩展知识

  • 标准库 is_integral 等就是”通用 false + 逐个偏特化 true”实现
  • 详细讲解:C++ 基础-中级

90. C++14 有哪些新特性?

📋 回答重点
  • 定位是 C++11 的小版本、补丁式完善
  • 泛型 lambda:参数用 auto,本质是匿名函数模板
  • std::make_unique:补上 C++11 缺失的对应物
  • 变量模板:template<typename T> constexpr T pi = ...
  • 返回类型推导:auto 直接推导,无需尾置返回类型
  • [[deprecated]] 弃用标记 + 二进制字面量/数字分隔符

扩展知识

  • 还有 std::shared_timed_mutex、放松 constexpr 限制
  • 详细讲解:C++ 基础-中级

91. lambda 的底层原理?闭包如何实现?捕获列表的本质?

📋 回答重点
  • lambda = 编译器生成的匿名类(闭包类型)+ 重载 operator()
  • 捕获列表里的变量 → 闭包类的成员变量
  • 值捕获 = 拷贝成员,引用捕获 = 引用成员
  • operator() 默认 const,加 mutable 才能改值捕获成员
  • 无捕获 lambda 可隐式转函数指针,有捕获则不能

扩展知识

  • 引用捕获的 lambda 在变量生命周期结束后调用会悬空(异步回调典型坑)
  • 详细讲解:C++ 基础-中级

92. C++20 concept 约束是什么?如何定义和使用?

📋 回答重点
  • concept 是约束模板参数的编译期谓词
  • 定义三种方式:常量表达式 / requires 表达式 / 组合
  • 使用四种写法:requires 子句、作模板参数、auto 简写、泛型 lambda
  • 相比 enable_if:可读性好、报错友好、可命名复用、天然支持重载
  • 底层仍是 SFINAE 思想,是 enable_if 的语法糖化

扩展知识

  • requires 表达式可检查 { a + b } -> convertible_to<int> 这类复合要求
  • 详细讲解:C++ 基础-中级

93. 变参模板(parameter pack)与折叠表达式?

📋 回答重点
  • 参数包:typename... Args(类型包)、Args... args(值包)
  • C++11 处理靠递归:递归函数 + 终止函数,一层层”剥开”
  • C++17 折叠表达式 (args + ...) 把递归简化为一行
  • 四种形式:一元/二元 × 左折叠/右折叠
  • sizeof...(args) 求参数个数;参数包可用于完美转发

扩展知识

  • 右折叠 (p op ...) 从右往左结合,左折叠 (... op p) 从左往右结合
  • 详细讲解:C++ 基础-中级

94. 动态库 ABI 兼容性如何保证?

📋 回答重点
  • ABI 是二进制接口(布局/mangling/调用约定),API 是源码接口
  • 破坏 ABI:增删/重排成员、改虚表、改函数签名、换编译器
  • 保证核心:extern "C" + 不透明指针,把内部实现完全隐藏
  • Pimpl 惯用法:对外只暴露一个指针,成员变动不影响对象大小
  • 保持编译器/标准库版本一致 + 符号版本控制 + ABI 检查工具

扩展知识

  • C++ 没有跨编译器统一 ABI,所以 Linux 上 C++ 库常以 C 接口导出
  • 详细讲解:C++ 基础-中级


95. 内存池如何设计?对比 new/delete 有什么优势?

📋 回答重点
  • new/delete 每次都走 malloc,有系统调用开销 + 内存碎片 + 分配慢
  • 内存池核心思想:一次大分配 + 内部快速复用,把系统调用和碎片收敛到池边界内
  • Arena:顺序分配、整体释放(reset 重置),适合生命周期一致场景
  • 对象池:预分配一批对象,空闲链表复用,省去反复构造/析构
  • 定长块池:按固定大小切块,空闲链表管理,O(1) 分配,专治同尺寸小块
  • 优势:O(1) 分配、碎片可控、连续内存缓存友好、可整体释放

扩展知识


96. 空类为什么 sizeof 是 1?虚继承下的对象内存布局?

📋 回答重点
  • 空类 sizeof == 1 而非 0:C++ 标准要求不同对象必须有不同地址
  • 空基类优化(EBO):空类作基类可不占字节,作成员则按 1 字节算
  • 含虚函数的空类有 vptr,sizeof == 8(64 位),不再是空类
  • 虚继承布局靠 vbptr(虚基类指针):每个虚继承分支带一个 vbptr 记录虚基类偏移
  • 虚基类成员只有一份,访问多一次间接;由最派生类初始化

扩展知识


97. 常见设计模式(工厂、策略、观察者)在 C++ 中如何实现?

📋 回答重点
  • 三模式分别解决:创建解耦(工厂)、算法可替换(策略)、一对多通知(观察者)
  • C++ 实现共性:抽象接口用虚函数,配合继承或组合
  • 工厂:静态/实例方法返回 unique_ptr<基类>,调用方只依赖抽象接口
  • 策略:策略接口 + 组合注入,setStrategy 运行期换算法
  • 观察者:主题维护观察者列表,attach 注册、notify 逐一通知
  • 工厂靠继承,策略/观察者靠组合(呼应组合优于继承原则)

扩展知识


98. PIMPL 惯用法的作用与实现?

📋 回答重点
  • PIMPL = Pointer to IMPLementation:私有成员全搬到内部 Impl 类,外层只留 unique_ptr<Impl>
  • 头文件只前置声明 struct Impl;,不暴露任何实现细节
  • 三大作用:编译防火墙、降低编译依赖、ABI 稳定
  • 改实现只重编译 .cpp,不波及所有使用者;增删私有成员不破坏公开 ABI
  • 代价:一次堆分配 + 一层间接调用;unique_ptr 要求析构/移动在 .cpp 定义

扩展知识

  • 不完整类型(Impl)下 unique_ptr 无法就地析构,故析构函数要在 .cpp 中
  • 需拷贝时手写深拷贝,或改 shared_ptr 获得浅拷贝
  • 详细讲解:C++ 基础-高级

99. 内存泄漏如何检测?AddressSanitizer / valgrind 的原理?

📋 回答重点
  • 两条线:预防靠 RAII/智能指针(治本),检测靠 ASan/Valgrind
  • ASan:编译插桩(-fsanitize=address)+ 影子内存 + 红区,越界/已释放立即报错
  • ASan 快(约 2 倍)、信息全(堆栈行号),但需重编译
  • Valgrind(Memcheck):动态二进制翻译,逐指令插桩模拟,无需重编译
  • Valgrind 慢(10–30 倍),用 V 位/A 位元数据检测未初始化、越界、不匹配释放、泄漏

扩展知识

  • LeakSanitizer 是 ASan 的泄漏检查子集
  • 越界读写、未初始化内存也能查,不止内存泄漏
  • 详细讲解:C++ 基础-高级

100. SIMD 向量化优化?使用过哪些 intrinsic 或库?

📋 回答重点
  • SIMD = 单指令多数据流:一条指令同时处理多个数据(SSE 4 个、AVX 8 个 float)
  • 三层手段:自动向量化(-O3 -march=native,零成本首选)→ intrinsic(_mm_/_mm256_)→ 高层库(Eigen/xsimd)
  • 自动向量化前提:无数据依赖、内存连续、循环次数可数
  • intrinsic 示例:_mm_loadu_ps + _mm_add_ps + _mm_storeu_ps 一次算 4 个 float
  • 注意点:内存对齐(alignas)、指令集分派(#ifdef __AVX__)、先 profile 再优化

扩展知识


101. 分支预测、CPU 流水线对性能的影响?

📋 回答重点
  • 流水线让多条指令重叠执行;分支指令让 CPU 需猜测走向,等结果会暂停流水线
  • 分支预测失败要冲刷流水线、丢弃投机结果,代价约 10–20 时钟周期
  • 有序数据预测几乎全中(快),随机数据约 50% 失败(慢数倍)
  • 优化①:likely/unlikely(__builtin_expect)提示编译器,冷路径放远
  • 优化②:无分支(branchless)写法消除分支;优化③:多累加器打破数据依赖链

扩展知识



102. std::optional、std::variant、std::any 的区别与使用场景?

📋 回答重点
  • std::optional<T>:可空单值,表达”可能有值也可能没有”,替代哨兵值/空指针
  • std::variant<Ts...>:类型安全 union,同一时刻是列出的多个类型之一,用 index 记录当前类型,无堆分配
  • std::any:任意类型的类型擦除,运行时可换成任意类型,通常堆分配
  • 取值方式:optional 用 */value_or,variant 用 get/visit,any 用 any_cast(类型不匹配抛 bad_any_cast)
  • 选择原则:可空用 optional、有限已知类型用 variant、运行时未知用 any

扩展知识

  • optional::value() 无值抛 bad_optional_access;value_or() 给默认值更安全
  • variant 内存 ≈ 最大成员 + tag,类型集合编译期固定;可用 std::monostate 表示”空”状态
  • any 只能存可拷贝构造对象,any_cast 需精确匹配原始类型
  • 详细讲解:C++ STL

103. std::string_view 的原理?使用时有什么坑?

📋 回答重点
  • string_view 是只读、非拥有的视图,内部只有”指针 + 长度”,不拷贝数据
  • 构造、传递、substr 都零拷贝、零堆分配,substr 是 O(1)
  • 适合做函数参数,统一接收 std::string/const char*/字面量而不拷贝
  • 坑一:悬垂引用——不拥有数据、不延长生命周期,指向的临时对象析构后悬空
  • 坑二:不保证 \0 结尾;坑三:不能直接传给需要 const char* 的 C 接口

扩展知识

  • 典型大小 16 字节(8 字节指针 + 8 字节长度)
  • 传给 printf 需显式 %.*s + .data() + 长度,或用 std::string(sv) 转回拥有型
  • 不要从临时 std::string 返回 string_view,也不要在底层被修改后继续用 view
  • 详细讲解:C++ STL

104. 数组指针和指针数组有什么区别?

📋 回答重点
  • 区别看运算符优先级:[] 优先级高于 *,括号改变结合顺序。
  • int* arr[5] 是指针数组——本质是「数组」,含 5 个 int* 元素。
  • int (*p)[5] 是数组指针——本质是「指针」,指向整个 int[5]。
  • 用法:指针数组常构建「长度可不同的二维数组(锯齿数组)」;数组指针常作二维数组形参 int (*matrix)[N]。
  • 记忆口诀:先看括号,(*p) 先指针后下标;无括号则 [] 先下标后指针。

扩展知识

  • 二维数组名 int grid[2][3] 退化后是指向 int[3] 的数组指针,正好匹配 int (*p)[3]。
  • 数组名退化为 int* 的机制,是理解数组指针的前提。
  • 详细讲解:C++ 基础-初级

105. C++ 中位域(bit field)是什么?有什么用途?

📋 回答重点
  • 位域:在 struct/union 中按位指定成员宽度,语法 unsigned int a : 3;。
  • 作用是把多个小字段压缩到同一个字节/字,节省内存。
  • 四大用途:节省内存、网络协议头解析、硬件寄存器映射、标志位/权限集合。
  • 三个坑:位域不能取地址、布局平台/编译器相关、建议用无符号类型。

扩展知识

  • 位域是「内存对齐/struct/union」话题里按位压缩的补充手段,跨平台协议解析时布局不可移植需谨慎。
  • 详细讲解:C++ 基础-初级

106. 函数指针、函数对象、std::function 有什么区别?

📋 回答重点
  • 三者都能表示「可调用对象」,抽象层次递进。
  • 函数指针:指向函数的指针,无状态、最底层,适合 C 风格回调。
  • 函数对象:重载 operator() 的类对象,能携带状态、可被内联、性能最好。
  • std::function:类型擦除的包装器,能统一装函数指针/函数对象/lambda/bind 结果,但有间接调用开销。
  • 选型:要状态+性能用函数对象,要统一接口/异构存储用 std::function,简单回调用函数指针。

扩展知识

  • 与 lambda 底层原理(闭包类 + operator())一脉相承;std::function 的类型擦除开销使其不如函数对象可内联。
  • 详细讲解:C++ 基础-中级


107. C++ 中 set 和 multiset 的原理和使用场景?

📋 回答重点
  • 两者底层都是红黑树(平衡二叉搜索树),元素有序,查找/插入/删除 O(log n)
  • 唯一区别:set 键唯一(自动去重),multiset 键可重复
  • set 用于”去重 + 有序”、快速判存在、范围查询;multiset 用于”有序 + 允许重复”
  • multiset 用 count 统计出现次数、equal_range 拿相等区间

扩展知识

  • multiset 的 erase(value) 删掉所有等于 value 的元素,erase(find(value)) 只删一个
  • 与 map 相比:set 只存 key(value 就是 key),map 存 key-value 对
  • 详细讲解:C++ STL

108. C++ 中各种容器的迭代器失效场景总结?

📋 回答重点
  • 连续容器 vector:扩容全部失效,中间插入/删除之后全部失效
  • 分段连续 deque:两端操作迭代器失效但引用不失效,中间操作全部失效
  • 节点式 list/map/set:只有被删除的迭代器失效,插入不影响其他
  • 哈希容器 unordered_*:rehash 全部失效,删除只失效被删者
  • 用 reserve 预留容量避免 vector 扩容和 rehash 失效;erase 用返回值继续遍历

扩展知识

  • 失效后的迭代器继续使用是未定义行为
  • deque 两端插入不使引用/指针失效(元素本身不搬移)
  • 详细讲解:C++ STL

109. STL 空间配置器 allocator 的作用?它如何管理内存?

📋 回答重点
  • 作用:把内存分配/释放(allocate/deallocate)与对象构造/析构(construct/destroy)分离
  • 经典实现 SGI STL 二级配置器:>128 字节用 malloc,≤128 字节用内存池 + 16 条自由链表
  • 内存池/自由链表减少小额内存的 malloc 调用与内存碎片
  • vector::reserve 只分配内存不构造对象,正是依赖这个分离设计

扩展知识

  • 现代 std::allocator 大多退化为对 operator new/delete 的封装,但分离思想仍是考点
  • 二级配置器按 8 字节对齐分 16 档(8/16/24…128 字节)
  • 详细讲解:C++ STL