跳转到内容

01 · 自我诊断测试(73 题找盲区)

用途:开始学习前先做一遍,摸清你的真实底子,按结果分配时间。 做法:每题诚实自评 ✅ 会 / 🟡 半会不会 / ❌ 不会。不要翻文档,凭第一反应。 完成后数一下各状态数量,看文末“结果解读”,按优先级学习。 答案速查在文档后半部分(每行一句话,作为回忆检查点)。


Part A:编译链接与内存(对应 02 章)

Section titled “Part A:编译链接与内存(对应 02 章)”
  1. 从 .cpp 到可执行文件要经过哪几个阶段?每个阶段做什么、产出什么?
  2. 静态链接和动态链接的区别?静态库和动态库各自的优缺点?静态是生成文件时装载,动态是运行时装载。优缺点不太懂。
  3. 全局变量、static 全局变量、static 局部变量、局部变量、字符串常量,分别存在哪?生命周期如何?
  4. 栈和堆的区别(分配方式、速度、容量、生命周期)?分配方式不会,栈的速度快,堆的容量大,栈的生命周期随着代码块结束,堆需要主动释放。
  5. 一个 struct 含 char a; int b; double c;,sizeof 是多少?为什么?内存对齐的规则是什么?
  6. 什么是大小端?怎么用代码判断当前机器是大端还是小端?
  7. #include 头文件重复包含怎么解决?#pragma once#ifndef 的区别?使用#pragma once 和#ifndef ,区别是前者是对文件,后者是对代码块
  8. extern "C" 是干什么的?为什么 C++ 和 C 混合编程要用它?

Part B:指针 / 引用 / 内存管理(对应 03 章)

Section titled “Part B:指针 / 引用 / 内存管理(对应 03 章)”
  1. 指针和引用的区别(至少说 5 点)?
  2. newmalloc 的区别?deletefree 呢?为什么 C++ 用 new 不用 malloc?new会调用构造函数,delete会调用析构函数,C++有类对象
  3. 什么是内存泄漏?怎么检测?怎么避免?在堆上开辟的内存未释放掉,然后指针也丢失了。不知道如何检测,使用智能指针来避免。
  4. unique_ptr / shared_ptr / weak_ptr 各自原理?shared_ptr 的引用计数存在哪?
  5. 什么是循环引用?怎么用 weak_ptr 解决?具体场景?
  6. make_sharedshared_ptr(new T) 有什么区别(性能/异常安全)?
  7. 野指针、悬垂指针、空指针的区别?怎么避免?
  8. const int* pint* const pconst int* const p 分别表示什么?常量指针(指向常量的指针)、指针常量(指针的值是常量),指向常量的常量指针
  9. 什么是内存池?为什么游戏开发里常用?
  1. 构造函数的初始化顺序是什么(基类/成员/自身)?为什么析构顺序相反?
  2. 拷贝构造函数什么时候被调用?赋值运算符什么时候被调用?怎么区分?
  3. 深拷贝和浅拷贝的区别?默认拷贝构造函数是浅拷贝吗?深拷贝是将实例中在堆上开辟的内容也进行了复制,浅拷贝只复制指针(也就是指向同一块内容);默认的应该是浅拷贝
  4. 移动构造和移动赋值什么时候触发?std::move 之后原对象为什么不能用了?
  5. 初始化列表和构造函数体内赋值有什么区别?为什么 const 成员必须用初始化列表?
  6. 空类 sizeof(A) 是多少?为什么?加一个虚函数后呢?
  7. explicit 的作用?什么时候必须加?
  8. delete(函数删除)和 default(默认实现)关键字是干嘛的?

Part D:继承 / 多态 / 虚函数(对应 05 章)

