面灵AI→

华为通用软件线下面经(一面 · 二面 · 主管面)

时间
2026-09
来源
牛客网

《面试题目》

一面 · 基础

  1. TCP 建连和断连的过程?
  2. 说一下对进程和线程的理解?
  3. C++ 怎么把代码转成二进制的?
  4. 线程池了解吗?为什么要用线程池?
  5. 谈谈或介绍一下你用过的数据类型?

一面 · 代码与调试

  1. 写代码怎么验证功能?
  2. 代码出错之后怎么调试?
  3. 前端有没有用一些抓包工具,比如 Postman?
  4. 有没有了解代码规范?

一面 · 手撕

  1. 手撕:独一无二单词。随后被追问 set.find() 底层是怎么实现的?

二面 · AI Coding

  1. 怎么用 AI 写代码的?是怎样的开发模式?
  2. SDD 的 S 是什么?
  3. 怎么确保 AI 的输出符合项目要求?具体怎么做、怎么验证?
  4. 有注重代码规范吗?怎么规范 AI 开发的架构?除了这些还有别的做法吗?
  5. 你项目代码量最大的是哪个?大概是多少量级?
  6. 如果是一个百万级项目,怎么用 AI?
  7. 遇到问题怎么排查?
  8. 定位到问题了,修改时是重写还是局部改?
  9. 怎么保证不对其他模块产生影响?有没有模块功能的测试用例?
  10. 有用过 gtest 测试工具吗?
  11. 有写过 skill 吗?过程是怎样的、为什么需要去写它?如果要配合或使用不同的 skill 要怎么办?

二面 · 项目

  1. 项目用的是什么模型?本地模型还是商用模型?
  2. 怎么处理商用 API 数据隐私的问题?
  3. 有本地模型部署的经验吗?
  4. 记忆是怎么设计的?

二面 · 手撕

  1. 手撕:允许一次反转的最长 target。

主管面 · 个人情况与规划

  1. 你是保研的,一开始就有规划吗?
  2. 你对工作城市有要求吗?
  3. 个人的职业规划是怎样的?
  4. 对华为部门有了解吗?对华为的工作强度有了解吗?

主管面 · 实习与比赛

  1. 实习是做什么的?考虑了安全性吗?怎么分工的?有没有遇到工作进度不一致的问题?实习最大的收获是什么?
  2. 比赛是承担哪部分工作?有没有遇到什么困难?

主管面 · 项目

  1. 项目一是做什么的?出发点和背景?是团队项目吗?
  2. 导师有没有对项目做指导?指导的过程有发生分歧吗?
  3. 项目二是做什么的?

《参考解析》

1. TCP 建连与断连:建连是三次握手:客户端发 SYN(带初始序号),服务端回 SYN+ACK(确认对方序号并给出自己的序号),客户端再回 ACK,此时双方确认了彼此的收发能力与初始序号。断连是四次挥手,因为 TCP 是全双工的,一方不再发数据不代表不再收数据:主动关闭方发 FIN,对方回 ACK 进入 CLOSE_WAIT,等它自己的数据发完再发 FIN,主动方回 ACK 并进入 TIME_WAIT,等待 2MSL 才真正关闭。TIME_WAIT 的作用是保证最后那个 ACK 能到达(丢了还能重传对方的 FIN),以及让本次连接的迷途报文在网络中消散,避免污染同四元组的新连接。

2. 进程与线程:进程是资源分配与隔离的单位,有自己的虚拟地址空间、页表、文件描述符表和信号处理;线程是 CPU 调度的单位,同一进程内的线程共享地址空间、堆、全局变量和 fd,各自持有栈、寄存器和线程局部存储。因此线程间通信靠共享内存加同步原语(互斥量、条件变量、原子量)就够,进程间则要走管道、共享内存、消息队列或 socket。切换开销上线程远小于进程(不用切页表、不易冲刷 TLB),但一个线程崩溃会带走整个进程,隔离性差。选型看场景:需要强隔离和故障隔离用多进程,需要频繁共享大量数据用多线程。

