
网易2023校招笔试-C开发工程师正式第二批这份卷子我当时是卡着时间做完的出来之后跟几个一起考的同学对了一圈答案发现不少题大家错得都挺一致。现在复盘下来这批题整体不算偏难怪但很吃基本功尤其是C语法细节、STL的熟练度、以及对算法复杂度的敏感度。如果你正在准备C开发岗位的校招笔试这份复盘应该能帮你少走不少弯路。1. 笔试整体复盘题型分布与考察逻辑1.1 试卷结构三小时里到底考了什么网易2023校招C开发工程师的正式第二批笔试时间给得挺充裕我记得是三个小时。整套卷子大概是三块选择题、编程题、还有一部分类似简答或者代码填空的内容。选择题部分基本是C语法为主夹杂着数据结构、操作系统、网络的基础题。语法考察得非常细比如const的修饰规则、static在不同场景下的含义、虚函数表的内存布局这类没有扎实的底子很容易在一两个选项上犹豫半天。还考了一些C11/14/17的新特性类似auto推导的边界情况、右值引用和移动语义的辨析这部分我在考场上有两道题是蒙的复盘的时候才查清楚。编程题一共四道难度梯度还是比较明显的。第一题是签到题基本是vector的简单操作加一点字符串处理第二题开始上强度考了一个字符串相关的模拟题第三题是经典的数据结构应用题我当时是用单调栈解的第四题压轴考的是图论和动态规划的结合时间不够的话基本只能拿部分分。比较有意思的是这套卷子里还出现了一些工程向的考察点。比如有一道代码填空题给了一段多线程代码让你补全锁的用法还有一道关于内存泄漏的简答题问怎么定位和修复。这说明网易在筛人的时候不只看你会不会写算法更在意你有没有工程意识。1.2 时间分配策略先拿稳分再啃硬骨头三个小时看着久但如果你在选择题上卡太久后面编程题的时间就会很紧张。我的建议是选择题控制在40分钟以内遇到拿不准的题先标记不要恋战。我身边有同学就是选择题纠结太久最后压轴题连题目都没看完很可惜。编程题建议从第一题开始往后做先把该拿的分拿满。第四题如果五分钟之内没有清晰的思路果断先写一个暴力解拿部分分等前面的题都提交了再回来优化。这批次笔试的编程题是ACM模式要自己处理输入输出不像LeetCode那样帮你封装好了这个区别特别重要后面我专门讲。1.3 网易这批笔试的命题风格偏工程落地不偏难怪把整套卷子刷下来我的整体感受是网易的题不跟你玩偏的怪的花活但特别看重你把基础知识“用到实处”的能力。同样是考排序它不问你快排的时间复杂度推导而是给你一段几乎排好序的数据问你在这种情况下哪种排序性能最好——这就是在考察你是否理解排序算法的适用场景而不是背结论。所以准备网易C笔试重心应放在C核心语法细节、STL容器与算法的熟练运用、经典数据结构的灵活变形、以及多线程和内存管理这些工程向主题上。它的每一道题背后都对应一个真实开发场景的抽象这一点如果你之前写过多线程程序或者处理过线上内存问题做题的时候会明显感觉更得心应手。2. C基础考点精讲最容易丢分的语法细节2.1 字符串初始化一题暴露你的C功底这次笔试的选择题里有一道关于字符串初始化的题看着很简单但正确率据我后来跟同学对答案其实不高。题目大概是让你判断下面几种初始化方式的区别std::string s1 hello; // 拷贝初始化 std::string s2(hello); // 直接初始化 std::string s3{hello}; // 列表初始化 std::string s4 s1; // 拷贝构造 std::string s5 std::move(s1); // 移动构造这题表面考初始化语法实际上考的是C11之后的初始化列表优先级、拷贝与移动的触发时机、以及explicit构造函数对初始化方式的影响。这里有个特别注意的点如果用std::vectorint v{5, 1}这是列表初始化结果是两个元素5和1但如果写成std::vectorint v(5, 1)结果是5个1。笔试里就喜欢考这种括号和花括号的差异我见过不止一次。刷题的时候如果你在VS Code里配置过C环境可以顺手把这类小例子跑一遍加深印象光看理论记不住。2.2 constexpr的版本演进从C11到C20热词里有一条“constexpr哪个c版本引入的”这个知识点今年特别容易被考到因为C新标准迭代太快很多人分不清。constexpr是C11引入的关键字用来声明“可以在编译期求值”的表达式或函数。但要注意C11的constexpr函数限制非常严格函数体内基本只能有一条return语句不能有循环和局部变量。到了C14放宽了这个限制允许在constexpr函数中使用局部变量、循环、分支。C17和C20又进一步扩展了constexpr的适用范围比如C17里constexpr可以做lambda表达式C20开始支持在constexpr上下文中使用一些标准库容器。网易这批笔试题里考了一道代码阅读题给了个constexpr函数问它是否符合C11标准。我看到那道题的时候第一反应是“这函数能跑啊”但仔细一看函数体里有for循环在C11标准下这就是非法的。这种题就是专门坑那些只熟悉最新标准、不了解历史演进的人。2.3 读入整行cin.getline的坑与正确写法笔试编程题第一题就涉及字符串处理很多人在读入包含空格的字符串时踩坑。直接用cin s遇到空格就停了但题目要读的是“I love C”这样的整行。正确的做法是#include iostream #include string int main() { std::string line; std::getline(std::cin, line); // 读入一整行 return 0; }但如果前面用了cin n后面再跟getline就会遇到一个经典问题cin n读完数字后缓冲区里还留了一个换行符getline会把它当空行读走。解决办法是在两者之间加一句getline把残留的换行符吃掉或者用cin.ignore()。int n; std::cin n; std::cin.ignore(); // 吃掉换行符 std::string line; std::getline(std::cin, line);在ACM模式的笔试里输入输出处理如果出问题代码逻辑再对也拿不到分这个细节一定要专门练。我自己吃过一次亏之后现在每次笔试前都会先写一遍这几种输入模式的模板确保肌肉记忆。2.4 结构体链表基本语法不能含糊链表相关的题在笔试中出现的频率很高而它最基础的就是结构体的定义和节点的创建。网易这次在选择题里也考了一个结构体链表的语法题选项里混了struct的C风格用法和C的类风格用法。struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* head new ListNode(1); ListNode* node2 new ListNode(2); head-next node2; delete node2; delete head;关键的考点是构造函数初始化列表、-和.的区别、以及手动管理内存时的new和delete配对。有考生用C风格的方式ListNode* head (ListNode*)malloc(sizeof(ListNode));导致后面的构造函数没有执行val未初始化这在笔试的代码填空里就属于必错的写法。还有一个容易忽略的点如果你在析构函数里delete了节点但节点在别处又被引用就会造成悬空指针。笔试里虽然不要求你写完整的内存管理但选择题和简答题会专门拿这种场景来考。3. 算法题核心战场这些题我全部写了一遍3.1 快速幂几乎每年都出现的题快速幂在网易这批笔试里作为一道独立的小题出现在选择题的算法分析部分但它在编程题里也经常作为某个子步骤出现。快速幂解决的问题是快速计算a^n对m取模的结果其中n可能大到10的9次方甚至更大。基本原理是二分把a^n看成a^(n/2)的平方再乘以a当n为奇数时。这样时间复杂度从O(n)降到O(log n)。迭代写法如下long long fastPow(long long a, long long n, long long mod) { long long res 1; a % mod; while (n 0) { if (n 1) { res res * a % mod; } a a * a % mod; n 1; } return res; }笔试中常犯的错误有两个一是忘了a % mod这一行导致a本身已经大于模数了中间计算溢出二是res res * a % mod这行写成res * a % mod由于运算符优先级问题实际结果完全不对。快速幂的复杂度是O(log n)很多同学能看懂但手撕容易错建议考前务必默写三遍以上。热词里还出现了“快速幂算法c”说明这是笔试备考的高频搜索词。网易这批笔试虽然没有直接让写一个快速幂的完整题但我记得选择题里有一道关于它的复杂度分析和边界条件判断所以这个概念一定要吃透。3.2 排序算法的考核形式不只是写冒泡笔试里不能直接考你“写一个快速排序”因为所有人都背过模板。网易的方式是给一个场景让你选最合适的排序算法。比如有一道题说有一个几乎已经排好序的大数组只有少数几个元素位置不对问哪种排序算法在这种情况下表现最佳。答案是插入排序。当数据接近有序时插入排序的时间复杂度可以接近O(n)而快排和归并在这种场景下反而因为递归开销和额外内存而吃亏。冒泡排序虽然也可以做类似的优化但交换次数多实际表现不如插入排序。这里面暗含了一个结论不要把算法当成背模板来看待。你应该能说出每个排序算法的适用场景、最坏情况、稳定性、空间复杂度。我当时整理了一个对比表方便考前扫一眼排序算法平均时间复杂度最坏情况额外空间稳定性冒泡排序O(n^2)O(n^2)O(1)稳定插入排序O(n^2)O(n^2)O(1)稳定选择排序O(n^2)O(n^2)O(1)不稳定快速排序O(n log n)O(n^2)O(log n)不稳定归并排序O(n log n)O(n log n)O(n)稳定堆排序O(n log n)O(n log n)O(1)不稳定选择题还考了“哪些排序算法是稳定的”这个经典问题。很多人把选择排序和堆排序记反要知道选择排序虽然做的是交换但它会打乱相同元素的相对位置所以不稳定。3.3 单调栈一类题型的通用解法第三道编程题我用的是单调栈解法。题目大意是给定一个整数数组对于每个元素找出它右边第一个比它大的元素的位置。这类“下一个更大/更小元素”的题是笔试的常客单调栈是标准解法。单调栈的核心思想是维护一个栈保持栈内元素的单调性递增或递减。当新元素入栈时如果破坏了单调性就弹出栈顶元素并在弹出的过程中计算答案。#include vector #include stack std::vectorint nextGreaterElement(std::vectorint nums) { int n nums.size(); std::vectorint res(n, -1); std::stackint stk; // 存下标 for (int i 0; i n; i) { while (!stk.empty() nums[stk.top()] nums[i]) { res[stk.top()] i; stk.pop(); } stk.push(i); } return res; }注意几个细节栈里存的是下标而不是值因为计算位置差需要下标循环结束之后栈里剩下的元素右边没有更大的数保持-1。用单调栈可以把时间复杂度从暴力法的O(n^2)降到O(n)这在数据范围达到10^5时是质的差别。笔试时如果没有这个敏感性写暴力法就很容易超时。3.4 最小公倍数别忽视数论基础热词里有“n个整数的最小公倍数怎么求c”这八成是准备笔试的人搜出来的。笔试中真正考n个数的最小公倍数的可能性不大但它作为一道选择题的子知识点出现过核心解法是用辗转相除法求最大公约数GCD然后通过公式求最小公倍数LCMgcd(a, b) * lcm(a, b) a * b所以lcm(a, b) a / gcd(a, b) * b注意这里先除后乘可以避免中间结果溢出。#include numeric int gcd(int a, int b) { while (b ! 0) { int tmp a % b; a b; b tmp; } return a; } long long lcm(int a, int b) { return 1LL * a / gcd(a, b) * b; }求n个数的最小公倍数只需从左到右两两合并ans lcm(ans, nums[i])。笔试里如果出现这样的题范围通常不会太大用long long做中间量基本就稳了。4. 高阶难点多线程与内存管理的考察方式4.1 C多线程笔试常考的三个点网易这批笔试的多线程考察让我印象很深因为不是单纯让你写一段std::thread代码而是结合了代码填空题和简答题。总结下来有三个核心考点。第一个是std::thread和std::mutex的基本使用。题目给了一段多个线程同时写一个共享变量的代码让你找出问题并修复。标准解法是用std::lock_guard管理锁确保在作用域结束后自动解锁。这里要注意std::lock_guard不能显式手动解锁如果需要提前释放锁要用std::unique_lock。#include thread #include mutex std::mutex g_mtx; int counter 0; void increment() { std::lock_guardstd::mutex lock(g_mtx); counter; }第二个是std::atomic与互斥锁的区别。对于简单的计数器可以用std::atomicint替代互斥锁性能更好。但笔试的简答题会问“什么情况下atomic不足以替代mutex”。答案是当需要保护的不只是一个变量而是一段复杂的状态转换逻辑时atomic无法保证原子性必须用锁。第三个考点是死锁的产生条件与避免。常见的死锁是多个线程按不同顺序锁定多个互斥量。避免方法是确保所有线程都按相同的顺序加锁或者使用std::lock一次性锁住多个互斥量。4.2 ABA问题从CAS到解决方案热词里“aba问题c”也出现了。ABA问题源于CASCompare-And-Swap操作线程1读到一个值是A在被调度出去之前线程2把值从A改成B又改回A等线程1恢复运行后CAS比较时发现值还是A于是CAS成功但实际上数据已经被改动过了。笔试中这道题以选择题形式出现问哪些方案可以解决ABA问题。正确答案是使用带版本号的引用或者使用std::atomic配合额外标记。比较经典的方案是std::atomicstd::shared_ptrT配合无锁数据结构或者使用ABA问题的经典解法——在指针中嵌入版本计数器。如果你完全不熟悉无锁编程里的CAS语义compare_exchange_strong / compare_exchange_weak这道题基本无从下手。4.3 设计模式与回调函数面向对象细节网易这批笔试的设计模式题比我想象中要多一些。有一道选择题是给出一段代码问它体现了哪种设计模式。代码里有一个抽象基类和多个派生类然后在运行期根据条件动态创建不同派生类的对象——这显然是工厂模式。但要注意选项里也混了策略模式和观察者模式如果只记模式名字而不看具体代码结构很容易错。回调函数也是C开发笔试的高频内容。笔试考回调函数的本质是函数指针的应用场景。C11之后更推荐用std::function和lambda表达式来代替裸函数指针#include functional #include vector void process(std::vectorint data, std::functionint(int) callback) { for (auto x : data) { x callback(x); } } int main() { std::vectorint data {1, 2, 3}; process(data, [](int x) { return x * 2; }); return 0; }这里要特别注意lambda的捕获列表按值捕获和按引用捕获在使用时完全不同笔试很喜欢在代码阅读题里考察[]、[]的差异以及捕获的变量在lambda生命周期中的有效性。5. 编程实战环节如何稳定拿分5.1 在线笔试环境的特点和应对网易的编程题用的是牛客网或者类似的在线OJ系统所以有几个点要提前适应。首先编译器版本和本地不一样。笔试环境默认是C14或C17但个别公司也会用C11。如果你在本地VS Code配的C环境是最新的g用了很多C20的特性比如std::span、概念concepts到了笔试环境可能编译不过。稳妥起见笔试前把代码控制在C11规范内最多用C14。另外在线OJ的编译选项通常比较严格Warning可能不当作错误但一些未定义行为比如整数溢出、越界访问会直接导致结果错误而且不容易排查。比如int类型在计算a * b时溢出本地跑可能没问题是因为编译器优化在线环境就可能结果不对。做算法题涉及乘法运算时建议直接用long long。5.2 输入输出处理的常见坑笔试编程题是ACM模式输入输出要自己处理。常见坑有三个第一个是数组形式不固定。有的题输入是一行空格分隔的数字有的题是每个数字占一行还有的题直接给[1,2,3]这种带方括号和逗号的格式。你需要根据输入描述做不同的解析。#include iostream #include sstream #include vector int main() { std::string line; std::getline(std::cin, line); std::istringstream iss(line); std::vectorint nums; int x; while (iss x) { nums.push_back(x); } return 0; }第二个是输出格式要求。很多题要求数字之间用空格分隔、末尾不能有多余空格。一个通用技巧是遍历输出如果不是第一个元素就先输出一个空格再输出当前元素这样永远不会有末尾空格。第三个是while (std::cin n)这种处理多组输入的方式。有些题输入是多组测试用例没有明确告诉你有多少组这时要用while (std::cin ...)的写法读到文件结束自动退出。如果你用if读取只能处理单组直接凉了一半。5.3 减少运行时间的实战技巧热词里有一条“c怎么只能加代码的情况下减少运行时间”这个说得很直白就是笔试里代码不让改只能加怎么优化运行时间。我有几个实际用过的招。第一招是去同步。如果代码里没有输入输出交叉的场景在main函数开头加这两行std::ios::sync_with_stdio(false); std::cin.tie(nullptr);这两行能显著提高cin/cout的运行速度。原理是关闭C的输入输出流和C标准输入输出库的同步避免每次cin都同步到scanf。代价是你不能在代码里混用printf和cout否则输出顺序可能错乱。第二招是减少中间对象的拷贝。在循环里频繁构造string、vector时改用reserve预留空间避免多次扩容和复制。例如std::vectorint ans; ans.reserve(n); // 提前知道最多n个元素第三招是尽量使用i而不是i。对于int类型两者在现代编译器下基本没有区别但对于迭代器类型i会返回旧值需要构造一个临时对象开销更大。笔试代码里养成写i的习惯没有坏处。第四招是关注时间复杂度。比如要多次查找元素是否在集合中用std::unordered_set的平均O(1)查找比std::set的O(log n)更快。此外在穷举子集和排列时优先考虑剪枝而不是强行优化某个函数的常量因子。6. 避坑实录这批笔试中的典型问题6.1 编译不过的三大原因笔试中如果提交显示编译错误最常见的三个原因我来总结一下。第一个是int main()写成了void main()。虽然有些编译器放宽了这个要求但OJ环境通常严格检查返回值。第二个是结构体定义末尾忘了加分号struct Node { int val; Node* next; }; // 这个分号千万不能丢第三个是被面试官坑到的代码里用了C11的nullptr但写了NULL也能过可有些编译环境会报NULL未定义。稳妥起见C代码里一律用nullptr如果你需要兼容C才使用NULL或0。6.2 超时的排查思路如果你提交后显示“运行超时”代码逻辑可能是对的但效率不达标。最常见的超时原因是暴力算法。比如找“下一个更大元素”你用两重循环数据范围一大必超时这时就该换单调栈。再比如计算斐波那契数列递归写法在n很大时直接超时要改用递推或矩阵快速幂。我的排查思路是先看数据范围。如果n是10^5级别那O(n^2)的算法大概率超时O(n log n)或者O(n)才行。如果n是10^3级别O(n^2)可能还可以。如果n是10^9级别那必须用O(log n)的算法。这个敏感度要在平时刷题时专门训练。另一个容易忽略的超时原因是STL容器的误用。比如在std::vector的头部频繁insert每次insert都是O(n)复杂度如果插入次数是n整体就是O(n^2)。这种场景应该改用std::deque或者std::list。还有std::unordered_map在极端哈希冲突情况下可能退化到O(n)笔试如果故意构造数据会卡这种点。一般来说用std::map红黑树实现更稳定虽然单次操作是O(log n)但不容易被卡。6.3 我踩过的几个具体坑我在这批笔试里踩了一个很典型的坑编程题第一题题目要求先读入数组长度n接下来一行是n个整数。我用cin n读完之后又用getline读第二行结果getline读到了一个空字符串。原因就是上一节提到的cin n之后缓冲区里残留了一个换行符getline直接把它消费掉了。后面所有数据全部错位。最后我用cin.ignore()解决了这个问题。这种问题如果你之前没遇到过考场上真的会浪费很长时间。还有一个坑是我提交了一道题的代码本地测试全部通过但OJ判题错误。后来发现是数组越界。代码里一个循环访问了nums[i 1]当i等于n-1时越界了。本地跑的时候因为是POD类型且内存布局恰好没报错但OJ环境内存布局不同就出问题了。从那以后凡是涉及i 1、i - 1的访问我都会先检查边界条件这个习惯在笔试里救了我好几次。另外笔试期间尽量少切换编译器环境。我记得考场上有同学一直用VS Code的Code Runner在本地跑测试但把代码复制到OJ上去之后因为OJ用的g版本不支持某些扩展语法编译就出错了。我个人的经验是平时练习就在一个跟OJ相近的在线平台上跑比如你可以在本机装一个较新版本的MinGW-w64用g -stdc17 -O2编译你的代码这样最接近笔试环境。VS Code里配置C/C环境的时候把编译命令显式加上-stdc17不要用默认。6.4 CHAT记录笔试后和朋友的讨论考完试那天晚上我和几个朋友在群里对了一下答案发现大家错得比较集中的地方是选择题里关于虚函数和移动语义的两道题。一道题是基类析构函数不是虚函数派生类对象通过基类指针delete会发生什么。答案是未定义行为——通常只会调用基类析构函数派生类部分不会被正确释放导致资源泄漏。这个知识点考察的是“当你把基类的析构函数标记为virtual时通过基类指针删除派生类对象才会正确调用派生类析构函数”这个规则。另一道题std::move到底做了什么。很多人以为std::move是移动数据的函数实际上它只是一个转换函数把左值强制转换为右值引用。真正发生移动构造或移动赋值是在接收方处理这个右值引用的时候。如果接收方没有实现移动构造编译器可能会退化为拷贝构造。理解了这一点你才能真正理解移动语义的限制条件。这两道题都属于“平时知道但不深究”的知识点。网易正好就考这些说明它对C基础的要求已经超过了“会用库函数”的层面而是真的考察你对语言机制的理解。6.5 笔试后需要继续补强的地方复盘完这批笔试我有几个方向是明确需要继续投入时间的。第一个是并发编程的实践经验。选择题考了std::atomic和std::mutex的区别我虽然能选对但要在代码里写出一个无锁的数据结构我还需要大量练习。第二个是泛型编程和模板元编程的细节笔试没细考但面试环节很可能会追着问。第三个是操作系统和计算机网络的基础网易这批笔试的选择题也涉及了一部分虽然占比不高但不能完全没有概念。另外如果你正在准备校招有个实操建议把每一家公司笔试做错的题都整理到一个文档里标注错误原因和正确思路。我自己有一个Excel表按公司、题型、知识点、错误原因、正确解法五列来记到了面试前集中过一遍效果非常好。笔试不是考完就结束了它其实是面试前最高效的模拟训练。7. 一些笔试通用的实操心得最后还是忍不住想再多唠叨几句因为这些都是我亲手踩出来的经验。第一个心得是笔试前一定要花十分钟把所有模板代码过一遍。我指的模板不是算法模板而是输入输出模板、快速幂、最大公约数与最小公倍数、并查集、前缀和与差分、单调栈和队列。这些都是笔试题里最容易出现的骨架过一遍就能让肌肉记忆恢复。第二个心得是如果一道题五分钟没有思路直接看数据范围。数据范围会提示你该用什么算法。看到n小于等于20优先考虑状态压缩或者回溯看到n小于等于1000可以接受O(n^2)看到n小于等于10^5必须用O(n log n)或更优解法。第三个心得是提交前留出五分钟检查。至少检查一遍有没有写return 0;、有没有把输出写错变量、有没有在注释里留下中文标点导致编译错误。这些小问题在紧张状态下特别容易发生别让到手的分飞了。第四个心得是不要把某一家公司笔试的失败看得太重。校招是长跑拿到offer的人通常也不是每一家都过了笔试的。关键是善于复盘和吸收把每次笔试当成一次免费的全真模拟。我后来拿到网易的面试邀请就是因为在复盘时把一些知识盲区及时补齐了在面试环节被问到的时候能讲出细节。