附录 A · 术语表
本表收录游戏开发/引擎方向 C++ 实习面试的高频术语,按主题分组,共 10 组 140 条。 「面试要点」列写的是最容易被追问的那个点或易错点,不是“很重要”式的空话。 术语覆盖范围与正文各章一致(02~13 章),数值、结论、易错点均以正文为准。
一、编译与链接
Section titled “一、编译与链接”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 链接 | Linking | 符号解析(把引用找到定义)+ 重定位(填真实地址) | undefined reference 发生在这一步,不在编译期 |
| 翻译单元 | Translation Unit | 一个 .cpp 加上它展开后的所有头文件,是编译的基本单位 | ODR 按翻译单元判定,所以头文件里放定义很容易踩坑 |
| 单一定义规则 | ODR | 程序里每个实体只能有一份定义,声明可以有很多份 | 例外只有两个:inline 函数/变量与模板,其余放头文件必报重复定义 |
| 符号表 | symbol table | 目标文件里“函数/变量名 → 地址”的映射 | 链接靠符号名匹配,符号名不一致就会找不到定义 |
| 重定位 | relocation | 把符号引用填成运行期真实地址 | 动态库加载时也要做重定位,这是它启动稍慢的原因 |
| 名字修饰 | name mangling | C++ 为支持重载给函数名改名,foo(int) 变成 _Z3fooi |
C 库符号是原名,所以 C++ 直接链 C 库会 undefined reference |
| C 语言链接 | extern “C” | 关闭名字修饰,让 C++ 能链上 C 编译出的符号 | 只影响链接不影响函数行为;这个块里不能重载 |
| 静态库 | static library (.a / .lib) | 链接期把用到的库代码拷进可执行文件 | 体积大、部署独立、启动快;改库要重新编译整个可执行文件 |
| 动态库 | dynamic library (.so / .dll) | 链接时只记录依赖,运行时或加载时才加载 | 可共享、可热更新,但缺文件就运行时报错 |
| 位置无关代码 | PIC | 动态库用相对寻址 + GOT 让代码与加载地址无关 | 做动态库要加 -fPIC,否则可能无法被多进程共享映射 |
二、内存布局与内存管理
Section titled “二、内存布局与内存管理”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 进程内存布局 | process memory layout | 高地址到低地址:栈→空闲区→堆→BSS→.data→.rodata→.text | 栈向下增长、堆向上增长,中间留空闲区避免相撞 |
| 代码段 | .text | 存放程序指令,只读 | 问“函数指令放哪”答 .text,别答 .rodata |
| 只读数据段 | .rodata | 字符串常量、const 全局常量、虚函数表 | 改写 .rodata 会段错误崩掉 |
| 已初始化数据段 | .data | 已初始化的全局变量与 static 变量 | int b = 5;(文件作用域)在 .data,static int c; 在 .bss |
| 未初始化数据段 | .bss | 未初始化的全局变量与 static 变量,运行时由系统清零 | 不占可执行文件体积,只记录大小 |
| 栈 | stack | 存局部变量与函数调用帧,编译期自动分配释放,向下增长 | 容量约 1~8MB,递归太深或超大局部数组会栈溢出 |
| 堆 | heap | new/malloc 出来的内存,向上增长,手动管理 |
越界破坏堆管理结构,可能延迟崩溃而不是立刻崩 |
| 默认初始化规则 | default initialization | 全局/static 默认为 0;栈和堆默认不初始化 | 易错:new int 是垃圾值,new int() 才是 0 |
| 栈帧 | stack frame | 一次函数调用在栈上占用的那段空间,含局部变量与返回地址 | 返回局部变量地址会悬垂,因为栈帧已被回收 |
| sizeof | sizeof | 类型/对象占多少字节,含尾部 padding | 空类 sizeof 是 1;含虚函数是 8(vptr) |
| alignof | alignof | 类型的对齐数,即起始地址必须是它的倍数 | sizeof 答“占地多大”,alignof 答“住址要求” |
| 内存对齐 | alignment | 成员起始偏移是对齐数的整数倍,CPU 才能一次读完 | 对齐是为了避免跨字访问,不是为省内存 |
| 填充与对齐规则 | padding / alignment rule | 成员偏移取整到自身对齐;结构体对齐取成员最大对齐;总大小再取整 | {char,int,double} 是 16,{char,double,int} 是 24,大的放前面省内存 |
| pragma pack | #pragma pack(n) |
强制按 min(n, alignof(成员)) 对齐,压掉 padding |
用于网络报文、文件格式、硬件寄存器映射 |
| 大小端 | endianness | 大端高位字节存低地址;小端低位字节存低地址 | 小端机 0x12345678 内存里依次是 78 56 34 12 |
| RAII | Resource Acquisition Is Initialization | 构造获取资源、析构释放资源,靠栈对象保证一定被调用 | 智能指针/lock_guard/fstream 都是;这是异常安全的根基 |
| 浅拷贝 | shallow copy | 默认拷贝只逐成员复制指针值,两个对象共享同一块内存 | 裸资源类浅拷贝 → 两次析构同一内存 → double free |
| 深拷贝 | deep copy | 为新对象重新分配资源并复制内容 | 拷贝赋值要先分配新内存成功后再释放旧资源,并做自赋值检查 |
| 悬垂指针 | dangling pointer | 曾经有效,但指向的对象已被释放 | 来源:delete 后没置空、返回局部变量或按值形参的地址 |
| 内存池 | memory pool | 预分配一大块切成固定大小块,用自由链表管理 | 分配/释放都是 O(1) 指针操作、无碎片,但只管内存不调构造 |
| 对象池 | object pool | 预创建一批对象,借出/归还复用 | 归还时重置状态不析构;地址固定,可做到连续内存 |
| 独占所有权 | unique_ptr | 独占资源,禁拷贝允许移动,默认删除器下与裸指针同大小 | 能放进 vector,因为 vector 只要求可移动不要求可拷贝 |
| 共享所有权 | shared_ptr | 引用计数共享对象,最后一个销毁时释放 | 计数在堆上的控制块里;计数原子安全,所指对象不安全 |
| 控制块 | control block | shared_ptr 在堆上的那块内存:强计数、弱计数、删除器、分配器 | 循环引用就是两块控制块的强计数都为 1,谁都放不掉 |
| 弱引用 | weak_ptr | 只加弱计数不加强计数,用 lock() 提升为 shared_ptr |
lock() 会临时把强计数 +1,保证提升期间对象不被销毁 |
| 循环引用 | cyclic reference | 两个 shared_ptr 互相持强引用,计数永不归零 → 泄漏 | 解法是把一个方向改 weak_ptr;谁不依赖对方活着谁就 weak |
| make_shared | make_shared | 对象与控制块一次分配完成,缓存友好且异常安全 | 对象大且还有 weak_ptr 观察时,改用 shared_ptr(new T) 让对象内存尽早释放 |
三、对象模型:类、继承与多态
Section titled “三、对象模型:类、继承与多态”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 特殊成员函数 | special member functions | 默认构造、析构、拷贝构造、拷贝赋值、移动构造、移动赋值 | 六个的调用时机要能逐个说清什么时候被调用 |
| Rule of 0/3/5 | Rule of Zero/Three/Five | 管裸资源就得成套写;用标准库类型则一个都不用写 | 只写析构不写拷贝构造,编译器仍生成浅拷贝 → double free |
| 拷贝构造 | copy constructor | 用已有对象创建一个新对象 | 口诀:有新对象 = 拷贝构造,老对象赋值 = 拷贝赋值 |
| 拷贝赋值 | copy assignment | 给已存在对象赋新值 | 必须处理自赋值;返回 T& 而不是 T,支持链式 a=b=c |
| 移动构造 | move constructor | 把源对象的资源指针搬过来并置空源对象 | 必须尽量 noexcept,否则 vector 扩容会退化为拷贝 |
| 初始化列表 | member initializer list | 在构造函数体之前直接构造成员,只调一次构造 | 初始化顺序按声明顺序,与列表书写顺序无关 |
| 有效但未指定 | valid but unspecified | 被 move 之后对象的状态:能析构能再赋值,内容不可依赖 | 别和悬垂混:悬垂是访问已释放内存(UB),这个是合法的 |
| 析构函数 | destructor | 无参无返回值、不可重载,对象销毁时自动调用 | 析构里抛异常可能 double throw → std::terminate |
| 空基类优化 | EBO | 空类作基类时那 1 字节被塞进填充字节,零额外空间 | 作成员不能优化;两个同类型空基类不能共用同一地址 |
| 虚函数表 | vtable | 每个含虚函数的类一张表,存该类所有虚函数地址 | 存在只读数据段 .rodata,每类一份、所有对象共享 |
| 虚指针 | vptr | 对象里指向所属类虚表的指针 | 单继承下在对象偏移 0,占 8 字节(64 位) |
| 纯虚函数 | pure virtual function (= 0) |
强制派生类实现的虚函数声明 | 纯虚 ≠ 没有实现:基类可给实现,用 Base::f() 显式调用 |
| 抽象类 | abstract class | 含至少一个纯虚函数的类,不能实例化 | 派生类没实现全部纯虚函数 → 它仍是抽象类 |
| 虚析构 | virtual destructor | 多态基类的析构函数必须 virtual | 否则通过基类指针 delete 是 UB,常见表现是派生类资源泄漏 |
| 对象切片 | object slicing | 按值传递派生对象时按基类拷贝构造,派生部分被切掉 | 多态只对指针/引用生效,按值传参多态一定失效 |
| 运行时类型识别 | RTTI | 运行期查对象真实类型的能力,只对多态类型可用 | type_info 挂在虚表前置槽,没有 vptr 就没地方存类型信息 |
| dynamic_cast | dynamic_cast | 运行期安全的下行转换,失败返回 nullptr 或抛异常 | 是运行期库调用、无法内联,热路径尽量别用 |
| 虚继承与菱形继承 | virtual inheritance / diamond | B、C 都继承 A、D 继承 B 和 C → A 子对象两份且访问二义;虚继承只留一份 | 代价是访问虚基类成员要查偏移表多一次间接;虚基类由最派生类构造 |
四、模板与泛型
Section titled “四、模板与泛型”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 模板定义可见性 | definition must be visible | 模板定义必须写在头文件里 | 定义放 .cpp,别的翻译单元看不见 → 链接报 undefined reference |
| 偏特化 | partial specialization | 限定一类类型(如 T*、const T)给出实现 |
函数模板不能偏特化,只能用重载 + 全特化替代 |
| SFINAE | Substitution Failure Is Not An Error | 替换失败不是错误:签名处替换失败就跳过该候选,不报错 | 只适用于直接上下文(签名/模板形参),函数体里的错误不算 |
| enable_if | std::enable_if | 条件成立才有 type 成员,从而让某个重载“存在” |
用于删掉整个重载;C++17 后多数场景可换 if constexpr |
| if constexpr | if constexpr (C++17) | 编译期分支,未选中的依赖分支不实例化 | 普通 if 两个分支都会编译;非依赖代码仍会被完整检查 |
| 类型萃取 | type_traits | 编译期类型判断与变换的库,如 is_integral、remove_reference |
C++17 起用 _v 拿值、_t 拿类型 |
| 可变参数模板 | variadic template | typename... Args 参数包,配 sizeof...(Args) 取个数 |
展开两种:递归剥参数(C++11)或折叠表达式(C++17) |
| 折叠表达式 | fold expression (C++17) | 用同一运算符把参数包折叠成一个表达式,如 (args + ...) |
空参数包需要初始值,如 (args + ... + 0) 才返回 0 |
| CRTP | Curiously Recurring Template Pattern | 派生类把自己作模板参数传给基类,基类 static_cast 静态分发 |
零虚表开销且可内联;ECS 组件、数学库常用 |
| 编译期多态 | static polymorphism | 用模板/重载在编译期决定调用哪个实现 | 可内联、零抽象开销,代价是代码膨胀、类型必须编译期确定 |
| 模板与虚函数 | template vs virtual | 模板函数不能是虚函数 | 原因是虚表要运行期确定函数集合,与编译期实例化冲突 |
五、现代 C++(C++11 及以后)
Section titled “五、现代 C++(C++11 及以后)”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 值类别 | value category | 左值(有名字可取地址)、纯右值(临时无名)、将亡值(可被偷资源) | 陷阱:int&& r 里的 r 本身是左值,右值引用是类型不是值类别 |
| 右值引用 | rvalue reference (T&&) |
只能绑右值的引用类型,是移动语义的基础 | 没有右值引用就没有 unique_ptr 的所有权转移 |
| 移动语义 | move semantics | 把源对象的资源“偷”过来并置空源对象,成本 O(1) 于资源指针 | 移动构造/移动赋值应尽量 noexcept,否则容器扩容会退化为拷贝 |
| std::move | std::move | 不移动任何东西,只是一个到右值引用的类型转换 | 真正搬资源的是移动构造函数,move 本身零操作 |
| 完美转发 | perfect forwarding | 保持参数在函数链中传递时的左/右值性不变 | 写法是转发引用 T&& + std::forward<T>(arg),用 move 会误伤左值 |
| 引用折叠 | reference collapsing | T& + && → T&;T&& + && → T&& |
这是完美转发能工作的底层机制;左值传入时 T 推导为 T& |
| lambda 表达式 | lambda | 匿名仿函数对象,捕获的变量成为这个类的成员 | 值捕获是快照,引用捕获会悬垂;空捕获可转函数指针 |
| lambda 捕获 | capture list | [x] 值捕获、[&x] 引用捕获、[x = expr] 初始化捕获(C++14) |
引用捕获的 lambda 逃出作用域就悬垂,放进异步任务要特别小心 |
| nullptr | nullptr (C++11) | 类型是 std::nullptr_t,专门表示空指针 |
NULL 是整数 0 的宏,重载 f(int)/f(char*) 时会选错版本 |
| constexpr | constexpr | 强制编译期求值,可做数组大小、模板参数 | const 只表示不可修改,运行期才知道值也可以 |
| consteval | consteval (C++20) | 强制编译期求值,运行时调用直接编译失败 | 比 constexpr 更严格:consteval 函数不允许退化成运行期调用 |
| concepts | concepts (C++20) | 给模板参数加命名约束,报错信息友好 | 写法 template <std::integral T>,比 SFINAE 可读得多 |
| 三向比较 | <=> (C++20) |
一次定义 <=>,自动生成 == != < <= > >= 六个比较 |
保证六者语义一致,避免手写出现 a<b 与 b>a 矛盾 |
| 协程 | coroutines (C++20) | 可挂起/恢复的函数,co_await / co_yield / co_return |
C++20 是无栈协程,状态放在堆上的 coroutine frame |
六、并发与多线程
Section titled “六、并发与多线程”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 进程 | process | 有独立内存空间与文件描述符,是隔离单位 | 一个进程崩不影响别的进程;切换要换页表,开销大 |
| 线程 | thread | 进程内的最小执行单元,共享进程内存 | 一个线程段错误整个进程崩;游戏用线程是为了共享内存零拷贝 |
| 数据竞争 | data race | 两个线程并发访问同一内存、至少一个写、且无同步 → UB | 读读不算竞争,读写与写写才算 |
| 互斥量 | std::mutex | 锁,保护临界区,同一时刻只允许一个线程进入 | 裸 lock/unlock 一旦中途 return 就忘解锁 → 其他线程永久阻塞 |
| lock_guard | std::lock_guard | RAII 锁:构造加锁、析构解锁,不能手动控制 | 绝大多数加锁场景用它最安全 |
| unique_lock | std::unique_lock | 可手动 lock/unlock、可移动、可配条件变量 | 条件变量必须用它,因为 wait 要原子地释放锁并重新上锁 |
| scoped_lock | std::scoped_lock (C++17) | 一次性锁多个互斥量,内部用死锁避免算法 | “两个账户互相转账”这类场景的标准答案 |
| 死锁四条件 | deadlock conditions | 互斥、占有且等待、不可剥夺、循环等待 | 破坏任意一个即可;实践中最常破坏循环等待(按固定顺序加锁) |
| 条件变量 | condition_variable | 让线程等待某条件成立,由其他线程 notify 唤醒 | 必须配 unique_lock;condition_variable_any 才允许其他锁类型 |
| 虚假唤醒 | spurious wakeup | 被唤醒 ≠ 条件一定满足,可能空唤醒 | wait(ul, pred) 内部等价 while(!pred) wait(ul),天然免疫 |
| 内存序 | memory order | 控制编译器/CPU 对指令可见性与重排的约束 | relaxed 只保证原子性;acquire/release 配对;seq_cst 是默认最安全也最慢 |
| 线程池 | thread pool | 固定数量工作线程 + 任务队列 + 条件变量 + stop 标志 | 省掉反复创建销毁线程的开销;析构要置 stop 并唤醒全部再 join |
| job system | job system | 把大任务拆成互相独立的小 job 放进多线程队列并行执行 | UE 的 TaskGraph 是例子;比“每系统一个线程”更负载均衡、少死锁 |
| 线程安全单例 | magic static | 局部 static 变量的初始化由 C++11 保证线程安全 | 单例首选写法;DCLP 的坑是 new 三步可能乱序,会拿到未构造完的对象 |
七、STL 容器与迭代器
Section titled “七、STL 容器与迭代器”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| vector | std::vector | 动态连续数组,三个指针管理 start / finish / end_of_storage | 结构题要能说出这三个指针分别代表什么 |
| 扩容机制 | reallocation | 容量满就分配新内存、搬元素、释放旧内存 | 增长因子 GCC 是 2 倍、MSVC 是 1.5 倍 |
| 迭代器失效 | iterator invalidation | 容器操作后原迭代器/指针/引用不再有效 | vector 扩容后全部失效;erase 后必须 it = v.erase(it) |
| reserve | reserve | 只扩 capacity,不改变 size,不构造元素 | 与 resize 的区别是高频考点:resize 会真的构造/销毁元素 |
| emplace_back | emplace_back | 转发参数在容器内原地构造,省一次临时对象拷贝/移动 | 参数已是现成对象时两者差不多;列表初始化容易踩坑 |
| 红黑树 | red-black tree | 自平衡 BST,最长路径 ≤ 2×最短路径,操作 O(log n) | 选它不选 AVL 的理由:旋转少、常数小、整体统计性能好 |
| map | std::map | 红黑树实现的有序映射 | 迭代器指向 pair<const Key, T>,所以 key 一定不可修改 |
| map operator[] 副作用 | map operator[] | key 不存在时会插入一个默认值 | 只判断存在要用 find / count / contains(C++20),别用 [] |
| unordered_map | std::unordered_map | 哈希表 + 链地址法,平均 O(1) 查找 | 桶内转红黑树是 Java 8 HashMap 的行为,C++ 没有 |
| 负载因子 | load factor | 元素数 / 桶数,超过阈值(默认 1.0)触发 rehash | rehash 是 O(n) 且全部迭代器失效 |
| 自定义哈希 | std::hash 特化 | 自定义类型作 unordered_map 的 key,要提供 hash 与 == |
两者缺一不可,只写一个编译不过 |
| vector<bool> | vector<bool> | 按位压缩存储,operator[] 返回 proxy 不是 bool& |
所以它不满足标准容器要求,取它元素的地址会出错 |
八、性能优化
Section titled “八、性能优化”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 测量优先 | measure, don’t guess | 先用 profiler 找到热点再动手优化 | 90% 的直觉优化是浪费;流程是测量→定位→优化→复测 |
| 虚函数开销 | virtual call overhead | 间接跳转(取 vptr 查表跳转)+ 无法内联 + 分支预测变差 | 单次约 5~15ns 看似无感,但每帧×海量对象就累积成几毫秒 |
| 去虚拟化 | devirtualization | 编译器把查虚表的间接调用改写成直接调用,之后才谈得上内联 | 触发条件:直接对象、final 类/函数、构造析构内、显式限定、LTO |
| 缓存行 | cache line | CPU 加载内存的单位,通常 64 字节 | 访问一个 int 会把周围 64 字节一起搬进缓存 |
| 缓存未命中 | cache miss | 数据不在缓存,要等主内存,约 200+ 周期 | 顺序遍历命中率接近 100%,随机访问可能慢 10~100 倍 |
| SoA / AoS | Structure of Arrays / Array of Structures | AoS 一个对象一个结构体;SoA 每个字段一个数组 | 批量处理单字段时 SoA 的缓存行全是目标数据,粒子与 ECS 都用它 |
| 分支预测 | branch prediction | CPU 猜 if 走哪边,猜错要清空流水线,惩罚约 20 周期 | 数据有序则易预测;缓解用排序、branchless 位运算、查找表 |
| 内联 | inline | 把函数体展开到调用处,省调用开销并暴露优化机会 | inline 关键字只是建议,编译器决定;虚调用无法内联 |
九、设计模式与工程实践
Section titled “九、设计模式与工程实践”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 单例模式 | Singleton | 保证全局唯一实例并提供全局访问点 | 游戏里诟病它把全局状态藏起来;替代是依赖注入或引擎统一持有子系统 |
| 工厂模式 | Factory | 把对象创建逻辑集中,加类型只改工厂 | 加类型用 switch 要改工厂代码,用注册表(map 存创建函数)只加一行 |
| 观察者模式 | Observer | 被观察者维护一组观察者,状态变化时逐一通知 | 四个坑:循环通知、回调里改监听容器、回调悬垂、热路径性能 |
| 状态模式 | State | 把状态抽成类,行为随状态切换 | 状态多了会类爆炸;要小心“update 中间切换状态”的时机问题 |
| 命令模式 | Command | 把操作封装成对象,可进队列、可撤销重做 | undo 靠历史栈弹栈调 undo();联机只传命令两端确定性执行 |
十、游戏开发与引擎
Section titled “十、游戏开发与引擎”| 术语 | 英文/缩写 | 一句话解释 | 面试要点 |
|---|---|---|---|
| 游戏循环 | game loop | 输入 → 更新 → 渲染,循环到退出 | 60fps 意味着一帧预算 16.7ms,超了就掉帧 |
| 固定步长 | fixed timestep | 逻辑/物理按固定 dt 推进,用 accumulator 累加 | 保证物理稳定与确定性,是联机同步的前提 |
| 可变步长 | variable timestep | 每帧用真实流逝时间做 dt | 实现简单,但帧率波动会让物理抖动、跨机器结果不一致 |
| 四元数 | quaternion | 用 4 个分量(w + xyz)表示旋转 | 无万向节死锁、slerp 平滑插值;欧拉角两轴对齐会失去一个自由度 |
| 实体组件系统 | ECS | Entity 只是 ID、Component 是纯数据、System 是纯逻辑 | 缓存友好(组件数组天然 SoA)+ 易并行 + 加能力只需加组件 |
| 粗检测 | broad phase | 用空间分区快速筛掉绝大多数不可能碰撞的对象对 | 把全对全检测从 O(n²) 降到接近 O(n) |
| 细检测 | narrow phase | 对候选对做精确求交 | 用 AABB/球/OBB 相交测试、多边形分离轴;它不再是全对全 |
| 四叉树/八叉树 | quadtree / octree | 递归把空间四等分/八等分 | 四叉树用于 2D 与静态地形,八叉树是 3D 场景查询的标准结构 |
| BVH | Bounding Volume Hierarchy | 用包围盒树包住物体,可实时更新 | 动态物体(角色、刚体)友好,是物理引擎 broad phase 的核心 |
| 渲染管线 | render pipeline | 顶点着色 → 图元装配/光栅化 → 片元着色 → 输出合并 → 帧缓冲 | 顶点着色每顶点一次、片元着色每像素一次 |
| 深度测试 | depth test | 在输出合并阶段按深度决定像素是否被遮挡 | 它属于输出合并阶段,不是片元着色阶段 |
| 延迟渲染 | deferred rendering | 先渲出 GBuffer(位置/法线/颜色/金属度),再统一算光照 | 光照从“每物体”变“每像素一次”,但吃带宽、MSAA 差、透明要回前向 |
| DrawCall | draw call | CPU 向 GPU 提交一次“画这个物体”的命令 | 贵在 CPU 提交通信 + GPU 状态切换,不是 GPU 算不过来 |
| 合批 | batching(批处理) | 同材质物体合并成一个网格一次画完 | 分静态合批与动态合批;减少 DrawCall 最常用的两招之一 |
| GPU 实例化 | instancing | 同一网格不同变换一次提交 N 个,着色器按 instanceID 取自己的变换 | 十万颗草用一次 Instancing;与合批的差别是网格相同、变换不同 |
| 遮挡剔除 | occlusion culling | 被其他物体挡住的物体不画 | 与“不在视野内”的剔除不同:遮挡关系要靠额外查询判断,开销更高 |
| UObject | UObject | UE 里几乎所有引擎对象的基类 | 提供反射、GC、序列化、编辑器集成、网络复制五大能力 |
| 反射 | reflection | 运行时查询类的属性/方法及其元信息 | C++ 原生没有;UE 用 UCLASS/UPROPERTY/UFUNCTION 宏 + UHT 生成代码补上 |
| UE 垃圾回收 | UE GC | 可达性分析(Mark-Sweep):从根出发标记可达对象,未标记的回收 | 循环引用天然解决;但 UObject 引用没用 UPROPERTY 标记就不算根,会被回收 |
| Mono | Mono (Unity) | IL 在运行时 JIT 编译成机器码 | 迭代快、支持热更;iOS 曾禁止 JIT 所以受限 |
| IL2CPP | IL2CPP (Unity) | IL 先转成 C++ 再 AOT 编译成原生机器码 | 平台全、防反编译、性能好,但包大、难热更;正式发布包用它 |
- 转换看数值,比较要同类型——多继承下
(void*)pa == (void*)pb比的是地址数值(差 16);要判断“是不是同一个对象”,先把两边转成同一个指针类型再比。 - 有新对象 = 拷贝构造,老对象赋值 = 拷贝赋值——看运算符左边是“创建”还是“已存在”,一句话区分两个高频考点。
- 栈和堆默认不初始化,全局/static 默认是 0——
new int是垃圾值,new int()才是 0,int y{}也是 0。 - sizeof 答“占地多大”,alignof 答“住址要求”——
{char,int}是 sizeof 8、alignof 4;按成员从大到小排 padding 最少。 - 小端 = 尾巴(低位字节)先存——小端机
0x12345678内存里依次是78 56 34 12,别背成12 34 56 78。 - 不能拷贝 ≠ 不能进 vector——unique_ptr 禁拷贝但可移动,
vector<unique_ptr<T>>完全合法,扩容走移动构造。 - move 不移动,forward 才有条件——
std::move只是转型,真正搬资源的是移动构造;通用转发必须用forward,用move会误伤左值。 - wait 一定要带谓词——
cv.wait(ul, pred)内部是 while 循环检查,天然免疫虚假唤醒;单参 wait 配if是经典错误。 - 谁不依赖对方活着,谁就用 weak——打破循环引用时,子→父用 weak、资源→管理器用 weak,方向选错等于没改。
- 先测量,再优化——90% 的直觉优化是浪费;性能话题先答“用 profiler 找热点”,再谈缓存、分支与内存池。