C++面试题整理

C++ 面试题大全(2025-2026 最新版)

涵盖 C++98 到 C++23,从基础到架构师级别,按主题分类整理。


目录

  1. [基础必考篇(TOP 10)](#基础必考篇(TOP 10))
  2. 面向对象:虚函数与多态
  3. [现代 C++ 新特性(C++11/14/17/20/23)](#现代 C++ 新特性(C++11/14/17/20/23))
  4. 智能指针
  5. 移动语义与完美转发
  6. [STL 容器底层原理](#STL 容器底层原理)
  7. 并发编程与无锁编程
  8. 设计模式
  9. 高频手撕代码题
  10. 大厂面试差异
  11. 分岗位高频题
  12. [Lambda 表达式深入](#Lambda 表达式深入)
  13. 操作系统基础
  14. [IO 多路复用](#IO 多路复用)
  15. 平台与架构
  16. 调试与版本控制

一、基础必考篇

Q1: 指针与引用的区别

特性 指针 引用
是否可为空 可以为 nullptr 不可为空
可否重新绑定 可以指向不同对象 一旦绑定不可更改
是否需要解引用 需要 *-> 直接使用
是否占用内存 占用(存地址) 不占(只是别名,编译器优化)
可否多级 二级/多级指针 没有多级引用
底层实现 存地址 本质也是存地址(编译器视角)

Q2: new/delete vs malloc/free

特性 new/delete malloc/free
本质 C++ 运算符 C 库函数
调用构造函数 ✅ 自动调用 ❌ 不调用
调用析构函数 ✅ 自动调用 ❌ 不调用
失败行为 std::bad_alloc 异常 返回 NULL
申请大小 编译器自动计算 需手动计算字节数
重载 可重载 不可重载
内存来源 自由存储区

Q3: const 关键字的多种用法

cpp 复制代码
const int a = 10;           // 1. 常量:值不可改
int const b = 20;           // 同上,等价写法

const int* p1 = &a;         // 2. 指向常量的指针:*p1不可改,p1可改
int const* p2 = &a;         // 同上,等价写法

int* const p3 = &a;         // 3. 常量指针:p3不可改,*p3可改

const int* const p4 = &a;   // 4. 常量指针指向常量:都不能改

void foo() const;           // 5. const成员函数:不能修改成员变量(mutable除外)
const std::string& bar();   // 6. const返回值:防止调用者修改返回值

Q4: static 关键字的作用

作用域 效果
局部静态变量 生命周期为整个程序,首次执行到定义处初始化(C++11起线程安全)
全局静态变量/函数 作用域限制在当前文件(内部链接),外部文件不可见
静态成员变量 属于类而非对象,所有对象共享,需在类外定义
静态成员函数 this 指针,只能访问静态成员

Q5: 堆与栈的区别

特性 栈 (Stack) 堆 (Heap)
分配方式 编译器自动 程序员手动(new/malloc)
大小限制 较小(通常几MB) 较大(受系统内存限制)
分配速度 极快(一条CPU指令) 较慢(需查找空闲块)
生命周期 作用域结束自动释放 需手动释放
碎片问题 可能产生
线程安全 天然线程安全 需同步

Q6: 深拷贝与浅拷贝

cpp 复制代码
class String {
    char* data;
public:
    // 浅拷贝(默认拷贝构造):只复制指针
    // String(const String& s) = default;  // data 指向同一块内存
    
    // 深拷贝:复制指针指向的内容
    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++ 混合编程。

cpp 复制代码
extern "C" {
    void c_function(int a);
}

Q9: inline 函数的理解

编译器将函数体展开到调用处,减少函数调用开销。现代编译器会自行判断是否内联,inline 关键字只是建议。主要解决头文件中函数重复定义的问题(内联函数允许多个编译单元内重复定义)。

Q10: structclass 的区别

只有一点:默认访问权限不同struct 默认 publicclass 默认 private。其他完全相同。


二、面向对象:虚函数与多态

多态实现原理:vtable + vptr

复制代码
对象内存布局:
┌───────────────┐
│   vptr        │──→ vtable(虚函数表,代码段/只读数据段)
├───────────────┤     ┌──────────────────────┐
│   成员变量    │     │ &ClassName::func1    │
└───────────────┘     │ &ClassName::func2    │
                      └──────────────────────┘
概念 说明
vtable(虚函数表) 编译期生成,类级别,同类型对象共享,存于代码段
vptr(虚表指针) 对象级别,在构造函数中初始化
动态绑定流程 对象 → vptr → vtable[索引] → 函数地址 → 调用

Q: 为什么构造函数不能是虚函数?

对象中的 vptr 是在构造函数执行期间才初始化的。如果构造函数是虚函数,调用前需要通过 vptr 查找,但此时 vptr 还未初始化------鸡生蛋问题。

Q: 为什么析构函数建议为虚函数?

当通过基类指针 delete 子类对象时,如果基类析构函数不是虚函数,则只会调用基类析构,子类资源泄漏。

cpp 复制代码
Base* p = new Derived();
delete p;  // ~Base() 非虚函数 → 只调 Base 析构 → 泄漏!

Q: 重载、重写、隐藏的区别

特性 重载 (Overload) 重写/覆盖 (Override) 隐藏 (Hide)
作用域 同一类中 基类-派生类之间 基类-派生类之间
函数名 相同 相同 相同
参数 必须不同 必须相同 只需同名
virtual 不要求 必须 非虚函数

Q: 什么是对象切片(Object Slicing)?

子类对象按值传递给父类参数时,子类特有部分被"切掉",vptr 也变为父类的 vptr,多态消失。

Q: overridefinal 的作用

  • override:显式声明重写,编译器检查签名是否匹配
  • final:禁止子类进一步重写此函数,或禁止类被继承

三、现代 C++ 新特性

C++11 核心特性

特性 说明
auto / decltype 类型自动推导
右值引用 && 移动语义基础
std::move / std::forward 移动 + 完美转发
智能指针 unique_ptr, shared_ptr, weak_ptr
Lambda 表达式 [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 + ...)
cpp 复制代码
// if constexpr 示例
template<typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t;         // 编译期选此分支
    else
        return t;          // 或此分支
}

C++20 核心特性

1. Concepts(概念)

编译期约束模板参数,让模板错误信息更清晰。

cpp 复制代码
#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; }

// 四种写法:
// 1. requires 子句
template<typename T> requires std::integral<T>
T f1(T a);

// 2. 约束模板参数
template<std::integral T>
T f2(T a);

// 3. 尾部 requires
template<typename T>
T f3(T a) requires std::integral<T>;

// 4. 缩写函数模板
std::integral auto f4(std::integral auto a);
2. Ranges(范围库)

管道式操作,惰性求值,零中间容器。

cpp 复制代码
#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; });

// result: 20, 40, 60(遍历时才计算,无临时容器)
std::ranges::sort(nums);  // 直接传容器,无需 begin()/end()
View Adaptor 功能
views::filter(pred) 过滤
views::transform(fn) 映射
views::take(n) 取前n个
views::drop(n) 跳过前n个
views::reverse 反向
views::iota 生成序列
3. Coroutines(协程)

三个关键字:co_awaitco_yieldco_return。无栈协程,挂起时状态存于堆分配的协程帧。

cpp 复制代码
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_ptr shared_ptr weak_ptr
所有权 独占 共享 无(观察者)
引用计数 有(use_count) 只增 weak_count
拷贝 禁止 允许 允许
移动 允许 允许 允许
大小 裸指针级别(不含删除器) 2 个指针(对象+控制块) 2 个指针(同上)
内存开销 最小 额外控制块 同 shared_ptr
典型场景 工厂函数、独占资源、PIMPL 多处共享、异步任务 打破循环引用、观察者模式

一、std::unique_ptr --- 独占所有权

核心语义 :同一时刻只有一个 unique_ptr 拥有对象。禁止拷贝,只能移动。

创建方式
cpp 复制代码
#include <memory>

// 1. make_unique(C++14,推荐方式)
auto p1 = std::make_unique<int>(42);       // 类型自动推导
auto p2 = std::make_unique<std::string>(10, 'x');  // 对应 string(10, 'x')

// 2. 从裸指针构造(不推荐直接写 new)
std::unique_ptr<int> p3(new int(100));

// 3. C++11 无 make_unique 时的写法
std::unique_ptr<int> p4(new int(200));

// 4. 数组(C++14 起专用重载)
auto arr = std::make_unique<int[]>(10);    // int[10]
arr[0] = 1;  // 支持 operator[]
所有权转移 --- 只能移动
cpp 复制代码
auto p1 = std::make_unique<int>(42);

// std::unique_ptr<int> p2 = p1;     // ❌ 编译错误!拷贝构造已删除
std::unique_ptr<int> p2 = std::move(p1);  // ✅ 移动构造
// p1 == nullptr,所有权转移给了 p2

// 作为函数参数和返回值
std::unique_ptr<int> process(std::unique_ptr<int> input) {
    *input += 1;
    return input;  // 编译器自动 move(RVO + 移动语义)
}

auto result = process(std::make_unique<int>(10));  // 临时对象直接移动
常用操作
cpp 复制代码
auto p = std::make_unique<int>(42);

// 解引用
int val = *p;         // 获取值
std::string s = p->c_str();  // 对指向对象的成员访问

// 获取裸指针(不转移所有权)
int* raw = p.get();   // 获取裸指针,unique_ptr 仍拥有对象
// ⚠️ 不要 delete raw!也不要保存 raw 到另一个智能指针中

// 释放所有权(返回裸指针,unique_ptr 变为空)
int* raw2 = p.release();  // p 不再拥有对象,调用者负责 delete
delete raw2;              // 必须手动释放

// 重置(销毁旧对象,可选接管新对象)
p.reset();                   // 直接销毁,p == nullptr
p.reset(new int(100));       // 销毁旧对象,接管新对象

// 交换
auto p2 = std::make_unique<int>(200);
p.swap(p2);  // 交换两个 unique_ptr 所管理的对象

// 判空
if (p)      { /* 非空 */ }
if (!p)     { /* 为空 */ }
if (p == nullptr) { /* 为空 */ }
自定义删除器
cpp 复制代码
// 场景:FILE* 需要 fclose 而非 delete
auto file_deleter = [](FILE* f) {
    if (f) fclose(f);
};
std::unique_ptr<FILE, decltype(file_deleter)> fp(fopen("test.txt", "r"), file_deleter);
// 离开作用域自动调用 fclose

// 场景:malloc 分配的内存需要 free
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);
实现原理(极简版)
cpp 复制代码
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:销毁控制块
创建方式
cpp 复制代码
// 1. make_shared(强烈推荐)
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);  // vector(100, 0)

// 2. make_shared 用于数组(C++20)
auto arr = std::make_shared<int[]>(10);

// 3. 从裸指针构造(不推荐)
std::shared_ptr<int> sp4(new int(100));  // 两次内存分配

// 4. 从 unique_ptr 移动构造(转移所有权)
auto up = std::make_unique<int>(42);
std::shared_ptr<int> sp5 = std::move(up);  // up == nullptr
拷贝与引用计数
cpp 复制代码
auto sp1 = std::make_shared<int>(42);
std::cout << sp1.use_count() << "\n";  // 1

{
    std::shared_ptr<int> sp2 = sp1;     // 引用计数 +1
    std::cout << sp1.use_count() << "\n";  // 2

    std::shared_ptr<int> sp3(sp1);      // +1
    std::cout << sp1.use_count() << "\n";  // 3

    auto sp4 = sp1;                     // +1
    std::cout << sp1.use_count() << "\n";  // 4
}  // sp2, sp3, sp4 析构,引用计数回到 1

std::cout << sp1.use_count() << "\n";  // 1
// sp1 析构 → use_count 变为 0 → 对象被释放
常用操作
cpp 复制代码
auto sp = std::make_shared<int>(42);

// 访问对象
int val = *sp;
// sp->member;  // 访问成员

// 获取裸指针
int* raw = sp.get();

// 重置
sp.reset();                  // 释放当前对象,sp 变空(use_count 减 1)
sp.reset(new int(200));      // 释放当前对象,接管新对象

// 判空
if (sp) { /* 非空 */ }
if (sp == nullptr) { /* 为空 */ }

// 检查引用计数(主要用于调试)
long n = sp.use_count();     // 当前 shared_ptr 数量
bool uniq = sp.unique();     // use_count == 1?(C++20 起废弃)
自定义删除器
cpp 复制代码
// 自定义删除器(与 unique_ptr 不同,删除器不改变 shared_ptr 类型)
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; });

// make_shared 不支持自定义删除器,有需要时用 new + 删除器
// 但 make_shared 结合默认删除器在所有其他场景都是首选
线程安全
复制代码
┌──────────────────────────────────────────────┐
│  shared_ptr 的线程安全性分两层:               │
├──────────────────────────────────────────────┤
│  1. 控制块/引用计数:线程安全(atomic 操作)    │
│     → 多个线程拷贝/销毁同一个 shared_ptr 安全   │
│  2. 管理的对象数据:不保证线程安全              │
│     → 多线程访问对象成员需要额外 mutex          │
│  3. 同一个 shared_ptr 对象:非线程安全          │
│     → 多线程同时赋值给同一个 shared_ptr 需同步  │
└──────────────────────────────────────────────┘
cpp 复制代码
// ✅ 安全:不同线程持有各自的 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();  // 对象数据仍需 mutex 保护
}
enable_shared_from_this

