网易2020校招笔试 C开发工程师(正式批)复盘:真题拆解与避坑指南
又到一年校招季,后台不少读者私信问大厂 C/C++ 岗笔试到底考什么。正好我电脑里还存着当年参加网易2020校招C开发工程师正式批的完整复盘笔记,趁着这个时间点把它整理出来。这篇文章只讲干货——从题型分布到考点拆解,从编程题思路到具体的坑,全部按实际考场经历还原。不管你是准备投递大厂开发岗的应届生,还是想检验自己 C 语言功底的从业者,这篇复盘都能帮你少走不少弯路。
网易的校招笔试在互联网大厂里属于比较有代表性的:题量适中、难度梯度合理、既考基础功底又考代码落地能力。2020 届正式批的这套题,整体风格偏向“基础扎实型”,没有特别偏门的算法,但如果你对 C 语言内存模型、指针操作、经典数据结构的理解不到位,很容易在选择题里连环踩坑,编程题也会写得磕磕绊绊。接下来我按“整体情况—选择题解析—编程题复盘—常见问题—备考建议”的顺序逐一展开。
1. 笔试整体情况与题型分布
1.1 考试形式与时间分配
网易 2020 校招 C 开发工程师正式批笔试采用的是在线笔试系统,全程在牛客网平台完成,总计时长 120 分钟,题量分为两部分:客观题(选择题)+ 主观题(编程题)。编程题支持 C/C++/Java 等主流语言提交,我选的是 C 语言作答。
先说说实际考场上的直观感受:时间其实不太够用。选择题 25 道,涉及面非常广,每道题平均只有不到 2 分钟;编程题 3 道,难度递增,最后一道题我花了将近 40 分钟才把边界情况调完。建议后来人拿到试卷后先花 2 分钟通读全卷,把编程题按难度排序,先做自己有把握的,不要在一道选择题上死磕。
具体题型分布大概是这样的:
| 题型 | 题量 | 分值占比 | 建议用时 |
|---|---|---|---|
| 单选题 | 15 道 | 30% | 30 分钟 |
| 多选题 | 10 道 | 25% | 20 分钟 |
| 编程题 | 3 道 | 45% | 70 分钟 |
1.2 考察范围与考点分布
从考点覆盖来看,这套笔试题目大致可以分成四个板块:C 语言基础与内存管理、数据结构与算法、操作系统与 Linux、计算机网络。其中 C 语言基础占比最大,这与岗位要求直接相关——作为 C 开发工程师,语言本身的细节掌握程度是筛选的第一道门槛。
我统计了一下自己当时的错题分布:C 语言指针和内存相关错了 2 道,操作系统进程线程相关错了 1 道,多选里网络协议错了 1 道。整体来看,这套题出得比较“正”,没有偏题怪题,但恰恰是这种“正”,会把基础不牢的人筛得明明白白。
2. 选择题考点深度拆解
2.1 C 语言基础与内存管理:这次笔试的“重头戏”
C 语言基础在选择题里占了将近一半,其中指针与内存管理是绝对核心。我记得特别清楚的一道题:
#include <stdio.h> #include <stdlib.h> void test(char *p) { p = (char *)malloc(100); } int main() { char *str = NULL; test(str); strcpy(str, "hello"); printf("%s\n", str); return 0; }问程序运行结果是什么。选项包括:输出 hello、编译报错、运行时崩溃、输出乱码。这道题的考点是值传递与地址传递的区别。在 C 语言里,函数参数默认是按值传递的,test(str)传入的是str这个指针变量的拷贝,函数内部p = malloc(100)修改的是形参p的指向,实参str仍然为 NULL。所以strcpy(str, "hello")本质上是向 NULL 地址写数据,程序必然崩溃。
要改正确,要么传递指针的指针void test(char **p),要么让函数返回指针。这个知识点可以说百考不厌,我在后续面试中也被问过多次变体。
另一道让我印象深刻的题是关于sizeof和strlen的对比:
char arr[] = "hello"; char *p = arr; printf("%lu %lu\n", sizeof(arr), sizeof(p)); printf("%lu %lu\n", strlen(arr), strlen(p));正确答案是6 8、5 5(在 64 位系统下)。sizeof(arr)计算的是整个数组占用字节数,包含结尾的'\0',所以是 6;sizeof(p)计算的是指针本身的字节数,64 位系统下是 8;strlen只统计到'\0'前的字符数,对数组和指针结果相同。
这个考点其实反映了 C 语言一个底层逻辑:数组名在大多数表达式中会退化为指向首元素的指针,但sizeof是少数保留数组属性的场景之一。理解了这个本质,遇到类似的题就能举一反三。
2.2 数据结构与算法基础:链表、栈和二叉树的经典变体
数据结构相关选择题主要围绕链表、栈和二叉树展开,难度适中但陷阱不少。有一道关于链表反转的多选题,选项里给了四种实现片段,让你选出正确的。我当时的做法是直接在草稿纸上画链表节点图,逐个走一遍指针变化。这个习惯帮了大忙,因为这类题单纯靠“脑补”很容易漏掉边界情况,比如链表为空、只有一个节点时的空指针判断。
还有一道二叉树遍历的题,给出前序遍历序列和中序遍历序列,要求推出后序遍历。这种题没有什么捷径,就是模拟递归过程,每次从先序序列中取出根节点,再到中序序列中定位左右子树边界,递归处理。需要注意的是题目可能给出的是拓展二叉树的序列(带空节点标记),那判断逻辑就要相应调整,此时用#表示空节点,递归的终止条件不再是区间为空而是遇到#。
栈的考点多与表达式求值和括号匹配结合。2020 年这批有一道算逆波兰表达式结果的题,数据结构本身不复杂,但题目把运算数设计成了负数和多位数,增加了字符串解析的复杂度。我建议这种题在草稿纸上先设计好状态机——数字读入、遇到分隔符入栈、遇到运算符弹出两个数运算——再看题目的输入格式是否吻合。
2.3 操作系统与 Linux:进程线程和常见命令的实际场景
操作系统的选择题数量不多,大概 4-5 道,但覆盖了进程线程区别、死锁必要条件、虚拟内存、Linux 文件权限等常见考点。
有一道题给了四个关于进程和线程的描述,让选错误的。其中一个是“线程是资源分配的基本单位,进程是调度的基本单位”——这个说法把两者搞反了,正确答案应该是“进程是资源分配的基本单位,线程是 CPU 调度的基本单位”。这种题纯考概念准确性,没有技巧,复习时多下功夫背熟即可。
Linux 相关的题考察了文件权限和常用命令。有一道给出rwxr-xr--,问属主、属组和其他用户分别有什么权限,以及对应的数字表示。答案是 754,其中属主可读可写可执行(7),属组可读可执行(5),其他用户只可读(4)。这种题就是送分题,但我认识不少同学在r对应 4、w对应 2、x对应 1 这个对应关系上栽了跟头,上考场前记得再过一遍。
2.4 计算机网络:TCP、HTTP 基础与常见协议的“坑”
计算机网络部分题量不大,主要考 TCP 协议状态变迁、HTTP 请求方法、DNS 解析流程等基础内容。网易这套题里最典型的一道是关于 TCP 三次握手和四次挥手的过程描述,判断哪个选项是正确的。这里有个高频陷阱:很多同学会把“断开连接需要四次挥手”理解成“客户端主动断开时每次都需要四次”,实际上如果通信双方同时发起断开请求,可能出现同时关闭(simultaneous close)的特殊情况,整个流程会退化为四次还是六次要看具体时序。
还有一个易错点是 HTTP 和 HTTPS 的默认端口。有同学记成 80 和 8080,正确是 80 和 443。这种题本身不难,但考的就是你日常积累细不细。建议复习网络时把 TCP 状态图、端口号、各层协议的关系整理成一张表,考前快速过一遍。
3. 编程题完整复盘
3.1 编程题第一题:字符串逆序输出(送分题)
第一道编程题看起来很简单:输入一行字符串,逆序输出。很多人觉得这就是先用strlen拿到长度再倒序遍历一遍,但实际提交时才发现坑在输入输出格式上。
题目说输入可能包含空格,这意味着不能只用scanf("%s"),因为它遇到空格就停止读取了。我当时用了fgets配合手动去掉末尾换行来处理:
#include <stdio.h> #include <string.h> int main() { char buf[1024]; fgets(buf, sizeof(buf), stdin); // 去掉 fgets 读入的换行符 buf[strcspn(buf, "\n")] = '\0'; int len = strlen(buf); for (int i = len - 1; i >= 0; i--) { putchar(buf[i]); } putchar('\n'); return 0; }这里有个细节值得注意:fgets会连同换行符一起读入,如果不处理,逆序输出时换行符会被放到最前面,直接导致格式错误。用strcspn找到换行符的位置并替换为'\0',是最稳妥的写法。
这道题背后的意义在于,它考察的是你能否写出健壮的处理输入输出的代码——字符串是否包含空格、缓冲区大小是否足够、换行符是否被正确吞掉。实际工程开发里,这类边界处理的经验往往比“算法能力”更先被考察到。
3.2 编程题第二题:链表合并与去重(进阶题)
第二题要求合并两个有序链表,并去除重复元素。题目本身不算难,就是一个类似归并的过程,但网易在题目里加了一个限制:不允许额外申请节点,只能通过调整指针完成合并。这意味着你不能简单地把两个链表的 val 拷贝出来排序再重建链表,必须原地操作。
我的思路是设置一个虚拟头节点dummy,然后用双指针遍历两个链表,每次把值较小的节点接在dummy后面,同时处理重复值。具体代码如下:
#include <stdio.h> #include <stdlib.h> struct ListNode { int val; struct ListNode *next; }; struct ListNode* mergeAndDedup(struct ListNode* l1, struct ListNode* l2) { struct ListNode dummy; dummy.next = NULL; struct ListNode *tail = &dummy; while (l1 && l2) { if (l1->val < l2->val) { if (tail->next == NULL || tail->val != l1->val) { tail->next = l1; tail = l1; } l1 = l1->next; } else if (l1->val > l2->val) { if (tail->next == NULL || tail->val != l2->val) { tail->next = l2; tail = l2; } l2 = l2->next; } else { // 相等时只保留一个,两个指针都向后移动 if (tail->next == NULL || tail->val != l1->val) { tail->next = l1; tail = l1; } l1 = l1->next; l2 = l2->next; } } // 处理剩余节点 struct ListNode *rest = l1 ? l1 : l2; while (rest) { if (tail->next == NULL || tail->val != rest->val) { tail->next = rest; tail = rest; } rest = rest->next; } tail->next = NULL; return dummy.next; }这段代码里有几个容易忽视的细节。第一是去重逻辑:在把某个节点接入结果链之前,要先判断tail->val是否和当前节点值相同,相同就跳过;第二是在相等去重时,两个链表的指针都要向后移动,否则会死循环;第三是最后要把tail->next置为 NULL,避免把原链表中被跳过的节点又带回来。
这种题的考察重点其实是“链表的边界处理能力”:虚拟头节点可以避免对空链表和头节点的特殊判断,而空指针判断和指针移动的先后顺序,则是链表类题目的核心。
3.3 编程题第三题:带权最短路径问题(拉开差距题)
第三题是一道图论题,要求在给定网格中寻找从起点到终点的带权最短路径。没有说明数据范围,但从题目描述推测,网格边长可能在 1000 级别。显然,朴素的 DFS 会超时,需要用 Dijkstra 或者带优先级队列的 BFS。
由于是在线笔试环境,我优先选择了 Dijkstra + 最小堆实现,用priority_queue每次取出当前距离最小的节点进行松弛。如果题目保证所有边权相同,那直接普通 BFS 即可,但 2020 这套题在路径上设了不同的消耗值,所以必须上 Dijkstra。
核心代码如下:
#include <vector> #include <queue> #include <climits> using namespace std; int shortestPath(vector<vector<int>>& grid) { int n = grid.size(), m = grid[0].size(); vector<vector<int>> dist(n, vector<int>(m, INT_MAX)); priority_queue<pair<int, pair<int, int>>, vector<pair<int, pair<int, int>>>, greater<>> pq; dist[0][0] = grid[0][0]; pq.push({dist[0][0], {0, 0}}); int dx[4] = {1, -1, 0, 0}; int dy[4] = {0, 0, 1, -1}; while (!pq.empty()) { auto [d, pos] = pq.top(); pq.pop(); int x = pos.first, y = pos.second; if (d > dist[x][y]) continue; for (int i = 0; i < 4; i++) { int nx = x + dx[i], ny = y + dy[i]; if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue; int nd = d + grid[nx][ny]; if (nd < dist[nx][ny]) { dist[nx][ny] = nd; pq.push({nd, {nx, ny}}); } } } return dist[n-1][m-1]; }这题最大的坑在于很多人拿到题后直接写 BFS,但没有看到路径权值不一致这个关键细节。如果你交上去才发现用错算法,重新写一份完整 Dijkstra 的时间成本非常高。所以拿到编程题之后,一定要先花 30 秒确认输入数据的特性:是否有负权边、边权是否一致、数据范围多大,再决定用什么算法。
我这次在第三题上踩的一个坑是priority_queue默认是大顶堆,必须手动传入greater<>把它改成小顶堆。这是一个非常典型的 C++ 使用细节,出错的瞬间会在考试压力下放大成十几分钟的排查时间。
4. 笔试中的经典陷阱与避坑指南
4.1 指针与内存管理的三个“连环坑”
在选择题和编程题里,指针相关的内容至少出现了 5 次。我总结出三个高频连环坑,第一是栈区返回局部变量地址:
char* getString() { char str[] = "hello"; return str; }str是函数内的局部数组,存储在栈上,函数返回后该内存已经被回收,主调方再去解引用就属于未定义行为。轻则输出随机值,重则崩溃。如果真想返回字符串,应该用static char str[]或malloc分配堆内存。
第二是内存泄漏:malloc之后忘记free,尤其在循环里多次分配内存但不释放,笔试题目会把这个场景包装成“以下代码有什么问题”。2020 年这套选择题里就有一道题目写的malloc后直接return,问是否会导致内存泄漏——答案是肯定的,而且即使程序退出后操作系统会回收内存,但在长时间运行的服务里这就是致命的。
第三是sizeof 与字符串长度混淆:前面已经说过,sizeof(arr)和strlen(arr)的值往往不一致。很多同学在写程序时,用sizeof(str)作为strcpy的长度参数,导致字符串拷贝越界。实际上strcpy会自动处理'\0',你需要保证的是目标缓冲区足够大,而不是手动传长度。
4.2 编译链接过程中容易被忽略的细节
网易这套笔试里有一道关于static关键字的题,选项涉及 static 修饰局部变量、全局变量和函数的不同效果。这里有个容易混淆的点:static修饰局部变量时,它的生命周期变为整个程序运行期,但作用域仍然只在函数内部;static修饰全局变量时,它的作用域被限制在当前源文件内,外部文件无法通过 extern 引用。
还有一个关于const的题,问const char *p和char * const p的区别。前者是“指向常量的指针”,指针本身可以修改,但不能通过它修改指向的值;后者是“指针常量”,指针本身不能修改,但指向的值可以通过它修改。这个经常有人搞反,可以这样记:const修饰的是它右边紧挨着的类型,const char *p中const修饰char,而char * const p中const修饰p本身。
4.3 在线笔试环境下的时间管理与提交策略
在线笔试和平时在自己电脑上写代码有很大区别:不能本地调试、不能随时查文档、编译错误会直接扣分。我这次踩过的一个直接教训是——编程题的输入输出格式没有百分百按示例格式排查,导致第一题白白浪费了一次提交机会。
在线笔试平台的判题逻辑通常是多个隐藏测试用例,每一个用例都严格比对输出。所以提交前一定要检查:末尾是否有多余空格或换行、输出大小写是否完全一致、浮点数精度是否保留到指定位数。建议在本地测试时,手动构造几组特殊输入,比如空字符串、只有一个字符、最大长度、包含空格和特殊字符的串。
时间分配上,我的建议是“选做结合”:选择题 50 分钟内必须完成,无论会不会都先选一个答案,不要空着;碰到多选题拿不准时,优先选择你最有把握的选项,并遵循“漏选得部分分,错选不得分”的规则,尽量保证稳拿分;编程题按照“第一题 15 分钟、第二题 25 分钟、第三题 30 分钟”的节奏去卡时间,如果第三题超过 30 分钟还没有清晰思路,果断回来检查前两题的边界。
5. 复盘总结与备考建议
5.1 面向大厂 C/C++ 岗位的复习路线
如果你准备的是网易、腾讯、阿里、字节这类大厂的 C/C++ 开发岗笔试,我的建议是复习路线分三步走。第一步是语言基础扫盲,重点吃透指针、数组、结构体、内存布局、堆与栈的区别、static/const/extern关键字的语义、位运算、宏定义与内联函数的区别。推荐把《C 程序设计语言》中关于指针的章节重读一遍,配合做课后题。
第二步是数据结构与算法专项,核心覆盖数组、链表、栈、队列、哈希表、二叉树、堆、图的最短路径和最小生成树、排序与查找、回溯算法。不必追求极端难题,但经典题目要能做到“闭眼默写”——链表反转、合并有序链表、二叉树遍历(递归+迭代)、快速排序/堆排序、Dijkstra、二分查找的边界处理,这些是笔试编程题的高频素材。
第三步是系统知识串讲,包括操作系统(进程线程、同步互斥、死锁、虚拟内存、页面置换)、计算机网络(TCP/UDP、三次握手/四次挥手、HTTP/HTTPS、DNS 流程)、Linux 基础(文件权限、进程管理命令、管道与重定向、常用文本处理命令)。这部分以选择题为主,复习时多做笔记反复记忆。
5.2 考前一周的“冲刺清单”
考前一周不建议再学新知识,而是做三件事:第一是刷近两年的真题,牛客网上有大量大厂笔试原题,每套题都按真实考试时间限时完成,训练答题节奏;第二是整理错题本,把易错的知识点、没看清的题目条件、没想明白的边界情况记录到一个文档里,每天早中晚各快速翻一遍;第三是模拟编程题环境,在牛客或 LeetCode 上做 3-5 道中等难度的题,提交过程严格按照“读题→设计算法→写码→构造测试用例→提交”的顺序完成。
还有一个细节:在线笔试前一定检查本地网络和浏览器兼容性,提前 30 分钟进入候考页面。2020 年那次笔试,我所在的考场群里有同学因为浏览器拦截了弹窗,导致编译器无法弹出,白白浪费了 10 分钟考试时间。
5.3 笔试通过后的面试衔接准备
笔试通过只是第一步,面试环节对 C 语言的考察会更加细化。以我后续的面试经验来看,网易的技术面一般会追问:你笔试里某道编程题的时间复杂度和空间复杂度是多少?能不能优化?某个内存泄漏场景如何排查?进程和线程在实际应用中的取舍是什么?所以笔试结束后不要马上松懈,而要把每一道错题都当成深入复习的入口。
我的个人习惯是:笔试结束后两小时内趁着记忆还新鲜,把整个考试遇到的题目、自己的解法、卡壳的位置全部复盘写进笔记。这份笔记会在后续面试复习中反复用到,远比考后两周再回忆来得完整。
5.4 关于心态与长期积累的一点体会
回到这套笔试本身,我最大的感悟是:网易 2020 校招 C 开发工程师正式批的题目并没有追求“难倒所有人”,而是在筛选那些真正写过代码、踩过坑、对底层机制有好奇心的人。选择题里的内存问题、编程题里的链表边界处理,本质上都是工程中经常遇到的真实场景。
我始终觉得,刷题是必要的,但刷题之后一定要回归到“理解原理”上来。为什么数组名在大部分表达式中会退化成指针?为什么Malloc的内存必须free?为什么 Dijkstra 要求边权非负?这些问题想明白了,笔试面试自然游刃有余。
最后分享一个小技巧:从这次笔试开始,我养成了用“费曼学习法”复盘每一道错题的习惯——每道错题都先假装自己是一个老师,要把它讲给完全不会的人听。讲到卡壳的地方,就是知识盲区,回头查资料再讲一遍。这个方法看起来慢,但对打牢 C 语言底层功底特别有效,后续在笔试和面试中都帮我拿下了不少关键分数。希望这篇复盘能帮正在备战的你少踩几个坑,也欢迎大家考完回来交流真题变体。