Section titled “Part D:继承 / 多态 / 虚函数(对应 05 章)”
  1. 多态的实现原理?虚函数表存在哪?虚函数指针在对象内存的什么位置?
  2. 静态绑定和动态绑定的区别?什么情况下多态不生效(构造函数/析构函数里调用虚函数?)?
  3. 构造函数里调用虚函数会发生什么?为什么?
  4. 菱形继承是什么?有什么问题?虚继承怎么解决?虚继承的存储结构?
  5. 抽象类、纯虚函数?为什么不能实例化?
  6. 基类析构函数为什么必须声明为虚函数?不声明会怎样?
  7. overridefinal 的作用?
  8. 虚函数、纯虚函数、普通函数的区别?虚函数能是内联的吗?
  1. vector 的底层实现?扩容机制(增长因子)?push_back 的摊还复杂度?底层是一个capacity长的数组,1.4倍进行扩容
  2. vector 的迭代器什么时候失效?insert/erase/push_back 分别怎么影响迭代器?
  3. map 和 unordered_map 的底层实现?各自优缺点?什么场景选哪个?
  4. map 为什么用红黑树而不是 AVL 树?
  5. unordered_map 的哈希冲突怎么解决?什么是负载因子、rehash?
  6. push_back 和 emplace_back 的区别?push_back有拷贝,emplack_back则是直接构造
  7. reserve 和 resize 的区别?reserve是改变capacity,resize是改变size
  8. list 和 vector 的区别?各自适用场景?list 有随机访问吗?
  9. deque 的底层结构?为什么叫“分段连续”?环形链表,我不知道啥叫分段连续
  10. set/map 的 key 为什么要求能比较?迭代器是只读的吗?

Part F:模板与泛型(对应 07 章)

Section titled “Part F:模板与泛型(对应 07 章)”
  1. 函数模板和类模板?模板是什么时候实例化的(编译期)?
  2. 模板特化和偏特化的区别?函数模板能做偏特化吗?为什么?
  3. SFINAE 是什么?std::enable_if 怎么用?
  4. 可变参数模板?sizeof...(Args) 和折叠表达式?
  5. 非类型模板参数是什么?template<int N>
  1. 左值、右值、纯右值、将亡值的区别?为什么要有右值引用?
  2. 移动语义解决了什么问题?std::move 本质是什么?
  3. 完美转发是什么?什么是引用折叠?decltype(auto)
  4. lambda 表达式的捕获方式(值/引用/初始化捕获)?底层实现(仿函数)?
  5. autodecltype 的区别?
  6. constexprconst 的区别?
  7. C++14/17/20 各自新增了什么?至少各说 3 个。
  8. 三向比较运算符 <=> 是什么?协程是什么?

Part H:并发多线程(对应 09 章)

Section titled “Part H:并发多线程(对应 09 章)”
  1. 进程和线程的区别?线程间怎么同步?
  2. 死锁的四个必要条件?怎么避免死锁?
  3. lock_guardunique_lock 的区别?std::scoped_lock
  4. condition_variable 为什么必须配 unique_lock?什么是虚假唤醒?
  5. atomicmutex 的区别?什么是内存序(memory order)?
  6. 单例模式的线程安全问题?双重检查锁(DCLP)有什么坑?C++11 之后怎么写线程安全单例?
  7. 什么是线程池?游戏引擎里的 job system 是干嘛的?

Part I:设计模式 / 性能 / 游戏专项(对应 10~13 章)

Section titled “Part I:设计模式 / 性能 / 游戏专项(对应 10~13 章)”
  1. 单例模式的优缺点?为什么游戏里经常被诟病?
  2. 观察者模式、状态模式、命令模式在游戏里分别用在什么场景?
  3. 虚函数有性能开销吗?开销在哪?
  4. 什么是缓存友好(cache friendly)?举例怎么做到?
  5. 游戏主循环(game loop)一般长什么样?固定步长和可变步长怎么选?
  6. ECS(Entity-Component-System)是什么?比传统继承体系好在哪?
  7. 四元数解决什么问题?为什么不用欧拉角?
  8. 碰撞检测常用哪些空间划分结构(四叉树/八叉树/BVH)?
  9. 什么是 DrawCall?怎么减少?
  10. UE 的 UObject 是干嘛的?反射是什么?GC 怎么做?Unity 的 IL2CPP 和 Mono 区别?

你的情况 应对策略
❌ ≥ 20 题 底子偏薄,按路线图第 1-2 周进度放慢,每章读两遍,第二周周末找我抽查
❌ 10~20 题 / 🟡 很多 正常水平,按路线图正常推进,优先消灭 ❌ 的章节
❌ < 5 题,🟡 < 15 题 基础扎实,可以直接进入“第 4 周”的现代 C++/并发章节,其他章节快速过 Q&A
大部分 ✅ 你很可能是来复习的,重点转向第 7-8 周(游戏专项 + 手撕 + 冲刺)

盲区记录表(把 ❌ 的题号写这里,学完对应章节后回来划掉):