问题 :在成员函数中如何正确返回 thisshared_ptr

cpp 复制代码
// ❌ 错误的做法
struct Bad {
    std::shared_ptr<Bad> get_shared() {
        return std::shared_ptr<Bad>(this);  // 危险!创建新的控制块
    }
};
// 调用两次 get_shared() 会创建两个控制块 → double free!

// ✅ 正确的做法
struct Good : public std::enable_shared_from_this<Good> {
    std::shared_ptr<Good> get_shared() {
        return shared_from_this();  // 复用已有的控制块,引用计数 +1
    }
    static std::shared_ptr<Good> create() {
        return std::make_shared<Good>();
    }
};

auto obj = Good::create();     // shared_ptr<Good>
auto obj2 = obj->get_shared(); // 共享同一个控制块,use_count = 2

重要约束 :调用 shared_from_this() 前,对象必须已被 shared_ptr 管理。通常将构造函数设为 private + 提供静态工厂方法。

make_shared vs new 深入
特性 new + shared_ptr make_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?
  1. 打破循环引用shared_ptr 循环引用导致内存泄漏
  2. 缓存/观察者模式:知道对象可能已被销毁,需要先检查
  3. 安全的悬垂检查 :将 shared_ptr 赋值给 weak_ptr 后,即使原对象被释放也不会 crash