3. C++ 代码怎么变成二进制:分四步。预处理展开宏、头文件和条件编译;编译做词法语法语义分析、生成中间表示并优化,产出汇编;汇编器把汇编翻译成机器码,生成目标文件(ELF),里面有代码段、数据段、符号表和重定位表,此时外部符号地址还是占位;链接器做符号解析与重定位,把多个目标文件和库合并成可执行文件——静态库在链接期复制进产物,动态库只记录依赖,真正的地址绑定由动态链接器在加载时通过 GOT/PLT 完成。所以「符号未定义」是链接错误,「找不到动态库」是运行期错误,两者排查方向完全不同。

4. 为什么要用线程池:一是摊薄线程创建销毁的成本,线程创建要走内核、分配栈与 TLS,属于毫秒级操作,对短任务来说这部分开销可能比任务本身还大;二是限制并发上限,无节制起线程会耗尽内存和调度能力,反而让整体吞吐下降;三是统一管理任务队列、拒绝策略、超时与优雅退出。线程数不是越多越好:CPU 密集型任务一般取核心数上下,IO 密集型可以适当放大,但更稳的做法是用队列长度和响应时间做观测再调整,而不是拍一个数字。

5. 常见数据类型的选型:回答这类问题要给取舍而不是背定义。vector 连续内存、随机访问和缓存友好,尾插摊还 O(1),但中间插入删除要搬移元素且可能让迭代器失效;deque 支持双端 O(1) 插入,适合队列;list 任意位置插入 O(1)、迭代器稳定,但每个节点单独分配、缓存不友好,实际很少用;map 是红黑树,有序、查找 O(log n);unordered_map 是哈希表,平均 O(1) 但最坏退化、迭代顺序不稳定;字符串传递优先 string_view 以避免拷贝。能顺带说清「为什么选它、代价是什么」,比把容器特性背一遍更有说服力。

6. 写完代码怎么验证功能:先明确验收标准,再按层次验证。单元测试覆盖正常路径、边界值(空输入、单元素、极值、越界)和异常路径;对难以构造的依赖用 mock 或桩替换;复杂算法用「对拍」——写一个低效但显然正确的暴力解,随机生成数据比对。外部接口用 Postman 或脚本做接口级回归;内存与并发问题交给 ASan/TSan/valgrind 跑一遍。最后把测试接进 CI,保证每次提交都自动跑。仅靠手点几次页面就声称功能没问题,是面试里最容易被追问穿的部分。

7. 代码出错怎么调试:先稳定复现,把输入缩到最小;再二分定位——按提交历史、按模块、按数据范围逐步排除,这比通读代码快得多。手段上,日志与断言用于缩小范围,gdb/core dump 用于定位崩溃现场(看栈和寄存器),sanitizer 抓越界与释放后使用,perf 抓性能热点。核心是假设驱动:先写下「我认为哪里错了」,再设计一个能证伪它的实验,而不是随机改代码碰运气。改完必须补一个能覆盖该场景的测试,否则同类问题会再犯。

8. 代码规范:规范的价值是可读性和协作成本,不是审美。内容包括命名与格式、头文件依赖最小化、错误处理与返回值约定、资源管理用 RAII、日志分级与脱敏、注释解释「为什么」而不是「做了什么」。落地靠工具而不是靠自觉:clang-format 统一格式、clang-tidy/cpplint 静态检查、CI 上做强制门禁、review 用 checklist 逐条过。人手一份风格偏好是团队效率的隐形税,把能自动化的部分全部自动化掉才是正解。

9. set.find() 的底层实现:要看问的是哪个容器。如果是 std::unordered_set,底层是哈希表:一个桶数组,libstdc++ 的实现里所有节点串成一条单链表、桶指向链表中的位置,find 先对 key 求哈希、映射到桶,再沿该桶的元素链逐个用 operator== 比较,平均 O(1),最坏 O(n)。元素数量超过 max_load_factor × bucket_count 会触发 rehash,导致迭代器失效但引用和指针仍有效。如果问的是 std::set,底层是红黑树,find 是从根往下比较的 O(log n) 路径。顺带能说出「需要有序遍历或范围查询选 set,只要去重和均摊常数查找选 unordered_set」就完整了。

