多益网络软件工程师一面:C++ 多态、递归与 AI 幻觉治理
- 轮次
- 一面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 大学期间做过最满意的项目是什么?
- 怎么做的这个项目?
- 主要工作量有哪些?
- 用到哪些技术栈?
- 前后端是怎么交互的?
- 项目过程中遇到过什么 bug,怎么解决的?
- 最近一年印象最深刻的程序开发卡点是什么?
- 说一下最常用的数据结构,和特点。
- 深拷贝和浅拷贝的区别?
- 谈一下递归的思想,尾递归?
- C++ 多态的分类和实现?
- 字节对齐的方式?
- 什么是线程安全?
- 你更喜欢在已有项目上工作,还是新的项目,为什么?
- 新加入一个团队,你觉得应该怎么做,才能更加融入团队?
- 最近有没有关注一些技术热点?讲一个它的特点和优点。
- 在软件开发中通常会使用数据库,不使用数据库可能会造成哪些问题?有没有用过数据库,举例说明一下。
- 如何理解 AI 幻觉,在开发过程中如何解决?
- 编程题:给输入一个正整数 1-9,输出这个数字之前包括这个数字的全部排列。
《参考解析》
最满意的项目:用 STAR 讲,重点放在「你做了什么决策」而不是「用了什么技术」。项目背景一句话,工作量拆成模块(后端接口、数据库设计、前端页面、部署),技术栈只挑与岗位相关的讲,前后端交互说清协议(REST/WebSocket)、鉴权、错误处理。Bug 部分挑一个真实的:现象、定位过程(日志/断点/二分)、根因、修复与回归,最后补一句「怎么防止再发生」(加校验、加测试、加监控)。
开发卡点:不要说「环境配不好」这种琐事。挑一个有技术含量的:比如并发下的状态不一致、性能瓶颈、第三方接口不稳定。讲清「卡在哪、试过什么、最后怎么解决、学到什么」。如果最后没完全解决,也如实说,并给出后续思路。
常用数据结构:数组(随机访问 O(1)、插入删除 O(n)、连续内存)、链表(插入删除 O(1)、查找 O(n)、额外指针)、栈(后进先出,函数调用、括号匹配)、队列(先进先出,任务调度、BFS)、哈希表(平均 O(1) 查找、无序、冲突处理)、树(BST/平衡树/堆,有序与优先级)、图(关系建模)。回答时结合实际用途,别只背复杂度。
深拷贝与浅拷贝:浅拷贝只复制对象的值,指针/引用成员仍指向同一块内存;深拷贝递归复制指向的内容,两个对象完全独立。C++ 里默认拷贝构造是浅拷贝,类里有裸指针时要自己实现深拷贝(或禁用拷贝、用智能指针),否则析构会二次释放;Java 里 Object.clone() 是浅拷贝,深拷贝要手动递归或序列化。追问常考「三法则/五法则」:析构、拷贝构造、拷贝赋值,C++11 再加移动构造与移动赋值。
递归与尾递归:递归是把问题拆成「同类的更小问题 + 终止条件」,代码简洁但每次调用都压栈,深度大会栈溢出。尾递归是递归调用发生在函数的最后一步且返回值不被后续使用,编译器可以复用栈帧(尾调用优化),把空间从 O(n) 降到 O(1)。C++ 编译器(如 GCC/Clang 在 -O2)对满足条件的尾递归会优化,但语言标准不保证;Java 不支持尾调用优化。改写手段是手动转成循环 + 累加器。
C++ 多态:分编译时多态和运行时多态。编译时多态靠函数重载、运算符重载、模板(静态绑定,编译期确定调用哪个);运行时多态靠虚函数——基类声明 virtual,派生类重写,对象里有虚表指针指向虚函数表,调用时通过 vptr 查表动态分派。析构函数在多态基类里要声明为 virtual,否则通过基类指针删除派生对象会只调基类析构,造成泄漏。还有 override/final 关键字帮助编译器检查。
字节对齐:编译器为了让 CPU 高效访问,会把成员按自身对齐要求放到地址是其对齐数的倍数上,结构体整体大小是其最大成员对齐数的倍数,必要时在末尾填充。规则:每个成员的偏移是 min(成员大小, #pragma pack 值) 的倍数;结构体大小是最大对齐数的倍数。可以用 #pragma pack(n) 或 alignas 调整,但会牺牲访问效率;调整成员顺序(大的在前、小的在后)能减少填充。面试常给一个结构体让你算 sizeof,按偏移逐个算即可。
线程安全:多线程并发访问共享数据时,程序仍能表现出正确行为,不出现数据竞争、丢失更新、死锁、可见性问题。保证手段:互斥锁/读写锁、原子操作、不可变对象、线程局部存储、消息传递(避免共享)、以及正确的内存序。要注意「无状态」天然线程安全,有状态就要界定临界区;锁的粒度、顺序(防死锁)、以及锁内不要做慢操作。
已有项目还是新项目:没有标准答案,讲清偏好与理由。偏好已有项目可以说「能在约束里做增量、更接近真实工程,能学到历史设计」;偏好新项目可以说「能从零设计、决策空间大」。加分的是补一句「两种都需要,取决于阶段」:新项目重架构与规范,老项目重兼容、回归与风险控制。
融入团队:先读文档与代码规范、跑通本地环境;主动认领小任务并按时交付,建立信任;遇到问题先自己查再问,问的时候带上已尝试的方案;参与代码评审与站会,了解业务背景;尊重既有约定,想改先沟通。核心是「先贡献、再改变」。
技术热点:挑一个你能讲两层的,比如 AI Coding / Agent:特点是能理解自然语言意图、调用工具、在多轮里自我修正;优点是降低样板代码与重复劳动的成本、把「查文档—写代码—跑测试」串起来;局限是幻觉、上下文长度、权限与安全、对复杂业务的理解不足。再补一句你怎么用它(写单测、读陌生代码、生成脚本)以及怎么校验输出。
不用数据库会怎样:数据只放内存/文件会面临:进程重启丢数据、并发写冲突、无法事务保证一致性、查询与索引效率低、权限与审计缺失、备份恢复困难。数据库提供持久化、事务(ACID)、并发控制、索引与查询优化、约束、备份与复制。举例可以讲订单:下单要同时扣库存、写订单、记流水,没有事务就会出现「扣了库存没订单」;或者面试项目里用 MySQL 存用户与答题记录、Redis 做缓存。
AI 幻觉与治理:幻觉是模型生成看似合理但事实错误或与输入不符的内容,根因是概率生成 + 训练数据噪声 + 上下文不足。工程上:① 让模型基于给定资料回答(RAG),要求引用出处;② 结构化输出 + 程序校验(schema、枚举、数值范围);③ 关键事实用工具/数据库核实,不让模型凭记忆答;④ 提示词要求「不知道就说不知道」,降低温度;⑤ 人工复核高风险场景(医疗、金融、法务);⑥ 用评测集持续度量幻觉率。要强调「不能靠一句提示词解决,要靠系统设计」。
全排列:经典回溯。用一个 used 数组标记已选数字,递归填每一位,填满输出;回溯时撤销标记。复杂度 O(n! × n)。输入 1-9 很小,直接 DFS 即可;如果要按字典序输出,从小到大尝试即可。
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> path;
vector<bool> used;
void dfs() {
if ((int)path.size() == n) {
for (int i = 0; i < n; i++) cout << (i ? " " : "") << path[i];
cout << '\n';
return;
}
for (int i = 1; i <= n; i++) {
if (used[i]) continue;
used[i] = true;
path.push_back(i);
dfs();
path.pop_back();
used[i] = false;
}
}
int main() {
cin >> n;
used.assign(n + 1, false);
dfs();
return 0;
}