创建 weak_ptr
cpp 复制代码
auto sp = std::make_shared<int>(42);

// 只能从 shared_ptr 或另一个 weak_ptr 创建
std::weak_ptr<int> wp1(sp);           // 从 shared_ptr
std::weak_ptr<int> wp2(wp1);          // 从另一个 weak_ptr(拷贝)
std::weak_ptr<int> wp3 = sp;          // 隐式转换

// ⚠️ 不能直接从裸指针创建
// std::weak_ptr<int> wp(new int(42));  // ❌ 编译错误
核心方法
cpp 复制代码
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;

// 1. expired() --- 检查对象是否已释放
if (!wp.expired()) {
    // 对象仍然存活(非原子检查,存在 TOCTOU 问题)
}

// 2. lock() --- 尝试获取 shared_ptr(推荐方式)
if (auto locked = wp.lock()) {
    // locked 是 shared_ptr<int>,确保对象在作用域内不会释放
    *locked = 100;
    // use_count 临时 +1,离开 if 块后 -1
} else {
    // 对象已释放
}

// 3. use_count() --- 当前 shared_ptr 数量(主要用于调试)
long n = wp.use_count();   // 返回 1(sp 还在)

// 4. reset() --- 清空弱引用
wp.reset();                // wp 不再观察任何对象
打破循环引用(详细示例)
cpp 复制代码
#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;  // ← 关键:用 weak_ptr 而非 shared_ptr
    ~B() { std::cout << "B destroyed\n"; }
};