10. AI 写代码的模式与 SDD:SDD 指 Spec-Driven Development,S 就是 Specification(规格/说明)。它的意思是先把要做什么、接口长什么样、验收标准是什么写成明确规格,再让模型按规格产出实现,而不是丢一句模糊需求就等代码。具体模式可以概括为:人负责拆任务、定接口与验收标准、审 diff 与做最终决策,模型负责生成骨架与实现、补测试、做机械性重构。任务要切到一次只解决一个可验证的小目标,中间用编译器和测试做即时反馈,出问题立刻回到上一个绿色状态。

11. 怎么确保 AI 的输出符合项目要求:把「要求」显式化,别指望模型猜。一是给足上下文——目录结构、相邻已有代码的写法、构建与测试命令、明确的禁止项;二是把架构约束沉淀成规则文件常驻上下文,而不是每次口头重申;三是用工具做客观门禁:编译、静态检查、单元测试、格式化,任何一项红了就打回;四是 review 时按 checklist 逐条核对命名、错误处理、边界、日志与依赖方向;五是让模型自己先给出改动计划与影响面,再由人确认后动手。凡是无法被验证的要求,都不要交给模型。

12. 百万级代码量的项目怎么用 AI:核心矛盾是上下文装不下整个工程,所以靠检索和分层,而不是靠更大的窗口。做法上:给模型精准的相关文件而不是全仓,用代码索引或语义检索先定位;维护模块级文档说明职责与依赖方向,让模型先建立地图再改代码;用规则文件固定架构约定与命名习惯;动一行动全身的改动,先要影响面分析(调用方清单、序列化兼容性、对外接口变化),再按模块小步提交,每步都编译并跑该模块的测试;最后靠 CI 门禁和灰度发布兜底。说不清「改动会影响谁」时就不该动。

13. 改动如何不波及其他模块 + gtest:原则是先收缩可见性再改实现:优先改内部实现、不动公开接口;必须改接口时先加新接口并让旧接口转调,等调用方迁完再删。技术上靠编译期强类型、模块边界清晰(单向依赖)和不变量断言。gtest 是 C++ 的单元测试框架:TEST/TEST_F 定义用例,EXPECT_* 失败继续、ASSERT_* 失败中止,fixture 里 SetUp/TearDown 管理公共环境,参数化测试覆盖多组输入,配合 gmock 打桩外部依赖,接进 CI 后每次改动都会跑全量回归。有了这层网,才谈得上「改完不影响别人」。

14. skill 是什么、怎么写、多个怎么配合:skill 是把重复出现的流程固化成可复用的说明书,通常包含触发条件(什么时候该用它)、步骤、输入输出约定、边界与失败处理,必要时附带脚本。值得写 skill 的信号很明确:同一件事已经向模型重复交代过多次,或者步骤多到容易漏、顺序错了就返工。多个 skill 并行协作的关键是职责单一、接口明确——每个 skill 说清自己产出什么文件或什么格式的数据,避免两个 skill 都想做同一件事导致行为不确定;触发词也要能互相区分开。写之前先想清楚哪一步是必须固定下来的,哪一步应该留给临场判断。

15. 两道手撕题的解法:「独一无二单词」这类题的核心是哈希计数:先遍历一遍用 unordered_map 统计每个单词(或字符)的出现次数,再按题目要求做第二次判定——找只出现一次的项就扫一遍计数表,判断「所有出现次数是否互不相同」就把次数塞进 unordered_set 看有没有重复,两遍遍历都是 O(n),这也是为什么面试官会顺势追问 set.find() 的底层。「允许一次反转的最长 target」要先做等价转换:把数组映射成 0/1(是否等于 target),问题变成「最多反转一个子数组后能得到的最长连续 1」。关键结论是一次反转只能把某个 target 块整体搬到前一个 target 块右边——设相邻两块长度为 a、b,反转「前一块之后到后一块末尾」这段区间,中间的杂项被推到右侧,两块直接合并成 a+b。于是只需扫一遍求出所有连续 target 块的长度,答案就是单块最大值与相邻两块之和的最大值,O(n) 时间 O(1) 额外空间;面试官追问优化,就是想看你能不能从 O(n²) 的枚举左右端点走到这个结论。