C++ 面试题大全(2025-2026 最新版)
涵盖 C++98 到 C++23,从基础到架构师级别,按主题分类整理。
目录
基础必考篇(TOP 10) 面向对象:虚函数与多态 现代 C++ 新特性(C++11/14/17/20/23) 智能指针 移动语义与完美转发 STL 容器底层原理 并发编程与无锁编程 设计模式 高频手撕代码题 大厂面试差异 分岗位高频题 Lambda 表达式深入 操作系统基础 IO 多路复用 平台与架构 调试与版本控制
一、基础必考篇
Q1: 指针与引用的区别
特性 指针 引用 是否可为空 可以为 nullptr 不可为空 可否重新绑定 可以指向不同对象 一旦绑定不可更改 是否需要解引用 需要 * 或 -> 直接使用 是否占用内存 占用(存地址) 不占(只是别名,编译器优化) 可否多级 二级/多级指针 没有多级引用 底层实现 存地址 本质也是存地址(编译器视角)
Q2: new/delete vs malloc/free
特性 new/deletemalloc/free本质 C++ 运算符 C 库函数 调用构造函数 ✅ 自动调用 ❌ 不调用 调用析构函数 ✅ 自动调用 ❌ 不调用 失败行为 抛 std::bad_alloc 异常 返回 NULL 申请大小 编译器自动计算 需手动计算字节数 重载 可重载 不可重载 内存来源 自由存储区 堆
Q3: const 关键字的多种用法
const int a = 10 ;
int const b = 20 ;
const int * p1 = & a;
int const * p2 = & a;
int * const p3 = & a;
const int * const p4 = & a;
void foo ( ) const ;
const std:: string& bar ( ) ;
Q4: static 关键字的作用
作用域 效果 局部静态变量 生命周期为整个程序,首次执行到定义处初始化(C++11起线程安全) 全局静态变量/函数 作用域限制在当前文件(内部链接),外部文件不可见 静态成员变量 属于类而非对象,所有对象共享,需在类外定义 静态成员函数 无 this 指针,只能访问静态成员
Q5: 堆与栈的区别
特性 栈 (Stack) 堆 (Heap) 分配方式 编译器自动 程序员手动(new/malloc) 大小限制 较小(通常几MB) 较大(受系统内存限制) 分配速度 极快(一条CPU指令) 较慢(需查找空闲块) 生命周期 作用域结束自动释放 需手动释放 碎片问题 无 可能产生 线程安全 天然线程安全 需同步
Q6: 深拷贝与浅拷贝
class String {
char * data;
public :
String ( const String& s) {
data = new char [ strlen ( s. data) + 1 ] ;
strcpy ( data, s. data) ;
}
} ;
Q7: 四种强制类型转换
转换 用途 特点 static_cast相关类型转换、非多态父子转换 编译期检查 dynamic_cast多态父子转换(含运行时类型检查) 运行时检查,失败返回nullptr/抛异常 const_cast移除/添加const属性 仅改const属性 reinterpret_cast任意类型间转换(二进制重解释) 最危险,慎用
Q8: extern "C" 的作用
C++ 编译器会做名称修饰(name mangling),extern "C" 告诉编译器按 C 链接方式处理,避免名称修饰。用于 C/C++ 混合编程。
extern "C" {
void c_function ( int a) ;
}
Q9: inline 函数的理解
编译器将函数体展开到调用处,减少函数调用开销。现代编译器会自行判断是否内联,inline 关键字只是建议。主要解决头文件中函数重复定义 的问题(内联函数允许多个编译单元内重复定义)。
Q10: struct 与 class 的区别
只有一点:默认访问权限不同 。struct 默认 public,class 默认 private。其他完全相同。
二、面向对象:虚函数与多态
多态实现原理:vtable + vptr
对象内存布局:
┌───────────────┐
│ vptr │──→ vtable(虚函数表,代码段/只读数据段)
├───────────────┤ ┌──────────────────────┐
│ 成员变量 │ │ &ClassName::func1 │
└───────────────┘ │ &ClassName::func2 │
└──────────────────────┘
概念 说明 vtable(虚函数表) 编译期生成,类级别,同类型对象共享,存于代码段 vptr(虚表指针) 对象级别,在构造函数中初始化 动态绑定流程 对象 → vptr → vtable[索引] → 函数地址 → 调用
Q: 为什么构造函数不能是虚函数?
对象中的 vptr 是在构造函数执行期间才初始化的。如果构造函数是虚函数,调用前需要通过 vptr 查找,但此时 vptr 还未初始化——鸡生蛋问题。
Q: 为什么析构函数建议为虚函数?
当通过基类指针 delete 子类对象时,如果基类析构函数不是虚函数,则只会调用基类析构,子类资源泄漏。
Base* p = new Derived ( ) ;
delete p;
Q: 重载、重写、隐藏的区别
特性 重载 (Overload) 重写/覆盖 (Override) 隐藏 (Hide) 作用域 同一类中 基类-派生类之间 基类-派生类之间 函数名 相同 相同 相同 参数 必须不同 必须相同 只需同名 virtual 不要求 必须 非虚函数
Q: 什么是对象切片(Object Slicing)?
子类对象按值传递给父类参数时,子类特有部分被"切掉",vptr 也变为父类的 vptr,多态消失。
Q: override 和 final 的作用
override :显式声明重写,编译器检查签名是否匹配final :禁止子类进一步重写此函数,或禁止类被继承
三、现代 C++ 新特性
C++11 核心特性
特性 说明 auto / decltype类型自动推导 右值引用 && 移动语义基础 std::move / std::forward移动 + 完美转发 智能指针 unique_ptr, shared_ptr, weak_ptrLambda 表达式 [capture](params) -> ret { body }nullptr类型安全的空指针 for 范围循环for (auto& x : container)= delete / = default显式禁止/默认特殊函数 constexpr编译期常量表达式 线程库 std::thread, std::mutex, std::condition_variable
C++14 增强
泛型 Lambda:[](auto a, auto b) { return a + b; } std::make_unique<T>() 工厂函数constexpr 支持更多语法(循环、分支)
C++17 核心特性
特性 说明 用法 std::optional<T>可能无值的值类型 替代指针表示"可能为空" std::variant<T...>类型安全的联合体 替代 union std::string_view非拥有字符串视图 不拷贝字符串的高性能传递 if constexpr编译期条件分支 模板中根据类型走不同逻辑 结构化绑定 解构元组/结构体 auto [x, y, z] = tuple;std::any可存任意类型的容器 类型安全的 void* CTAD 类模板参数推导 std::pair p{1, 2.0}; 无需写模板参数Fold Expressions 参数包折叠 (args + ...)
template < typename T >
auto get_value ( T t) {
if constexpr ( std:: is_pointer_v< T> )
return * t;
else
return t;
}
C++20 核心特性
1. Concepts(概念)
编译期约束模板参数,让模板错误信息更清晰。
# include <concepts>
template < typename T >
concept Addable = requires ( T a, T b) {
{ a + b } -> std:: same_as< T> ;
} ;
template < Addable T>
T add ( T a, T b) { return a + b; }
template < typename T > requires std:: integral< T>
T f1 ( T a) ;
template < std:: integral T>
T f2 ( T a) ;
template < typename T >
T f3 ( T a) requires std:: integral< T> ;
std:: integral auto f4 ( std:: integral auto a) ;
2. Ranges(范围库)
管道式操作,惰性求值,零中间容器。
# include <ranges>
std:: vector< int > nums = { 1 , 2 , 3 , 4 , 5 , 6 } ;
auto result = nums
| std:: views:: filter ( [ ] ( int n) { return n % 2 == 0 ; } )
| std:: views:: transform ( [ ] ( int n) { return n * 10 ; } ) ;
std:: ranges:: sort ( nums) ;
View Adaptor 功能 views::filter(pred)过滤 views::transform(fn)映射 views::take(n)取前n个 views::drop(n)跳过前n个 views::reverse反向 views::iota生成序列
3. Coroutines(协程)
三个关键字:co_await、co_yield、co_return。无栈协程,挂起时状态存于堆分配的协程帧。
Generator< int > fibonacci ( ) {
int a = 0 , b = 1 ;
while ( true ) {
co_yield a;
int tmp = a;
a = b;
b = tmp + b;
}
}
4. Modules(模块)
替代 #include 头文件机制,提升编译速度,隔离命名空间。
5. std::span<T>
轻量级视图,不拥有数据,安全替代 T* + size。
C++23 重点
特性 说明 std::expected<T, E>返回值带错误信息(替代异常) std::flat_map / std::flat_set连续内存存储的有序容器 std::mdspan多维视图 std::generator同步协程生成器 std::ranges::to<>将 View 收集到容器 views::enumerate带索引遍历
四、智能指针
总览对比
特性 unique_ptrshared_ptrweak_ptr所有权 独占 共享 无(观察者) 引用计数 无 有(use_count) 只增 weak_count 拷贝 禁止 允许 允许 移动 允许 允许 允许 大小 裸指针级别(不含删除器) 2 个指针(对象+控制块) 2 个指针(同上) 内存开销 最小 额外控制块 同 shared_ptr 典型场景 工厂函数、独占资源、PIMPL 多处共享、异步任务 打破循环引用、观察者模式
一、std::unique_ptr — 独占所有权
核心语义 :同一时刻只有一个 unique_ptr 拥有对象。禁止拷贝,只能移动。
创建方式
# include <memory>
auto p1 = std:: make_unique < int > ( 42 ) ;
auto p2 = std:: make_unique < std:: string> ( 10 , 'x' ) ;
std:: unique_ptr< int > p3 ( new int ( 100 ) ) ;
std:: unique_ptr< int > p4 ( new int ( 200 ) ) ;
auto arr = std:: make_unique < int [ ] > ( 10 ) ;
arr[ 0 ] = 1 ;
所有权转移 — 只能移动
auto p1 = std:: make_unique < int > ( 42 ) ;
std:: unique_ptr< int > p2 = std:: move ( p1) ;
std:: unique_ptr< int > process ( std:: unique_ptr< int > input) {
* input += 1 ;
return input;
}
auto result = process ( std:: make_unique < int > ( 10 ) ) ;
常用操作
auto p = std:: make_unique < int > ( 42 ) ;
int val = * p;
std:: string s = p-> c_str ( ) ;
int * raw = p. get ( ) ;
int * raw2 = p. release ( ) ;
delete raw2;
p. reset ( ) ;
p. reset ( new int ( 100 ) ) ;
auto p2 = std:: make_unique < int > ( 200 ) ;
p. swap ( p2) ;
if ( p) { }
if ( ! p) { }
if ( p == nullptr ) { }
自定义删除器
auto file_deleter = [ ] ( FILE* f) {
if ( f) fclose ( f) ;
} ;
std:: unique_ptr< FILE, decltype ( file_deleter) > fp ( fopen ( "test.txt" , "r" ) , file_deleter) ;
auto free_deleter = [ ] ( void * p) { std:: free ( p) ; } ;
std:: unique_ptr< char , decltype ( free_deleter) > buf (
static_cast < char * > ( std:: malloc ( 1024 ) ) , free_deleter
) ;
struct Connection { void close ( ) ; } ;
auto conn_deleter = [ ] ( Connection* c) { c-> close ( ) ; } ;
std:: unique_ptr< Connection, decltype ( conn_deleter) > conn ( new Connection, conn_deleter) ;
实现原理(极简版)
template < typename T >
class SimpleUniquePtr {
public :
explicit SimpleUniquePtr ( T* ptr = nullptr ) : ptr_ ( ptr) { }
~ SimpleUniquePtr ( ) { if ( ptr_) delete ptr_; }
SimpleUniquePtr ( const SimpleUniquePtr& ) = delete ;
SimpleUniquePtr& operator = ( const SimpleUniquePtr& ) = delete ;
SimpleUniquePtr ( SimpleUniquePtr&& other) noexcept
: ptr_ ( other. ptr_) { other. ptr_ = nullptr ; }
SimpleUniquePtr& operator = ( SimpleUniquePtr&& other) noexcept {
if ( this != & other) {
if ( ptr_) delete ptr_;
ptr_ = other. ptr_;
other. ptr_ = nullptr ;
}
return * this ;
}
T& operator * ( ) const { return * ptr_; }
T* operator -> ( ) const { return ptr_; }
T* get ( ) const { return ptr_; }
explicit operator bool ( ) const { return ptr_ != nullptr ; }
T* release ( ) { T* tmp = ptr_; ptr_ = nullptr ; return tmp; }
void reset ( T* ptr = nullptr ) { if ( ptr_) delete ptr_; ptr_ = ptr; }
private :
T* ptr_;
} ;
二、std::shared_ptr — 共享所有权
核心语义 :多个 shared_ptr 可共享同一对象的所有权,通过引用计数 管理生命周期。最后一个 shared_ptr 销毁时释放对象。
内存模型
┌─────────────────┐ ┌─────────────┐ ┌──────────────────────┐
│ shared_ptr A │────→│ 控制块 │ │ 对象 T │
├─────────────────┤ ├─────────────┤ ├──────────────────────┤
│ ptr_ ──────────┼──→ │ use_count=2 │ │ ... 数据成员 ... │
└─────────────────┘ │ weak_count=0│ └──────────────────────┘
│ deleter │
┌─────────────────┐ │ allocator │
│ shared_ptr B │ └─────────────┘
├─────────────────┤ ↑
│ ptr_ ──────────┼───────────┘
│ ctrl_ ─────────┼──→ 控制块
└─────────────────┘
释放时机:
- use_count → 0:销毁对象 T(调用 deleter)
- use_count=0 且 weak_count=0:销毁控制块
创建方式
auto sp1 = std:: make_shared < int > ( 42 ) ;
auto sp2 = std:: make_shared < std:: string> ( "hello" ) ;
auto sp3 = std:: make_shared < std:: vector< int >> ( 100 , 0 ) ;
auto arr = std:: make_shared < int [ ] > ( 10 ) ;
std:: shared_ptr< int > sp4 ( new int ( 100 ) ) ;
auto up = std:: make_unique < int > ( 42 ) ;
std:: shared_ptr< int > sp5 = std:: move ( up) ;
拷贝与引用计数
auto sp1 = std:: make_shared < int > ( 42 ) ;
std:: cout << sp1. use_count ( ) << "\n" ;
{
std:: shared_ptr< int > sp2 = sp1;
std:: cout << sp1. use_count ( ) << "\n" ;
std:: shared_ptr< int > sp3 ( sp1) ;
std:: cout << sp1. use_count ( ) << "\n" ;
auto sp4 = sp1;
std:: cout << sp1. use_count ( ) << "\n" ;
}
std:: cout << sp1. use_count ( ) << "\n" ;
常用操作
auto sp = std:: make_shared < int > ( 42 ) ;
int val = * sp;
int * raw = sp. get ( ) ;
sp. reset ( ) ;
sp. reset ( new int ( 200 ) ) ;
if ( sp) { }
if ( sp == nullptr ) { }
long n = sp. use_count ( ) ;
bool uniq = sp. unique ( ) ;
自定义删除器
auto deleter = [ ] ( int * p) { delete p; } ;
std:: shared_ptr< int > sp1 ( new int ( 42 ) , deleter) ;
std:: shared_ptr< int > sp2 ( new int ( 100 ) , [ ] ( int * p) { delete p; } ) ;
线程安全
┌──────────────────────────────────────────────┐
│ shared_ptr 的线程安全性分两层: │
├──────────────────────────────────────────────┤
│ 1. 控制块/引用计数:线程安全(atomic 操作) │
│ → 多个线程拷贝/销毁同一个 shared_ptr 安全 │
│ 2. 管理的对象数据:不保证线程安全 │
│ → 多线程访问对象成员需要额外 mutex │
│ 3. 同一个 shared_ptr 对象:非线程安全 │
│ → 多线程同时赋值给同一个 shared_ptr 需同步 │
└──────────────────────────────────────────────┘
void worker ( std:: shared_ptr< Data> sp) { }
auto sp = std:: make_shared < Data> ( ) ;
std:: thread t1 ( worker, sp) ;
std:: thread t2 ( worker, sp) ;
std:: mutex mtx;
void safe_worker ( std:: shared_ptr< Data> sp) {
std:: lock_guard< std:: mutex> lock ( mtx) ;
sp-> modify ( ) ;
}
enable_shared_from_this
问题 :在成员函数中如何正确返回 this 的 shared_ptr?
struct Bad {
std:: shared_ptr< Bad> get_shared ( ) {
return std:: shared_ptr < Bad> ( this ) ;
}
} ;
struct Good : public std:: enable_shared_from_this < Good > {
std:: shared_ptr< Good> get_shared ( ) {
return shared_from_this ( ) ;
}
static std:: shared_ptr< Good> create ( ) {
return std:: make_shared < Good> ( ) ;
}
} ;
auto obj = Good :: create ( ) ;
auto obj2 = obj-> get_shared ( ) ;
重要约束 :调用 shared_from_this() 前,对象必须已被 shared_ptr 管理。通常将构造函数设为 private + 提供静态工厂方法。
make_shared vs new 深入
特性 new + shared_ptrmake_shared内存分配 2 次(对象 + 控制块) 1 次(合并分配) 异常安全 f(shared_ptr<T>(new T), g()) 可能泄漏完全安全 缓存局部性 差(两块内存分散) 好(连续内存) 自定义删除器 ✅ 支持 ❌ 不支持 内存释放延迟 无延迟 对象内存随控制块一起释放(weak_ptr 存活时) 弱指针下的内存占用 只占用对象大小 占用对象+控制块大小(直到所有 weak_ptr 也释放)
三、std::weak_ptr — 观察者 / 弱引用
核心语义 :不拥有对象所有权,只是"观察" shared_ptr 管理的对象。weak_ptr 不影响引用计数中的 use_count,只增加 weak_count。
为什么需要 weak_ptr?
打破循环引用 :shared_ptr 循环引用导致内存泄漏缓存/观察者模式 :知道对象可能已被销毁,需要先检查安全的悬垂检查 :将 shared_ptr 赋值给 weak_ptr 后,即使原对象被释放也不会 crash
创建 weak_ptr
auto sp = std:: make_shared < int > ( 42 ) ;
std:: weak_ptr< int > wp1 ( sp) ;
std:: weak_ptr< int > wp2 ( wp1) ;
std:: weak_ptr< int > wp3 = sp;
核心方法
auto sp = std:: make_shared < int > ( 42 ) ;
std:: weak_ptr< int > wp = sp;
if ( ! wp. expired ( ) ) {
}
if ( auto locked = wp. lock ( ) ) {
* locked = 100 ;
} else {
}
long n = wp. use_count ( ) ;
wp. reset ( ) ;
打破循环引用(详细示例)
# include <memory>
# include <iostream>
class B ;
class A {
public :
std:: shared_ptr< B> bptr;
~ A ( ) { std:: cout << "A destroyed\n" ; }
} ;
class B {
public :
std:: weak_ptr< A> aptr;
~ B ( ) { std:: cout << "B destroyed\n" ; }
} ;
int main ( ) {
auto a = std:: make_shared < A> ( ) ;
auto b = std:: make_shared < B> ( ) ;
a-> bptr = b;
b-> aptr = a;
}
内存泄漏版(全用 shared_ptr): 安全版(一端 weak_ptr):
A ←────── B A ←────── B
│ │ │ │
└─── shared ──→ B └─── shared ──→ B
shared ──┘ weak ──→ A (不影响计数)
use_count(A) = 2 ⇢ 永远不为 0 use_count(A) = 1 ⇢ 正确释放
use_count(B) = 1 use_count(B) = 1 ⇢ 正确释放
实用场景:观察者模式 + 自动清理
class Subject {
std:: vector< std:: weak_ptr< IObserver>> observers_;
public :
void attach ( std:: shared_ptr< IObserver> obs) {
observers_. push_back ( obs) ;
}
void notify ( const Event& ev) {
for ( auto it = observers_. begin ( ) ; it != observers_. end ( ) ; ) {
if ( auto obs = it-> lock ( ) ) {
obs-> onEvent ( ev) ;
++ it;
} else {
it = observers_. erase ( it) ;
}
}
}
} ;
实用场景:缓存
class ImageCache {
std:: unordered_map< std:: string, std:: weak_ptr< Image>> cache_;
public :
std:: shared_ptr< Image> get ( const std:: string& key) {
auto & wp = cache_[ key] ;
if ( auto img = wp. lock ( ) ) {
return img;
}
auto img = std:: make_shared < Image> ( loadFromDisk ( key) ) ;
wp = img;
return img;
}
} ;
四、三种智能指针选择指南
是否需要共享所有权?
/ \
否 是
/ \
┌──────────┐ ┌───────────────┐
│unique_ptr │ 是否可能产生循环引用? │
└──────────┘ / \
是 否
/ \
┌──────────────┐ ┌────────────┐
│shared_ptr │ │ shared_ptr │
│+ weak_ptr │ │ │
│(一端用weak) │ └────────────┘
└──────────────┘
核心使用原则
原则 说明 优先 unique_ptr 90% 场景都用独占所有权即可 需要共享才用 shared_ptr 引用计数有开销(atomic ±1) 用 make_shared/make_unique 异常安全 + 性能更好(一次分配) 禁止裸 new C++14 起,new 只出现在工厂函数/make 函数内部 不要从同一个裸指针创建多个 shared_ptr 导致多个控制块 → double free weak_ptr 不负责释放weak_ptr 不能直接解引用,必须 lock() 先继承 enable_shared_from_this 需要从 this 安全获取 shared_ptr 时使用
五、移动语义与完美转发
值类别分类
类别 说明 例如 左值 (lvalue) 有名字、可寻址、可重复访问 int a = 10; 中 a纯右值 (prvalue) 临时对象、字面量 42、std::string("hello")将亡值 (xvalue) 资源即将被转移 std::move(x) 的返回值
std::move vs std::forward
特性 std::movestd::forward功能 无条件 转为右值有条件 转发(保持原值类别)本质 static_cast<T&&>依赖引用折叠的智能转换 使用场景 资源所有权转移 模板中完美转发参数 参数推导 自动推导 必须显式指定模板参数
核心误区
std::move 不移动任何东西! 它只是一个无条件的类型转换,真正的移动发生在移动构造函数/移动赋值运算符中。
具名右值引用变量是左值! 任何有名字的变量都是左值,即使类型是右值引用。
void foo ( std:: string&& s) {
data = s;
data = std:: move ( s) ;
}
引用折叠规则
组合 结果 T& &T&T& &&T&T&& &T&T&& &&T&&
规则:有一个是左值引用,结果就是左值引用;两个都是右值引用才是右值引用。
完美转发失败的情况
大括号初始化列表 {}(无法推导类型) 0 或 NULL 作为空指针常量位域(bit-fields) 仅声明但未定义的 static const 成员
六、STL 容器底层原理
vector
项目 说明 底层结构 连续内存的动态数组 内部指针 start(首), finish(尾+1), end_of_storage(容量尾)扩容倍数 MSVC: 1.5倍, GCC: 2倍 随机访问 O(1) 尾部插入 均摊 O(1),偶尔扩容时为 O(n) 中间插入/删除 O(n) 迭代器失效 扩容时全部失效;insert/erase 当前位置及之后失效
size ( ) = finish - start
capacity ( ) = end_of_storage - start
扩容为何 1.5 倍而非 2 倍? 2 倍扩容后,释放的旧内存永远无法被新的扩容请求复用,产生碎片。1.5 倍则可累积复用。
map
项目 说明 底层结构 红黑树(自平衡二叉搜索树) 插入/查找/删除 O(log n) 元素顺序 按 key 自动排序 迭代器类型 双向迭代器 适用场景 需有序遍历、范围查询、性能稳定性
红黑树 vs AVL 树: 红黑树非严格平衡,插入删除旋转次数更少,综合场景性能更优。STL 因此选择红黑树。
unordered_map
项目 说明 底层结构 哈希表(开链法/拉链法) bucket 结构 vector + 单向链表 查找 平均 O(1),最坏 O(n) Rehash 时机 load_factor() > max_load_factor() 时迭代器 单向迭代器(forward iterator) key 要求 必须支持 std::hash<Key> 和 operator==
负载因子 (load factor) : size() / bucket_count(),默认最大为 1.0。
map vs unordered_map 选择
场景 推荐 需要有序 map 范围查询 map 实时系统(稳定性能) map 纯快速查找 unordered_map 海量数据平均性能 unordered_map 内存受限 map
vector 的 resize() vs reserve()
特性 resize(n)reserve(n)修改 size ✅ ❌ 新增元素 ✅(默认值填充) ❌ 用途 改变元素个数 预留空间减少扩容
vector vs list 深入对比
特性 vectorlist底层结构 连续内存(动态数组) 双向链表(非连续节点) 随机访问 O(1) O(n) 头部插入/删除 O(n) O(1) 尾部插入/删除 均摊 O(1) O(1) 中间插入/删除 O(n)(需移动元素) O(1)(已有迭代器定位) 内存占用 紧凑,无额外指针开销 每节点额外 2 个指针(前驱+后继) 缓存友好 ✅ 极好(连续内存,预读命中) ❌ 差(节点分散,cache miss 频繁) 迭代器失效 扩容全失效;插入删除后部分失效 仅被删除节点失效,其余稳定 遍历性能 极高(指针自增,CPU 预取) 较慢(指针跳转,每次可能 cache miss)
选型原则 :
默认选 vector——连续内存带来的缓存优势在绝大多数场景胜过链表 只有频繁在中间做 O(1) 插入删除且数据量大到移动成本不可接受时,才考虑 list 需要迭代器长期稳定(插入不导致失效)时用 list
手写队列(数组实现 + 链表实现)
template < typename T >
class ArrayQueue {
public :
explicit ArrayQueue ( size_t cap) : data_ ( cap) , head_ ( 0 ) , tail_ ( 0 ) , size_ ( 0 ) , cap_ ( cap) { }
bool push ( const T& val) {
if ( size_ == cap_) return false ;
data_[ tail_] = val;
tail_ = ( tail_ + 1 ) % cap_;
size_++ ;
return true ;
}
bool pop ( T& val) {
if ( size_ == 0 ) return false ;
val = data_[ head_] ;
head_ = ( head_ + 1 ) % cap_;
size_-- ;
return true ;
}
bool empty ( ) const { return size_ == 0 ; }
size_t size ( ) const { return size_; }
private :
std:: vector< T> data_;
size_t head_, tail_, size_, cap_;
} ;
template < typename T >
class ListQueue {
public :
void push ( const T& val) {
auto node = std:: make_unique < Node> ( val) ;
if ( ! tail_) {
head_ = std:: move ( node) ;
tail_ = head_. get ( ) ;
} else {
tail_-> next = std:: move ( node) ;
tail_ = tail_-> next. get ( ) ;
}
size_++ ;
}
bool pop ( T& val) {
if ( ! head_) return false ;
val = head_-> data;
head_ = std:: move ( head_-> next) ;
if ( ! head_) tail_ = nullptr ;
size_-- ;
return true ;
}
private :
struct Node {
T data;
std:: unique_ptr< Node> next;
Node ( const T& d) : data ( d) { }
} ;
std:: unique_ptr< Node> head_;
Node* tail_ = nullptr ;
size_t size_ = 0 ;
} ;
七、并发编程与无锁编程
std::atomic 六大内存序
内存序 含义 典型场景 memory_order_relaxed只保证原子性,无顺序约束 引用计数自增 memory_order_acquire之后的读写不能重排到此之前 消费者读取标志 memory_order_release之前的读写不能重排到此之后 生产者写入标志 memory_order_acq_rel兼具 acquire + release CAS 循环 memory_order_seq_cst全局顺序一致性(最强最慢) mutex 底层实现 memory_order_consume仅依赖排序(已基本废弃) 几乎不用
volatile vs atomic
特性 volatilestd::atomic保证原子性 ❌ ✅ 防止编译器重排 ❌(仅防寄存器缓存) ✅ 防止 CPU 乱序 ❌ ✅(生成内存屏障) 用途 硬件寄存器、信号处理 多线程同步
release-acquire 同步(必考)
std:: atomic< bool > ready{ false } ;
int data = 0 ;
void producer ( ) {
data = 42 ;
ready. store ( true , std:: memory_order_release) ;
}
void consumer ( ) {
while ( ! ready. load ( std:: memory_order_acquire) )
;
assert ( data == 42 ) ;
}
无锁自旋锁
class SpinLock {
std:: atomic_flag flag = ATOMIC_FLAG_INIT;
public :
void lock ( ) {
while ( flag. test_and_set ( std:: memory_order_acquire) )
;
}
void unlock ( ) {
flag. clear ( std:: memory_order_release) ;
}
} ;
无锁栈 push(CAS)
template < typename T >
class LockFreeStack {
struct Node { T data; Node* next; Node ( const T& d) : data ( d) , next ( nullptr ) { } } ;
std:: atomic< Node* > head{ nullptr } ;
public :
void push ( const T& val) {
Node* node = new Node ( val) ;
node-> next = head. load ( std:: memory_order_relaxed) ;
while ( ! head. compare_exchange_weak (
node-> next, node,
std:: memory_order_release,
std:: memory_order_relaxed) )
;
}
} ;
compare_exchange_weak vs compare_exchange_strong
weak :可能伪失败(spurious failure),性能更好,必须放在循环中strong :保证只有值不匹配时才失败,但某些平台有额外开销
x86 vs ARM 差异
平台 内存模型 acquire/release 开销 x86 强内存模型 几乎零开销(只约束编译器) ARM/RISC-V 弱内存模型 生成 DMB/DSB 屏障指令,有真实开销
互斥锁(std::mutex)用法详解
# include <mutex>
std:: mutex mtx;
void bad_usage ( ) {
mtx. lock ( ) ;
mtx. unlock ( ) ;
}
void good_with_guard ( ) {
std:: lock_guard< std:: mutex> lock ( mtx) ;
}
void good_with_unique_lock ( ) {
std:: unique_lock< std:: mutex> lock ( mtx, std:: defer_lock) ;
lock. lock ( ) ;
lock. unlock ( ) ;
lock. lock ( ) ;
}
std:: mutex mtx1, mtx2;
void transfer ( ) {
std:: scoped_lock lock ( mtx1, mtx2) ;
}
互斥锁类型 特点 std::mutex基础互斥锁,不可递归 std::recursive_mutex同一线程可多次加锁 std::timed_mutex支持 try_lock_for() / try_lock_until() std::shared_mutex (C++17)读写锁:多读单写 std::lock_guard最简单 RAII,不可手动解锁 std::unique_lock灵活 RAII,可延迟/手动解锁/配合条件变量 std::scoped_lock (C++17)多锁 RAII,死锁避免
线程 vs 进程
特性 进程 (Process) 线程 (Thread) 定义 资源分配的基本单位 CPU 调度的基本单位 地址空间 独立,进程间默认隔离 共享,同一进程内线程共享地址空间 通信方式 管道、消息队列、共享内存、socket 共享内存(需同步保护) 创建开销 大(复制页表、分配资源) 小(只需分配栈和寄存器) 切换开销 大(切换页表、刷新 TLB) 小(同进程只需切换寄存器) 安全性 一个崩溃不影响其他进程 一个崩溃可能导致整个进程崩溃 数据共享 困难,需要 IPC 容易,但需同步保护
线程池(Thread Pool)
为什么要用线程池? 线程创建/销毁有开销,频繁创建会降低性能。线程池预先创建一组工作线程,任务到来时分配给空闲线程执行,任务完成后线程不销毁而是等待新任务。
# include <vector>
# include <queue>
# include <thread>
# include <mutex>
# include <condition_variable>
# include <functional>
# include <future>
class ThreadPool {
public :
explicit ThreadPool ( size_t num_threads) : stop_ ( false ) {
for ( size_t i = 0 ; i < num_threads; ++ i) {
workers_. emplace_back ( [ this ] {
while ( true ) {
std:: function< void ( ) > task;
{
std:: unique_lock< std:: mutex> lock ( queue_mtx_) ;
condition_. wait ( lock, [ this ] {
return stop_ || ! tasks_. empty ( ) ;
} ) ;
if ( stop_ && tasks_. empty ( ) ) return ;
task = std:: move ( tasks_. front ( ) ) ;
tasks_. pop ( ) ;
}
task ( ) ;
}
} ) ;
}
}
template < typename F , typename . . . Args>
auto submit ( F&& f, Args&& . . . args) -> std:: future< decltype ( f ( args. . . ) ) > {
using ReturnType = decltype ( f ( args. . . ) ) ;
auto task = std:: make_shared < std:: packaged_task< ReturnType ( ) >> (
std:: bind ( std:: forward < F> ( f) , std:: forward < Args> ( args) . . . )
) ;
std:: future< ReturnType> result = task-> get_future ( ) ;
{
std:: lock_guard< std:: mutex> lock ( queue_mtx_) ;
if ( stop_) throw std:: runtime_error ( "submit on stopped ThreadPool" ) ;
tasks_. emplace ( [ task] { ( * task) ( ) ; } ) ;
}
condition_. notify_one ( ) ;
return result;
}
~ ThreadPool ( ) {
{
std:: lock_guard< std:: mutex> lock ( queue_mtx_) ;
stop_ = true ;
}
condition_. notify_all ( ) ;
for ( std:: thread& worker : workers_) {
if ( worker. joinable ( ) ) worker. join ( ) ;
}
}
private :
std:: vector< std:: thread> workers_;
std:: queue< std:: function< void ( ) >> tasks_;
std:: mutex queue_mtx_;
std:: condition_variable condition_;
bool stop_;
} ;
八、设计模式
1. 单例模式(Singleton)
现代 C++ 推荐写法(C++11 局部静态变量线程安全) :
class Singleton {
public :
Singleton ( const Singleton& ) = delete ;
Singleton& operator = ( const Singleton& ) = delete ;
static Singleton& getInstance ( ) {
static Singleton instance;
return instance;
}
private :
Singleton ( ) = default ;
} ;
饿汉 vs 懒汉
方式 初始化时机 线程安全 特点 饿汉 类加载时 ✅ 天生安全 空间换时间 懒汉 首次调用时 需同步保护 时间换空间,延迟加载
2. 工厂模式(Factory)
三种工厂对比:
模式 说明 优点 缺点 简单工厂 一个工厂类按参数创建产品 简单 新增产品需改工厂类 工厂方法 定义创建接口,子类决定实例化 符合开闭原则 每增产品需增工厂类 抽象工厂 创建一系列相关对象 保证产品族一致性 扩展产品族困难
现代 C++ 可注册工厂 :
class Factory {
public :
using Creator = std:: function< std:: unique_ptr < Product> ( ) > ;
void registerProduct ( const std:: string& type, Creator creator) {
creators_[ type] = std:: move ( creator) ;
}
std:: unique_ptr< Product> createProduct ( const std:: string& type) {
if ( auto it = creators_. find ( type) ; it != creators_. end ( ) )
return it-> second ( ) ;
throw std:: runtime_error ( "Unknown type: " + type) ;
}
private :
std:: unordered_map< std:: string, Creator> creators_;
} ;
3. 观察者模式(Observer)
现代 C++ 实现(std::function + std::weak_ptr) :
class Subject {
std:: vector< std:: weak_ptr< IObserver>> observers_;
public :
void attach ( std:: shared_ptr< IObserver> obs) {
observers_. push_back ( obs) ;
}
void notify ( const Data& data) {
for ( auto it = observers_. begin ( ) ; it != observers_. end ( ) ; ) {
if ( auto obs = it-> lock ( ) ) {
obs-> update ( data) ;
++ it;
} else {
it = observers_. erase ( it) ;
}
}
}
} ;
SOLID 六大原则
原则 核心思想 单一职责(SRP) 一个类只负责一项职责 开闭原则(OCP) 对扩展开放,对修改封闭 里氏替换(LSP) 子类可以替换父类出现 依赖倒置(DIP) 依赖抽象而非具体实现 接口隔离(ISP) 多个专用接口优于单一接口 迪米特法则(LoD) 最少知道原则,降低耦合
九、高频手撕代码题
1. 手写 String 类(必考🔥)
class String {
public :
String ( const char * str = "" ) {
if ( str == nullptr ) str = "" ;
len_ = strlen ( str) ;
data_ = new char [ len_ + 1 ] ;
strcpy ( data_, str) ;
}
String ( const String& other) {
len_ = other. len_;
data_ = new char [ len_ + 1 ] ;
strcpy ( data_, other. data_) ;
}
String ( String&& other) noexcept {
len_ = other. len_;
data_ = other. data_;
other. data_ = nullptr ;
other. len_ = 0 ;
}
String& operator = ( const String& other) {
if ( this != & other) {
delete [ ] data_;
len_ = other. len_;
data_ = new char [ len_ + 1 ] ;
strcpy ( data_, other. data_) ;
}
return * this ;
}
String& operator = ( String&& other) noexcept {
if ( this != & other) {
delete [ ] data_;
len_ = other. len_;
data_ = other. data_;
other. data_ = nullptr ;
other. len_ = 0 ;
}
return * this ;
}
~ String ( ) { delete [ ] data_; }
private :
char * data_;
size_t len_;
} ;
2. 手写简化版 shared_ptr
template < typename T >
class SimpleSharedPtr {
public :
explicit SimpleSharedPtr ( T* ptr = nullptr )
: ptr_ ( ptr) , ref_count_ ( ptr ? new int ( 1 ) : nullptr ) { }
SimpleSharedPtr ( const SimpleSharedPtr& other)
: ptr_ ( other. ptr_) , ref_count_ ( other. ref_count_) {
if ( ref_count_) ( * ref_count_) ++ ;
}
SimpleSharedPtr& operator = ( const SimpleSharedPtr& other) {
if ( this != & other) {
release ( ) ;
ptr_ = other. ptr_;
ref_count_ = other. ref_count_;
if ( ref_count_) ( * ref_count_) ++ ;
}
return * this ;
}
~ SimpleSharedPtr ( ) { release ( ) ; }
T* operator -> ( ) const { return ptr_; }
T& operator * ( ) const { return * ptr_; }
int use_count ( ) const { return ref_count_ ? * ref_count_ : 0 ; }
private :
void release ( ) {
if ( ref_count_ && -- ( * ref_count_) == 0 ) {
delete ptr_;
delete ref_count_;
}
ptr_ = nullptr ;
ref_count_ = nullptr ;
}
T* ptr_;
int * ref_count_;
} ;
3. 线程安全单例(Meyers Singleton)
class Singleton {
public :
Singleton ( const Singleton& ) = delete ;
Singleton& operator = ( const Singleton& ) = delete ;
static Singleton& getInstance ( ) {
static Singleton instance;
return instance;
}
private :
Singleton ( ) = default ;
} ;
4. 生产者-消费者模型
# include <mutex>
# include <condition_variable>
# include <queue>
template < typename T >
class ProducerConsumer {
public :
void produce ( T item) {
std:: unique_lock< std:: mutex> lock ( mtx_) ;
not_full_. wait ( lock, [ this ] { return queue_. size ( ) < capacity_; } ) ;
queue_. push ( std:: move ( item) ) ;
not_empty_. notify_one ( ) ;
}
T consume ( ) {
std:: unique_lock< std:: mutex> lock ( mtx_) ;
not_empty_. wait ( lock, [ this ] { return ! queue_. empty ( ) ; } ) ;
T item = std:: move ( queue_. front ( ) ) ;
queue_. pop ( ) ;
not_full_. notify_one ( ) ;
return item;
}
private :
std:: queue< T> queue_;
std:: mutex mtx_;
std:: condition_variable not_empty_;
std:: condition_variable not_full_;
size_t capacity_ = 100 ;
} ;
5. 线程安全 LRU 缓存(O(1) get/put)
class LRUCache {
public :
LRUCache ( int capacity) : capacity_ ( capacity) { }
int get ( int key) {
std:: lock_guard< std:: mutex> lock ( mtx_) ;
auto it = map_. find ( key) ;
if ( it == map_. end ( ) ) return - 1 ;
list_. splice ( list_. begin ( ) , list_, it-> second) ;
return it-> second-> second;
}
void put ( int key, int value) {
std:: lock_guard< std:: mutex> lock ( mtx_) ;
auto it = map_. find ( key) ;
if ( it != map_. end ( ) ) {
it-> second-> second = value;
list_. splice ( list_. begin ( ) , list_, it-> second) ;
return ;
}
if ( list_. size ( ) >= capacity_) {
int old_key = list_. back ( ) . first;
list_. pop_back ( ) ;
map_. erase ( old_key) ;
}
list_. emplace_front ( key, value) ;
map_[ key] = list_. begin ( ) ;
}
private :
int capacity_;
std:: list< std:: pair< int , int >> list_;
std:: unordered_map< int , std:: list< std:: pair< int , int >> :: iterator> map_;
std:: mutex mtx_;
} ;
6. 线程安全环形缓冲区(Ring Buffer)
template < typename T >
class RingBuffer {
public :
explicit RingBuffer ( size_t size)
: buffer_ ( size) , head_ ( 0 ) , tail_ ( 0 ) , count_ ( 0 ) { }
bool push ( const T& item) {
if ( count_ == buffer_. size ( ) ) return false ;
buffer_[ head_] = item;
head_ = ( head_ + 1 ) % buffer_. size ( ) ;
count_++ ;
return true ;
}
bool pop ( T& item) {
if ( count_ == 0 ) return false ;
item = buffer_[ tail_] ;
tail_ = ( tail_ + 1 ) % buffer_. size ( ) ;
count_-- ;
return true ;
}
private :
std:: vector< T> buffer_;
std:: atomic< size_t> head_;
std:: atomic< size_t> tail_;
std:: atomic< size_t> count_;
} ;
7. 内存池(简易版本)
class MemoryPool {
public :
MemoryPool ( size_t block_size, size_t block_count)
: block_size_ ( block_size) {
pool_ = :: operator new ( block_size * block_count) ;
for ( size_t i = 0 ; i < block_count; ++ i) {
void * block = static_cast < char * > ( pool_) + i * block_size;
free_list_. push_back ( block) ;
}
}
void * allocate ( ) {
if ( free_list_. empty ( ) ) throw std:: bad_alloc ( ) ;
void * ptr = free_list_. back ( ) ;
free_list_. pop_back ( ) ;
return ptr;
}
void deallocate ( void * ptr) {
free_list_. push_back ( ptr) ;
}
~ MemoryPool ( ) { :: operator delete ( pool_) ; }
private :
void * pool_;
size_t block_size_;
std:: vector< void * > free_list_;
} ;
更多手写题清单
序号 题目 难度 考察点 1 手写 String 类 ⭐⭐⭐ 拷贝/移动构造、赋值、析构、RAII 2 手写 shared_ptr ⭐⭐⭐⭐ 引用计数、控制块、线程安全 3 线程安全单例 ⭐⭐ 局部静态变量、双重检查锁定 4 生产者-消费者 ⭐⭐⭐ 条件变量、mutex、队列 5 环形缓冲区 ⭐⭐⭐ 原子操作、取模索引 6 无锁队列(MPMC) ⭐⭐⭐⭐⭐ CAS、ABA 问题、内存序 7 内存池 ⭐⭐⭐⭐ 固定大小分配、链表管理 8 LRU 缓存 ⭐⭐⭐ list + unordered_map、线程安全 9 快速排序(模板版) ⭐⭐ 模板、递归、partition 10 线程池 ⭐⭐⭐⭐ future/promise、任务队列、条件变量
十、大厂面试差异
阿里 vs 腾讯 vs 字节
维度 阿里巴巴 腾讯 字节跳动 算法难度 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ C++ 基础深度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ 操作系统 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ 网络编程 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ STL 源码深度 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ 现代 C++ 新特性 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ 并发编程 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 设计模式 ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
十一、分岗位高频题
后端/高性能服务器方向
epoll 原理、ET/LT 模式、Reactor/Proactor 模式 零拷贝(sendfile/mmap/io_uring) C++20 协程调度器设计 手写线程池、内存池 TCP 三次握手/四次挥手、TIME_WAIT 分布式锁(Redis/ZK)、雪花算法 ID 生成
嵌入式/物联网方向
volatile 深入理解、内存对齐中断服务程序(ISR)注意事项 为什么嵌入式慎用异常和虚函数(代码体积、确定性) RTOS 任务调度、优先级反转 位操作技巧、寄存器编程 static_assert 编译期检查
游戏开发/图形引擎方向
缓存友好性设计、SIMD 优化 面向数据设计(DOD)vs 面向对象设计(OOP) ECS 架构(组件-实体-系统) 渲染管线、矩阵/四元数运算 内存分配策略(栈分配器、池分配器)
高频交易/金融系统方向
纳秒级低延迟优化 缓存行对齐(alignas(64)) 分支预测优化(likely/unlikely) NUMA 架构优化 无锁数据结构(RCU、Hazard Pointer) memory_order 深入理解
附录:推荐准备路线
阶段 时间 内容 第1阶段 1-2周 精读《Effective C++》,手写 String/shared_ptr/LRU,完成基础题 100 道 第2阶段 2-3周 按岗位方向专精:后端(网络+并发)、嵌入式(RTOS+驱动)、游戏(图形学) 第3阶段 1-2周 准备 2 个深度项目,能清晰讲解技术选型和性能优化证据;重点复习 C++20/23 新特性
2025-2026 年趋势总结 :C++ 面试呈现 基础深度 + 现代特性 + 岗位专精 三管齐下的趋势。C++20 的 Concepts、协程、Ranges 以及 C++23 的 std::expected 等新特性在字节等大厂面试中权重越来越高,无锁编程、内存模型、协程调度等高并发主题是高级岗位的必备考点。
十二、Lambda 表达式深入
Lambda 表达式在函数中的使用场景
std:: vector< int > v = { 1 , 2 , 3 , 4 , 5 } ;
std:: sort ( v. begin ( ) , v. end ( ) , [ ] ( int a, int b) { return a > b; } ) ;
int threshold = 10 ;
auto it = std:: find_if ( v. begin ( ) , v. end ( ) , [ threshold] ( int x) {
return x > threshold;
} ) ;
auto future = std:: async ( std:: launch:: async, [ & data] ( ) {
return processData ( data) ;
} ) ;
class Widget {
int threshold_ = 5 ;
public :
void filter ( std:: vector< int > & input) {
input. erase (
std:: remove_if ( input. begin ( ) , input. end ( ) ,
[ this ] ( int x) { return x < threshold_; }
) ,
input. end ( )
) ;
}
} ;
Lambda 捕获方式
捕获方式 说明 对捕获变量的影响 []不捕获 只能访问全局/静态变量 [=]按值捕获所有 Lambda 内部有一份拷贝,不影响外部 [&]按引用捕获所有 修改会影响外部,注意悬挂引用 [x, &y]x 按值,y 按引用 混合捕获 [this]捕获 this 指针 可访问类成员 [*this] (C++17)拷贝整个对象 Lambda 持有对象的拷贝 [x = std::move(obj)]初始化捕获 (C++14) 移动语义捕获
Lambda 本质
编译器将 lambda 展开为一个匿名函数对象(仿函数),捕获列表对应成员变量,operator() 对应函数体。
十三、操作系统基础
虚拟内存 vs 物理内存
特性 虚拟内存 物理内存 定义 每个进程看到的连续地址空间(逻辑概念) 实际安装在硬件上的 RAM 条 大小 32位系统 4GB,64位可达 256TB 有限,如 8GB/16GB 连续性 逻辑上连续 通过页表映射,物理上可以不连续 隔离性 进程间天然隔离(A 进程地址 0x1000 ≠ B 进程地址 0x1000) 共享同一物理空间 管理单元 页面 (Page),通常 4KB 页面帧 (Page Frame)
核心机制——页表映射 :
虚拟地址 ──→ [页表] ──→ 物理地址
0x7fff1234 MMU查询 0xa3b41234
页表项包含:
- 物理页帧号
- 有效位(是否在物理内存中)
- 脏位(是否被修改过)
- 访问位(LRU 换出用)
缺页中断(Page Fault) :访问的虚拟页不在物理内存时触发,OS 从磁盘(swap)换入到物理内存。
TLB(快表) :页表缓存,加速虚拟地址翻译。TLB miss 开销很大(需多次内存访问查页表)。
内存管理理解
堆上内存通过 new/malloc 分配,程序员手动管理生命周期。现代 C++ 通过 RAII + 智能指针将动态内存自动回收。栈上内存编译器自动管理,作用域结束自动释放。
常见问题 :
内存泄漏 :new 了没 delete → 用智能指针解决悬垂指针 :指向已释放内存的指针 → 释放后置 nullptr重复释放 :对同一地址 delete 两次 → 智能指针的引用计数解决缓冲区溢出 :越界写入 → 用 std::vector、std::string 替代裸数组内存碎片 :频繁分配释放小对象 → 内存池
可重入函数(Reentrant Function)
特性 可重入函数 不可重入函数 定义 被中断后再次调用仍正确 重入会导致数据错乱 静态/全局变量 不使用 使用 动态内存分配 不使用非原子的 malloc 可能使用 标准库函数 strcpy、memcpy(只读输入)strtok(静态缓冲区)典型场景 中断服务程序 (ISR)、信号处理函数 普通应用代码
char * strtok ( char * str, const char * delim) {
static char * saved;
}
char * strtok_r ( char * str, const char * delim, char * * saveptr) {
}
十四、IO 多路复用
select / poll / epoll 对比
特性 selectpollepollfd 数量限制 默认 1024(FD_SETSIZE) 无限制 无限制 数据结构 fd_set(位图) pollfd 数组 红黑树 + 就绪链表 扫描方式 每次遍历所有 fd → O(n) 每次遍历所有 fd → O(n) 只遍历就绪 fd → O(1) 内核态/用户态拷贝 每次调用拷贝整个 fd 集合 每次调用拷贝整个 pollfd 数组 fd 只注册一次,epoll_wait 只返回就绪事件 触发模式 水平触发 (LT) 水平触发 (LT) LT + ET(边缘触发) 适用场景 fd 少、精确时间 中等规模 大规模并发连接(C10K)
水平触发 (LT) vs 边缘触发 (ET)
特性 LT (Level Triggered) ET (Edge Triggered) 通知方式 数据未读完下次继续通知 只在状态变化时通知一次 编程复杂度 低 高(需循环读直到 EAGAIN) 惊群问题 更容易出现 相对少 适用场景 简单可靠 高性能场景
int epfd = epoll_create1 ( 0 ) ;
struct epoll_event ev;
ev. events = EPOLLIN | EPOLLET;
ev. data. fd = listen_fd;
epoll_ctl ( epfd, EPOLL_CTL_ADD, listen_fd, & ev) ;
struct epoll_event events[ MAX_EVENTS] ;
while ( running) {
int nfds = epoll_wait ( epfd, events, MAX_EVENTS, timeout) ;
for ( int i = 0 ; i < nfds; i++ ) {
if ( events[ i] . data. fd == listen_fd) {
int client = accept ( listen_fd, . . . ) ;
ev. events = EPOLLIN | EPOLLET;
ev. data. fd = client;
epoll_ctl ( epfd, EPOLL_CTL_ADD, client, & ev) ;
} else {
char buf[ 4096 ] ;
while ( true ) {
int n = read ( events[ i] . data. fd, buf, sizeof ( buf) ) ;
if ( n <= 0 ) break ;
}
}
}
}
十五、平台与架构
Windows / Linux / ARM / x86 的关系
┌─────────────────────────────────────────────┐
│ 应用层(C++ 代码) │
├─────────────────────────────────────────────┤
│ 操作系统 (OS) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Windows │ │ Linux │ │
│ │ (NT 内核) │ │ (宏内核) │ │
│ └──────┬───────┘ └──────┬───────┘ │
├──────────┼─────────────────┼─────────────────┤
│ │ 指令集架构 │ │
│ ┌──────┴───────┐ ┌──────┴───────┐ │
│ │ x86-64 │ │ ARM (aarch64)│ │
│ │ (Intel/AMD) │ │ (Apple M/高通) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────┘
维度 Windows Linux 内核类型 NT 混合内核 宏内核 (Monolithic) 线程模型 系统线程为主 pthread / std::thread 异步 IO IOCP (IO Completion Port) epoll / io_uring 动态库 .dll.so路径分隔符 \/换行符 \r\n\nABI MSVC ABI(不兼容跨编译器) Itanium ABI(GCC/Clang 兼容)
维度 x86-64 ARM (aarch64) 设计哲学 CISC(复杂指令集) RISC(精简指令集) 指令编码 变长(1-15 字节) 定长 4 字节 内存模型 强内存模型(TSO) 弱内存模型 典型设备 桌面/服务器 PC 手机、嵌入式、Mac (M1-4) 功耗 较高 较低
对 C++ 开发的影响 :
不同平台/架构的 sizeof(long) 可能不同(x86 Linux 8 字节,Win64 4 字节) 原子操作在 ARM 上有真实内存屏障开销,x86 上 acquire/release 几乎零开销 std::thread::native_handle() 返回不同类型(Windows: HANDLE,Linux: pthread_t)
十六、调试与版本控制
GDB 常用命令
命令 说明 gdb ./program启动调试 gdb ./program core分析 core dump run / r运行程序 break file.cpp:42 / b main设置断点 continue / c继续执行 next / n单步执行(不进入函数) step / s单步执行(进入函数) finish执行到当前函数返回 print x / p x打印变量值 backtrace / bt查看调用栈 frame N切换到第 N 帧 info locals查看当前帧所有局部变量 info args查看当前帧函数参数 info threads查看所有线程 thread N切换到第 N 号线程 watch x监视变量 x 的变化 list显示源码 disassemble反汇编 quit退出
在 GDB 中查看崩溃点变量状态
gdb ./program core
bt
bt full
frame 0
info locals
info args
print var_name
print *ptr
print vec.size( )
print/x var
info registers
x/16x $rsp
x/s str_ptr
info threads
thread apply all bt
前提 :需要有调试符号(编译时加 -g)且 core dump 大小限制未被关闭(ulimit -c unlimited)。
Git 常用操作
回退到之前的 commit
git reset --soft HEAD~1
git reset --mixed HEAD~1
git reset --hard HEAD~1
git reset --hard < commit-hash>
git revert HEAD
git revert < commit-hash>
操作 是否改写历史 适用场景 git reset是(本地) 本地 commit 未 push git revert否(新增 commit) 已经 push 到远程
创建分支与合并
git branch feature-xxx
git checkout -b feature-xxx
git switch -c feature-xxx
git push -u origin feature-xxx
git checkout main
git merge feature-xxx
git add < 冲突文件>
git commit
git checkout feature-xxx
git rebase main
git checkout main
git merge feature-xxx
Git 分支管理策略
main / master ← 生产就绪代码,只通过 PR 合并
└── develop ← 开发主线
├── feat/xxx ← 功能分支(短生命周期,合入后删除)
├── fix/xxx ← Bug 修复分支
└── release/x ← 发布分支
常见分支数量:日常同时活跃 2-5 个分支(1 个开发主线 + 若干功能/修复分支)。遵循"分支宜短命"原则——功能完成后尽快合入主线并删除旧分支。