int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();

    a->bptr = b;   // A 持有 B 的 shared_ptr
    b->aptr = a;   // B 持有 A 的 weak_ptr(弱引用,不影响引用计数)

    // 离开作用域:
    // a 的 use_count = 1(仅 a 自己持有)→ 销毁 A
    // A 析构 → bptr 释放 → b 的 use_count = 1 → 销毁 B
    // ✅ 两个对象都正确释放!
}
复制代码
内存泄漏版(全用 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  ⇢ 正确释放
实用场景:观察者模式 + 自动清理
cpp 复制代码
class Subject {
    std::vector<std::weak_ptr<IObserver>> observers_;
public:
    void attach(std::shared_ptr<IObserver> obs) {
        observers_.push_back(obs);  // 存 weak_ptr
    }

    void notify(const Event& ev) {
        for (auto it = observers_.begin(); it != observers_.end(); ) {
            if (auto obs = it->lock()) {    // 尝试获取 shared_ptr
                obs->onEvent(ev);           // 观察者仍存活,通知
                ++it;
            } else {
                it = observers_.erase(it);  // 观察者已销毁,自动清理
            }
        }
    }
};
// 优点:观察者销毁后无需手动取消注册,Subject 自动跳过
实用场景:缓存
cpp 复制代码
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;
    }
    // 当外部不再使用某张图片时,缓存项自动失效(weak_ptr expired)
    // 不会因缓存持有 shared_ptr 而导致图片永远无法释放
};