Part A 不会的题号:1、3、5、6、8(2、4 半会不会,7 需补优缺点)
Part B 不会的题号:9、12、13、14、15、17(11 检测不会,16 已会)
Part C 不会的题号:18、19、21、22、23、24、25(20 已会)
Part D 不会的题号:26、27、28、29、30、31、32、33
Part E 不会的题号:35、36、37、38、41、43(34 扩容因子记错,42 误解为环形链表)
Part F 不会的题号:44、45、46、47、48
Part G 不会的题号:49、50、51、52、53、54、55、56
Part H 不会的题号:57、58、59、60、61、62、63
Part I 不会的题号:64、65、66、67、68、69、70、71、72、73

记录于 2026-08-13 首次自测:73 题中已答 11 题(4 题满分、2 题基本对、4 题半对、1 题错误),空白 62 题。


答案速查(一句话回忆版,细节看对应章节)

Section titled “答案速查(一句话回忆版,细节看对应章节)”
  1. 预处理(展开头文件/宏)→ 编译(词法/语法/语义,生成汇编)→ 汇编(.o 目标文件)→ 链接(符号解析+重定位)。

  2. 静态链接在编译期把库代码拷进可执行文件,体积大、独立部署;动态链接运行时加载 .so/.dll,体积小、可共享、更新方便,但有“依赖缺失”风险。

  3. 全局/static 全局 → 全局区(数据段);static 局部 → 全局区、首次执行到声明时初始化、作用域仅函数内;局部 → 栈;字符串常量 → 只读常量区;new 出来的 → 堆。

  4. 栈自动分配自动释放、快、容量小(默认约 1~8MB)、连续;堆手动分配、慢、容量大、碎片化。

  5. char(1)+int(4)+double(8),对齐后 sizeof = 16(int 对齐到 4,double 对齐到 8,总大小为最大对齐数的整数倍)。规则:成员按自身对齐数(通常等于大小)对齐,结构体按最大对齐数对齐。

  6. 大端 = 高字节存低地址;小端 = 低字节存低地址。用 union { int i; char c[4]; } 看首字节,或用指针强转。

  7. #ifndef XXX / #define XXX / #endif#pragma once;后者更快且由编译器保证,前者跨平台。

  8. 告诉编译器“这段代码按 C 的符号规则链接”(C++ 有名字修饰 name mangling,C 没有)。

  9. 引用是别名、必须初始化、不能改指向、占用空间理论上为 0(实现是常量指针)、没有 NULL 引用;指针可以改、可以为空、可以多级。

  10. new 调用构造、返回类型化指针、重载可控、失败抛异常;malloc 只分配内存、返回 void*、失败返回 NULL。delete 调用析构。

  11. 泄漏 = 堆上内存没有释放且失去指针。检测:Valgrind / ASAN / CRT 泄漏报告;避免:RAII + 智能指针。

  12. unique_ptr 独占(禁止拷贝、支持移动);shared_ptr 引用计数(计数在堆上的控制块里);weak_ptr 弱引用(不增加强引用计数,只增加弱计数,lock() 提升)。

  13. 两个 shared_ptr 互相持有时计数永远不为 0 → 泄漏;把其中一方改为 weak_ptr。

  14. make_shared 一次分配(对象+控制块),异常安全,推荐;shared_ptr(new T) 可能两次分配。

  15. 野指针 = 未初始化指针;悬垂 = 指向已释放内存;空指针 = nullptr。避免:初始化置空、释放后置空、智能指针。

  16. const int*:指向的 const;int* const:指针本身 const;const int* const:都 const。从右往左读。

  17. 预先分配一大块连续内存、按块分配回收,避免频繁 new/delete 的系统调用和碎片化,游戏高频创建销毁对象时用。

  18. 基类 → 成员(按声明顺序)→ 自身构造体;析构完全相反(自身 → 成员 → 基类)。

  19. 以值传参、以值返回、用已存在对象拷贝构造新对象时 → 拷贝构造;对已存在的对象赋值 → 赋值运算符。

  20. 浅拷贝只拷指针;深拷贝连内容一起拷。默认的是浅拷贝,含指针成员时必须自己实现深拷贝。

  21. 右值(临时对象、move 结果)作为参数时触发移动构造;移动后源对象处于“有效但未指定”状态(典型实现会把资源指针置空,但标准不保证),不能依赖其内容。

  22. 初始化列表直接初始化成员(一次构造);体内赋值是先默认构造再赋值(多一次)。const 成员、引用成员、没有默认构造的成员必须在初始化列表。

  23. 空类 sizeof = 1(为了让不同对象地址不同);加一个虚函数后 = 8/16(含一个 vptr)。

  24. 禁止隐式类型转换(禁止构造函数把其他类型隐式转成该类);单个参数构造函数建议加。

  25. =delete 禁止某个函数(如禁止拷贝);=default 让编译器生成默认版本(如同时自定义了别的构造时保住默认构造)。

  26. 虚表(vtable)存虚函数地址,每个含虚函数的类一张;对象内有一个 vptr 指向虚表;运行时根据 vptr 找到真实函数。

  27. 静态绑定 = 编译期决定(非虚函数、值传递、普通指针);动态绑定 = 运行时决定(虚函数 + 指针/引用)。构造/析构里调用虚函数不发生多态(走当前类版本)。

  28. 基类构造时虚表还是基类的(子类还没构造完),调用的是基类版本——所以构造/析构里别依赖多态。

  29. 菱形 = 一个派生类继承两个都继承同一基类的类;问题:二义性 + 基类子对象重复。虚继承让共享同一个基类子对象,虚基类表/偏移量定位。

  30. 含纯虚函数 = 0 的类是抽象类,不能实例化;派生类实现全部纯虚函数后才能实例化。

  31. 基类析构必须虚(多态基类):否则通过基类指针 delete 派生类对象是未定义行为,常见表现是只调基类析构、派生类资源泄漏。

  32. override 明确这是重写(写错签名编译报错);final 禁止继续重写/继承。

  33. 虚函数可内联:仅在静态绑定(非指针调用)时才能内联;多态调用必须查虚表,无法内联。

  34. 连续动态数组(三指针:begin/end/capacity);扩容 ×1.5 或 ×2,拷贝/移动旧元素,摊还 O(1)。

  35. push_back 扩容后所有迭代器失效(不扩容只有 end() 失效);insert/erase 至少使插入点/被删元素及其后迭代器失效,扩容则全部失效。

  36. map = 红黑树(有序、O(logn)、内存不连续、迭代器稳定);unordered_map = 哈希表(平均 O(1)、无序、需要哈希函数与相等比较)。

  37. 红黑树平衡要求宽松(最长路径 ≤ 2 倍最短),插入删除旋转次数少、无最坏退化,统计性能好。

  38. 链地址法(桶挂链表);负载因子 = 元素数/桶数,超过阈值(如 1.0)触发 rehash(扩容重散列)。

  39. emplace_back 直接原地构造(转发参数),省一次临时对象拷贝/移动;push_back 先构造临时再拷贝/移动。

  40. reserve 只扩容不改变 size(元素数);resize 改变 size(多出来的默认构造)。

  41. list = 双向链表,插入删除 O(1)、除被删除元素本身外迭代器不失效,但没有随机访问(O(n) 找);vector 相反。

  42. deque 是分段连续:多段定长连续缓冲区 + 中控器 map(指针数组),头尾插入 O(1),可下标访问;注意头尾插入会使迭代器失效(引用不失效)。

  43. map/set 需要 key 有 <(或自定义比较);set 的迭代器是只读的,map 的迭代器指向 pair<const Key, T>:key 不可改,value 可改。

  44. 编译期实例化:用到哪种类型就生成一份代码;模板本身不生成代码,直到被实例化。

  45. 特化 = 指定具体类型;偏特化 = 部分参数指定/限定类型(如指针特化)。函数模板不能偏特化(只能全特化+重载代替)。

  46. SFINAE = “替换失败不是错误”:函数模板重载决议时,若签名处替换非法就放弃该版本找别的。enable_if 在编译期按条件开关重载。

  47. 模板参数个数不定,... 包;用 sizeof... 数个数,用递归展开或折叠表达式 (args + ...) 求和。

  48. template<int N> 用编译期常量做模板参数,如 std::array<T, N>、模板递归求阶乘。

  49. 左值有地址可取地址;右值是临时值(纯右值);将亡值(xvalue)= 可以“被偷”的右值(如 std::move 的结果)。右值引用让临时对象可被移动而非拷贝。

  50. 避免不必要的深拷贝(拷贝临时对象 → 移动,只“搬指针”)。std::move 本质只是 static_cast<T&&>,不移动任何东西。

  51. 完美转发 = 保持参数的左/右值性原样转发;引用折叠规则 T& + && → &T&& + && → &&decltype(auto) 保留引用性/const。

  52. 值捕获拷贝一份;引用捕获保持引用;初始化捕获 C++14 支持。底层是一个重载了 operator() 的匿名类(仿函数)。

  53. auto 靠初始化表达式推导(可能丢引用/const);decltype 保留类型全貌(含引用/const),用于声明依赖表达式类型的变量。

  54. const 是运行时常量(编译期不一定知道);constexpr 是编译期求值(可用于数组大小、模板参数)。

  55. C++14:泛型 lambda、初始化捕获、constexpr 放宽、返回类型推导;C++17:结构化绑定、if constexpr、折叠表达式、inline 变量、string_view、optional;C++20:concepts、协程、ranges、三向比较、format。

  56. <=> 一次得到全序/偏序关系(返回 strong_ordering 等,defaulted 时自动生成 == != < <= > >=);协程 = 可挂起/恢复的函数(co_await/co_yield/co_return),游戏异步任务常用。

  57. 进程 = 独立资源容器,线程 = 进程内共享资源的最小执行单元。同步手段:mutex/条件变量/原子/信号量。

  58. 互斥、占有且等待、不可剥夺、循环等待。避免:一次性拿所有锁、按固定顺序加锁、用 try_lock/超时、避免嵌套锁。

  59. lock_guard 构造加锁析构解锁,不可手动控制;unique_lock 可手动 lock/unlock、可移动、可配条件变量;scoped_lock 可同时锁多个。

  60. wait 需要 atomic 地释放锁并休眠,所以必须 unique_lock;虚假唤醒 = 被唤醒但条件不满足,所以必须 while 循环检查谓词。

  61. atomic 提供原子操作(整型/指针在主流平台通常无锁,标准不保证;用 is_lock_free 判断),只能保护单个变量;mutex 保护临界区(任意代码块)。内存序控制 CPU/编译器重排(relaxed/acquire/release/seq_cst)。

  62. 双检查锁在 C++11 前有内存序问题;C++11 后直接用 static 局部变量(magic static,编译器保证线程安全初始化)。

  63. 线程池 = 复用一组线程处理任务队列,避免频繁创建线程开销;job system = 游戏引擎把任务拆成 job 并行执行的框架(UE 的 TaskGraph)。

  64. 全局状态、难测试、隐藏依赖、生命周期难控;游戏里多用“注入式”或每系统一实例替代。

  65. 观察者 = 事件系统/UI 监听;状态 = 角色状态机(待机/攻击/死亡);命令 = 玩家输入 → 指令队列(可撤销/回放)。

  66. 有:查虚表(额外一次间接跳转)+ 无法内联 + 分支预测差。热路径(每帧每物体调用)才明显。

  67. 让 CPU 缓存命中率高:连续内存遍历(vector > list)、结构体数组(SoA)、按访问顺序布局、避免随机跳转。

  68. 循环 = 处理输入 → 更新(Update) → 渲染(Render)。固定步长(物理稳定,确定性)用于物理/逻辑;可变步长简单但帧率波动影响表现;常用混合:渲染可变,逻辑/物理固定。

  69. ECS = 实体(id)+ 组件(纯数据)+ 系统(处理逻辑)。优点:数据连续 → 缓存友好、灵活组合、易并行、无深继承。

  70. 四元数无万向节死锁、插值平滑(slerp)、紧凑;欧拉角有万向锁 + 插值不平滑。

  71. 四叉树(2D)、八叉树(3D)、BVH(动态物体)、网格/空间哈希(均匀分布)。

  72. DrawCall = 一次绘制命令(GPU 状态切换 + 提交顶点)。减少:合批(同材质合并)、纹理图集、实例化、减少状态切换、静态/动态批处理。

  73. UObject = UE 对象基类(反射、GC、序列化、编辑器集成);反射 = 运行时能查询类的成员/方法(UCLASS/UPROPERTY 宏生成元数据);GC = 可达性分析自动回收;Unity IL2CPP = C# 转 C++ 再编译(性能好、安全、打包大),Mono = 直接 JIT(热更/迭代快)。


做完后把你 ❌ 和 🟡 的题号 发给我,我会:

  1. 帮你确认哪些是最高优先级;
  2. 按你的盲区给你定制第一周的学习顺序;
  3. 之后每周让你重测一次,看盲区消灭情况。