中高级面试必考模块,涵盖多线程、锁、C++11 新特性、模板、编译链接,偏向工程实战与底层原理,综合区分技术深度。
1. C++ 线程创建与线程函数
1.1 线程创建方式(std::thread)
C++11 提供标准线程库 std::thread,跨平台,替代系统原生线程 API。
1.2 四种可作为线程入口的函数
- 普通全局函数
- 类静态成员函数
- lambda 表达式
- 仿函数 (重载 operator())
1.3 核心接口与规则
thread t(入口函数, 参数):创建并自动启动线程。join():阻塞主线程,等待子线程执行完毕,回收线程资源。detach():分离线程,线程后台运行,主线程不再等待,由系统自动回收资源。- 禁止:线程对象生命周期结束前,未 join/detach,程序崩溃。
高频坑点
线程传参默认值拷贝 ;如需传递引用,使用 std::ref/std::cref。
2. 互斥锁 mutex、死锁成因与避免
2.1 std::mutex 互斥锁
作用:保护共享资源,保证同一时刻仅一个线程访问临界区,解决数据竞争。
lock():加锁,阻塞等待unlock():解锁std::lock_guard:RAII 风格自动锁,构造加锁、析构解锁,避免漏解锁。
2.2 死锁四大必要条件(必背)
- 互斥条件:资源同一时刻仅一个线程持有。
- 请求与保持:线程持有已有资源,又请求新资源。
- 不可剥夺:资源不能被强行抢占,只能持有者主动释放。
- 循环等待:线程间形成环形资源等待链。
2.3 死锁解决方案
- 破坏循环等待:统一锁的加锁顺序。
- 破坏请求保持:一次性申请所有需要的锁。
- 使用
std::try_lock:尝试加锁,失败则主动释放已有资源。 - 减少嵌套锁,尽量降低锁粒度。
3. 条件变量 condition_variable
3.1 作用
实现线程间同步与等待唤醒,常搭配互斥锁使用,用于生产者-消费者模型。
3.2 核心接口
wait(lock):解锁并阻塞当前线程,等待被唤醒;唤醒后重新加锁。notify_one():唤醒一个等待中的线程。notify_all():唤醒所有等待线程。
3.3 标准使用范式
- 线程先持有互斥锁。
- 调用 wait 进入等待,释放锁。
- 其他线程满足条件后执行 notify 唤醒。
- 被唤醒线程重新获取锁,继续执行。
补充
wait 存在虚假唤醒 ,必须搭配 while 循环判断条件,不能用 if。
4. 原子变量 atomic 作用
4.1 定义
std::atomic C++11 原子类型,无锁并发方案。
4.2 核心作用
- 保证变量读写是原子操作,不会被线程打断。
- 不使用互斥锁,规避锁的开销,性能更高。
- 解决多线程下简单数值竞争问题(计数、标志位)。
4.3 适用与局限
- 适合:计数器、状态标记、简单加减运算。
- 局限 :仅保障单个变量原子性,复杂业务逻辑仍需锁。
对比
atomic 轻量高效;mutex 功能强大,适用于复杂临界区。
5. 锁的性能对比(互斥锁 / 自旋锁)
5.1 互斥锁 mutex
- 原理:加锁失败时,线程休眠,让出 CPU,进入等待队列。
- 开销:线程切换、上下文切换开销大。
- 适用场景:临界区执行时间长、锁等待时间久。
5.2 自旋锁 spin_lock
- 原理:加锁失败,线程循环轮询,不放弃 CPU。
- 开销:无上下文切换,CPU 空转消耗。
- 适用场景:临界区极短,预计很快获取锁。
5.3 选型总结
- 代码执行快、竞争轻微 → 自旋锁。
- 代码执行慢、长时间等待 → 互斥锁。
- C++ 标准库无原生自旋锁,可使用
std::mutex+ 自定义自旋、或系统 API 实现。
6. C++11 lambda 表达式详解
6.1 基础语法
[捕获列表](参数列表) mutable -> 返回值类型 { 函数体 }
6.2 捕获方式(必考)
[]:不捕获任何变量。[=]:值捕获,拷贝外部所有变量。[&]:引用捕获,引用外部所有变量。[var]:仅值捕获指定变量。[&var]:仅引用捕获指定变量。[this]:类内 lambda,捕获当前对象 this 指针。
6.3 关键字说明
mutable:允许修改值捕获的变量(默认只读)。-> 返回值:函数体多分支、返回值不统一时必须显式指定。
6.4 应用场景
线程入口、STL 算法回调、临时简单函数、异步回调。
7. 右值引用与移动语义
7.1 左值 & 右值
- 左值:有名字、可取地址的变量。
- 右值:临时值、无名字、不可取地址(字面量、函数返回临时对象)。
7.2 右值引用 &&
语法:类型&&,专门绑定右值,延长临时对象生命周期。
7.3 移动语义 std::move
std::move:强制将左值转为右值,本身不移动数据。- 移动构造/移动赋值:盗取临时对象资源,浅拷贝接管堆内存,替代深拷贝。
- 价值:避免大对象拷贝,大幅提升性能。
7.4 核心结论
拷贝语义:复制一份新数据;移动语义:转移资源所有权,零拷贝开销。
8. 完美转发 std::forward
8.1 问题背景
模板函数传参时,参数左右值属性会丢失,无法原样转发。
8.2 std::forward 作用
保持参数原有左值/右值属性,实现参数原样转发。
8.3 万能引用 + 完美转发组合
- 万能引用
T&&:既能接收左值,也能接收右值。 - 搭配
std::forward<T>(arg):保留值类别,完成完美转发。
区分记忆
std::move:左值转右值,一定移动。std::forward:原样转发,保留原有属性。
9. 模板基础、函数模板与类模板
9.1 模板核心作用
实现代码复用、泛型编程,一套代码适配多种数据类型,编译期实例化。
9.2 函数模板
- 语法:
template <typename T> 函数定义 - 原理:编译器根据实参类型,自动推导 T 并生成对应重载函数。
- 特点:类型安全,编译期检查。
9.3 类模板
- 语法:
template <class T> class 类名 - 原理:使用时必须显式指定类型,无法自动推导。
- 应用:STL 容器全部基于类模板实现。
9.4 模板特化
- 全特化:针对某一具体类型单独实现。
- 偏特化:针对部分参数/类型特征定制实现。
10. 编译链接四大过程(预处理 / 编译 / 汇编 / 链接)
1. 预处理(.c/.cpp → .i)
- 展开宏、替换文本
- 处理
#include头文件、删除注释 - 处理条件编译
#ifdef/#endif
2. 编译(.i → .s 汇编文件)
- 语法、语义、类型检查
- 翻译成汇编代码,优化代码
3. 汇编(.s → .o/.obj 目标文件)
- 汇编指令转为二进制机器码
- 生成目标文件,包含代码段、数据段、符号表
4. 链接(多个.o → 可执行文件)
- 合并所有目标文件、段表。
- 符号解析:找到未定义函数/变量地址。
- 地址重定位:修正虚拟地址。
- 分为静态链接 和动态链接。
编译错误 vs 链接错��
- 编译错误:语法错误、头文件缺失、类型不匹配。
- 链接错误:函数未实现、重复定义、符号未找到。
🔥 本章综合高频追问
-
问 :移动构造为什么不常手动写?
答 :编译器会在满足条件时合成默认移动构造。
-
问 :线程 detach 后还能访问局部变量吗?
答:不建议,主线程退出会销毁局部变量,子线程野访问崩溃。
-
问 :模板是编译期还是运行期特性?
答 :编译期,模板会在实例化阶段生成对应代码。
📝 模块总结
本模块是 C++ 进阶分水岭,覆盖多线程并发、C++11 核心新特性、泛型模板、编译原理。面试中用于考察工程能力与语言深度,全部掌握可应对中高级岗位综合面试。