四、三种智能指针选择指南

复制代码
                     是否需要共享所有权?
                      /            \
                    否              是
                    /                \
          ┌──────────┐        ┌───────────────┐
          │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) 临时对象、字面量 42std::string("hello")
将亡值 (xvalue) 资源即将被转移 std::move(x) 的返回值

std::move vs std::forward

特性 std::move std::forward
功能 无条件转为右值 有条件转发(保持原值类别)
本质 static_cast<T&&> 依赖引用折叠的智能转换
使用场景 资源所有权转移 模板中完美转发参数
参数推导 自动推导 必须显式指定模板参数

核心误区

std::move 不移动任何东西! 它只是一个无条件的类型转换,真正的移动发生在移动构造函数/移动赋值运算符中。

具名右值引用变量是左值! 任何有名字的变量都是左值,即使类型是右值引用。

cpp 复制代码
void foo(std::string&& s) {
    data = s;              // ❌ s 是左值,调拷贝
    data = std::move(s);   // ✅ 强制转右值,调移动
}

引用折叠规则

组合 结果
T& & T&
T& && T&
T&& & T&
T&& && T&&

规则:有一个是左值引用,结果就是左值引用;两个都是右值引用才是右值引用。

完美转发失败的情况

  • 大括号初始化列表 {}(无法推导类型)
  • 0NULL 作为空指针常量
  • 位域(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 当前位置及之后失效
cpp 复制代码
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

vectorresize() vs reserve()

特性 resize(n) reserve(n)
修改 size
新增元素 ✅(默认值填充)
用途 改变元素个数 预留空间减少扩容

vector vs list 深入对比

特性 vector list
底层结构 连续内存(动态数组) 双向链表(非连续节点)
随机访问 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

手写队列(数组实现 + 链表实现)

cpp 复制代码
// 数组实现(环形队列)
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

特性 volatile std::atomic
保证原子性
防止编译器重排 ❌(仅防寄存器缓存)
防止 CPU 乱序 ✅(生成内存屏障)
用途 硬件寄存器、信号处理 多线程同步

release-acquire 同步(必考)

cpp 复制代码
std::atomic<bool> ready{false};
int data = 0;

// 生产者
void producer() {
    data = 42;
    ready.store(true, std::memory_order_release);  // 之前的所有写入对 acquire 可见
}

// 消费者
void consumer() {
    while (!ready.load(std::memory_order_acquire))  // 与 release 配对
        ;
    assert(data == 42);  // 一定成功!
}

无锁自旋锁

cpp 复制代码
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)

cpp 复制代码
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))
            ;  // CAS 失败则重试
    }
};

compare_exchange_weak vs compare_exchange_strong

  • weak:可能伪失败(spurious failure),性能更好,必须放在循环中
  • strong:保证只有值不匹配时才失败,但某些平台有额外开销

x86 vs ARM 差异

平台 内存模型 acquire/release 开销
x86 强内存模型 几乎零开销(只约束编译器)
ARM/RISC-V 弱内存模型 生成 DMB/DSB 屏障指令,有真实开销

互斥锁(std::mutex)用法详解

cpp 复制代码
#include <mutex>

// 1. 裸 mutex(不推荐直接用)
std::mutex mtx;
void bad_usage() {
    mtx.lock();
    // ... 若中间抛异常,锁永远不会释放!
    mtx.unlock();
}

