面灵AI→

鹰角网络引擎工具岗一面(C++与图形学)

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. std::vector 的底层实现是怎样的?
  2. vector 的元素在堆上还是栈上?
  3. C++ 的多态有哪些实现方式?
  4. 智能指针有哪些?各自的特点和适用场景是什么?
  5. 了解多线程吗?多线程编程要注意什么?
  6. 了解序列化吗?
  7. ECS 和 OOP 各自的优缺点是什么?
  8. mipmap 是干嘛的?为什么远距离会出现闪烁和摩尔纹?它有哪些额外开销?
  9. 手撕:找出数组中和为 n 的连续子数组。
  10. 除了 UE 之外你还用过哪些引擎?
  11. 你了解哪些 DCC 工具?
  12. 美术说你的工具有问题,怎么办?
  13. 如果一个美术找你要做新工具,你怎么处理?
  14. 实习经历和个人项目里最难的技术点是什么?
  15. 你平时是怎么「拷打」AI 让它帮你干活的?

《参考解析》

vector 的实现与内存位置:libstdc++ 的 vector 就是三个指针 _M_start / _M_finish / _M_end_of_storage,元素放在一段连续内存上,size() 是前两者之差、capacity() 是一三之差。容量不够时按倍数扩容(libstdc++ 2 倍、MSVC 1.5 倍),过程是申请新块 → 把旧元素 move/copy 过去 → 析构旧元素 → 释放旧块,所以 push_back 均摊 O(1),但扩容后所有迭代器、指针、引用全部失效,循环插入前先 reserve 是最常见的优化。另外 clear() 只把 _M_finish 拉回起点、不还内存,shrink_to_fit() 只是非强制的请求。

「堆上还是栈上」要把对象和数据分开讲:std::vector<int> v; 这个对象本身(三个指针)在定义它的作用域里,局部变量就位于栈上;元素数据由 allocator 从堆上分配。C++11 起默认构造不分配堆内存,空 vector 没有任何堆块;对象和元素都在栈上的容器是 std::array。

多态:静态多态包括函数重载、模板(编译期决议)和 CRTP;动态多态是虚函数加虚表——含虚函数的对象头部有 vptr 指向本类的 vtable,调用时经 vptr 找到函数地址,运行期决议。由此推出一条必考结论:用基类指针删除派生类对象时基类析构必须是 virtual,否则派生类部分不析构,是未定义行为。虚调用不能被内联、有间接跳转开销,游戏热路径上常用标签 + switch 或 CRTP 换成静态多态。

智能指针:unique_ptr 独占所有权,不可拷贝可移动,体积就是一个裸指针,零额外开销;shared_ptr 靠控制块里的引用计数(计数是原子的,但它所指的对象本身并不线程安全),make_shared 会把对象和控制块一次分配;循环引用会导致泄漏,用 weak_ptr(不增加计数,通过 lock() 提升为 shared_ptr)打断,典型场景是父子节点互相持有。注意 shared_ptr 通常是 16 字节(对象指针 + 控制块指针),高频小对象场景比 unique_ptr 贵得多。

mipmap 与远处的闪烁、摩尔纹:mipmap 是预先算好的逐级 1/2 分辨率金字塔。当屏幕上一个像素覆盖的纹素数量大于 1(远距离或大角度斜视)时,如果只在最高分辨率层采样,采样点不足会失真:高频纹理在低采样率下产生摩尔纹,摄像机一动采样相位改变,看上去就是闪烁。mipmap 用屏幕空间 UV 导数(ddx/ddy)估计纹素与像素之比,选择合适层级,让一个像素覆盖的纹素数稳定在 1 左右。额外开销:显存约增加 1/3(1+1/4+1/16+… = 4/3),预生成耗时;层与层之间要用三线性过滤插值(两次双线性,共 8 次取样),否则能看到层级分界线;各向异性过滤代价更高。另外 mipmap 假设各向同性,掠射角下仍然会糊,需要各向异性过滤兜底。

手撕:和为 n 的连续子数组。先问清数组是否有负数,两种解法完全不同。元素全为非负时用滑动窗口:l = r = 0,维护窗口和 sum,sum < n 就右移 r 累加;sum == n 记录 [l, r] 后左移 l;sum > n 左移 l 减掉。整个过程 l、r 各自只前进,复杂度 O(n)、额外空间 O(1)。如果数组中可能有负数,滑窗单调性不成立,改用「前缀和 + 哈希表」:设 pre[i] = a[0]+...+a[i-1],要求 pre[j] - pre[i] == n 即 pre[i] == pre[j] - n,一次遍历,用哈希表记下每个前缀和最早出现的下标,命中就得到一个区间,时间 O(n)、空间 O(n)。求和时注意用 64 位避免溢出。

ECS 与 OOP:ECS 把数据和行为彻底分开——组件是纯数据(尽量 POD),同类型组件连续存储,系统只遍历自己关心的组件组合。好处是缓存友好(遍历时不做无用的指针跳转)、天然按「组合」而非继承扩展、容易并行和多线程调度,适合数量大且同质的实体(单位、子弹、特效粒子)。代价是逻辑被打散在多个系统里、调用链不如 OOP 直观、调试和序列化需要框架和编辑器工具配合,对少量异构对象属于过度设计。OOP 的继承树在对象类型少、行为差异大时更好写,但深继承树容易出现「菱形问题」和为了复用而硬塞的基类。

面试复盘:面试官节奏平稳,问题覆盖 C++、图形学和引擎工具协作三类,追问不深但面很宽。原帖作者自评没答全,尤其图形学部分(mipmap 的成因与开销)和 ECS 对比准备不足——这两个都是引擎工具岗的高频题,建议按「原理 + 为什么 + 代价」三段式准备;美术协作类开放题按「先复现、再定位、给出临时方案与排期」来答即可。