元戎启行 软件工程师一面:C++ 八股与 01 串手撕
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
项目部分(约 15 分钟)
- 自我介绍,然后讲一个最拿得出手的项目
- 在项目里具体负责什么?
- 遇到过什么难点,怎么解决的?
- 项目中用过智能指针吗?什么场景下用的?
八股部分
- C++ 多态怎么实现?virtual 有什么作用?底层逻辑是什么?
- 右值引用和移动语义了解吗?std::move 是干什么的?
- C++ 编译流程:预处理、编译、汇编、链接分别做什么?
- 进程和线程有什么区别?进程间通信有哪些方式?
- epoll 和 poll 有什么区别?LT 和 ET 有什么区别?
- TCP 三次握手、四次挥手,TCP 和 UDP 的区别
手撕代码
- 给一个 01 串,保证开头是 0、结尾是 1,找到任意一个「01」子串的位置,要求复杂度尽可能低
反问环节
- 这个软件工程师岗具体是做哪一块?(JD 里写了硬件、驱动、算法、数据闭环好几个方向)
《参考解析》
-
智能指针这题考的是「用没用过」而不是「背没背过」:面试官追问使用场景,就是在验证真实性。按所有权讲最稳:
unique_ptr表达独占所有权、开销为零,适合资源持有;shared_ptr用引用计数表达共享所有权,代价是控制块与原子计数,要注意循环引用必须靠weak_ptr打破;weak_ptr只观察不持有,用lock()提升成shared_ptr再访问。再补一句工程口径:能用unique_ptr就别用shared_ptr,跨线程传递时想清楚谁负责释放。 -
多态的回答要能落到虚表和对象布局:虚函数表随类生成、对象头里存虚表指针,通过基类指针调用时按运行时类型查表分派,这就是动态绑定。可以顺带讲清几个常被追问的点:构造函数里调虚函数不会分派到派生类(此时派生部分还没构造)、析构函数要声明为 virtual 否则
delete基类指针不会调派生析构、RTTI 与dynamic_cast依赖同一套运行时信息。移动语义与std::move是纯右值转换,本身不移动任何数据,真正做事的是移动构造/移动赋值。 -
编译流程要讲清每步的输入输出:预处理展开宏、
#include和条件编译,产出翻译单元;编译做语法分析与优化,产出汇编或目标文件;汇编把汇编翻译成机器码目标文件;链接做符号解析与重定位,把多个目标文件和库拼成可执行文件或共享库。能被追问的点是静态库与动态库的差别、符号表与未定义符号报错怎么读。 -
epoll 与 poll 的差别要落到内核数据结构:
poll每次调用都要把整个 fd 集合拷进内核并线性扫描;epoll把注册和等待拆开,epoll_ctl维护内核里的红黑树,epoll_wait只返回就绪链表里的 fd,所以在高并发下少了很多无谓拷贝与扫描。LT 是水平触发,只要缓冲区还有数据就会一直通知,写起来简单不容易漏;ET 是边沿触发,只在状态变化时通知一次,必须配合非阻塞 fd 循环读到EAGAIN,漏读就会卡住。选哪个看业务对实现复杂度和唤醒次数的取舍。 -
手撕 01 串有比暴力枚举更省的解法:题目保证开头是 0、结尾是 1,所以「01」子串一定存在。从前往后找第一个出现的 ‘1’,它的前一个位置就是答案——因为首个 ‘1’ 之前全是 ‘0’,边界处必然是 0 挨着 1;对称地,从后往前找最后一个出现的 ‘0’,它的后一个位置也是答案。两端同时向中间找,谁先碰到边界就用谁,代价是 O(min(前导 0 的个数, 后缀 1 的个数)),最坏才是 O(n)。答题时先讲这个观察,再写代码,最后说清复杂度和为什么不用滑动窗口或逐位比对。
-
项目题用「难点 → 定位 → 方案 → 验证」四步讲:这场面试的复盘很值得借鉴——面试官问项目细节时,模板很容易被问穿。挑一个自己真写过的项目,把当时的现象、你排查过哪些方向、为什么最后选这个方案、改完用什么指标验证讲清楚。项目本身简单没关系,讲不出「为什么这么写」才是减分项。