// 2. lock_guard:最简单 RAII 封装(不可手动解锁)
void good_with_guard() {
    std::lock_guard<std::mutex> lock(mtx);  // 构造时加锁,析构时解锁
    // 临界区代码...
}  // 出作用域自动解锁,异常安全

// 3. unique_lock:可延迟锁定、手动解锁、配合条件变量
void good_with_unique_lock() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 延迟加锁
    // ... 做一些无需锁的操作 ...
    lock.lock();       // 显式加锁
    // 临界区...
    lock.unlock();     // 提前解锁
    // ... 无需锁的操作 ...
    lock.lock();       // 再次加锁
}  // 析构时自动解锁

// 4. scoped_lock (C++17):同时锁多个 mutex,避免死锁
std::mutex mtx1, mtx2;
void transfer() {
    std::scoped_lock lock(mtx1, mtx2);  // 原子地锁定两个 mutex
    // 临界区...
}
互斥锁类型 特点
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)

为什么要用线程池? 线程创建/销毁有开销,频繁创建会降低性能。线程池预先创建一组工作线程,任务到来时分配给空闲线程执行,任务完成后线程不销毁而是等待新任务。

cpp 复制代码
#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();  // 执行任务(不在锁内)
                }
            });
        }
    }

    // 提交任务,返回 future 以获取结果
    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 局部静态变量线程安全)

cpp 复制代码
class Singleton {
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static Singleton instance;  // C++11保证线程安全,Meyers Singleton
        return instance;
    }

private:
    Singleton() = default;
};
饿汉 vs 懒汉
方式 初始化时机 线程安全 特点
饿汉 类加载时 ✅ 天生安全 空间换时间
懒汉 首次调用时 需同步保护 时间换空间,延迟加载

2. 工厂模式(Factory)

三种工厂对比:

模式 说明 优点 缺点
简单工厂 一个工厂类按参数创建产品 简单 新增产品需改工厂类
工厂方法 定义创建接口,子类决定实例化 符合开闭原则 每增产品需增工厂类
抽象工厂 创建一系列相关对象 保证产品族一致性 扩展产品族困难

现代 C++ 可注册工厂

cpp 复制代码
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

cpp 复制代码
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 类(必考🔥)

cpp 复制代码
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

cpp 复制代码
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)

cpp 复制代码
class Singleton {
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }

private:
    Singleton() = default;
};

4. 生产者-消费者模型

cpp 复制代码
#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)

cpp 复制代码
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)

cpp 复制代码
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. 内存池(简易版本)

cpp 复制代码
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 表达式在函数中的使用场景

cpp 复制代码
// 1. 作为回调/谓词直接传入 STL 算法
std::vector<int> v = {1, 2, 3, 4, 5};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// 2. 捕获局部变量作为回调(替代 std::bind)
int threshold = 10;
auto it = std::find_if(v.begin(), v.end(), [threshold](int x) {
    return x > threshold;  // 捕获外部变量 threshold
});

// 3. 作为异步任务的回调
auto future = std::async(std::launch::async, [&data]() {
    return processData(data);  // 引用捕获,直接操作外部数据
});

// 4. 在类成员函数中使用,捕获 this
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_; }  // 捕获 this 访问成员
            ),
            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::vectorstd::string 替代裸数组
  • 内存碎片:频繁分配释放小对象 → 内存池

可重入函数(Reentrant Function)

特性 可重入函数 不可重入函数
定义 被中断后再次调用仍正确 重入会导致数据错乱
静态/全局变量 不使用 使用
动态内存分配 不使用非原子的 malloc 可能使用
标准库函数 strcpymemcpy(只读输入) strtok(静态缓冲区)
典型场景 中断服务程序 (ISR)、信号处理函数 普通应用代码
cpp 复制代码
// ❌ 不可重入:使用了静态变量保存状态
char* strtok(char* str, const char* delim) {
    static char* saved;  // 静态变量保存进度,重入时会被覆盖
    // ...
}

// ✅ 可重入版本:状态由调用者传入
char* strtok_r(char* str, const char* delim, char** saveptr) {
    // 状态保存在调用者提供的指针中
}

十四、IO 多路复用

select / poll / epoll 对比

特性 select poll epoll
fd 数量限制 默认 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)
惊群问题 更容易出现 相对少
适用场景 简单可靠 高性能场景
cpp 复制代码
// epoll 基本流程
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) {  // ET 模式必须循环读完
                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 \n
ABI 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 中查看崩溃点变量状态

bash 复制代码
# 1. 打开 core dump 文件
gdb ./program core

# 2. 查看崩溃时的调用栈
bt
bt full        # 显示所有帧的局部变量

# 3. 切换到崩溃帧
frame 0        # 0 号帧通常是崩溃点

# 4. 查看所有局部变量
info locals

# 5. 查看函数参数
info args

# 6. 打印具体变量
print var_name
print *ptr          # 解引用指针
print vec.size()    # 可调用 STL 容器方法
print/x var         # 十六进制格式打印

# 7. 查看寄存器状态
info registers

# 8. 查看内存内容
x/16x $rsp          # 查看栈顶 16 个四字节
x/s str_ptr         # 以字符串形式查看内存

# 9. 多线程崩溃分析
info threads        # 列出所有线程
thread apply all bt # 查看所有线程的调用栈

前提 :需要有调试符号(编译时加 -g)且 core dump 大小限制未被关闭(ulimit -c unlimited)。

Git 常用操作

回退到之前的 commit
bash 复制代码
# 方式1:git reset --- 修改 HEAD 位置
git reset --soft HEAD~1     # 回退1个提交,改动保留在暂存区
git reset --mixed HEAD~1    # 回退1个提交,改动保留在工作区(默认)
git reset --hard HEAD~1     # 回退1个提交,改动完全丢弃(危险!)

git reset --hard <commit-hash>  # 回退到指定 commit

# 方式2:git revert --- 生成新 commit 撤销旧 commit(安全,适合已有 push 的分支)
git revert HEAD              # 创建一个新 commit 来撤销最近一次提交
git revert <commit-hash>     # 撤销指定 commit
操作 是否改写历史 适用场景
git reset 是(本地) 本地 commit 未 push
git revert 否(新增 commit) 已经 push 到远程
创建分支与合并
bash 复制代码
# 创建分支
git branch feature-xxx           # 创建分支(不切换)
git checkout -b feature-xxx      # 创建并切换到新分支
git switch -c feature-xxx        # 同上,Git 2.23+ 推荐

# 推送分支到远程
git push -u origin feature-xxx

# 合并分支
git checkout main                 # 先切到目标分支
git merge feature-xxx             # 将 feature-xxx 合并到当前分支

# 解决冲突后
git add <冲突文件>
git commit                       # 完成合并

# 变基合并(保持线性历史)
git checkout feature-xxx
git rebase main                   # 将 feature-xxx 的提交放到 main 最新提交之后
git checkout main
git merge feature-xxx             # 此时是 fast-forward,无额外 merge commit
Git 分支管理策略
复制代码
main / master        ← 生产就绪代码,只通过 PR 合并
  └── develop        ← 开发主线
       ├── feat/xxx  ← 功能分支(短生命周期,合入后删除)
       ├── fix/xxx   ← Bug 修复分支
       └── release/x ← 发布分支

常见分支数量:日常同时活跃 2-5 个分支(1 个开发主线 + 若干功能/修复分支)。遵循"分支宜短命"原则------功能完成后尽快合入主线并删除旧分支。

相关推荐
小为资料库2 小时前
2026上教资面试历年真题汇总及结构化题库PDF电子版(含小学、初中和高中各科全)
面试·职场和发展·pdf
code monkey.2 小时前
【Linux之旅】Linux 线程同步与互斥实战:从锁机制到生产消费模型全指南
linux·c++·线程·同步·互斥
我能坚持多久2 小时前
STL详解——list的模拟实现
c++·windows·list
雪度娃娃2 小时前
行为型设计模式——命令模式
c++·设计模式·命令模式
我能坚持多久2 小时前
STL详解——list的介绍以及功能展示
开发语言·c++
大大杰哥2 小时前
2026陕西省ICPC省赛补题(前六题)
c++·算法
Brilliantwxx2 小时前
【C++】 继承与多态(上)
开发语言·c++·笔记·算法
不负岁月无痕2 小时前
STL -- C++ string 类 模拟实现
java·开发语言·c++
·心猿意码·2 小时前
OCCT源码解析(六):TKG3d 模块——三维曲面体系
c++·3d