爱奇艺2020校招C方向笔试题考点解析与实战技巧
2026/8/31 5:26:50 网站建设 项目流程

聊到“爱奇艺2020校招C方向笔试题(第二场)”,很多同学的第一反应是找原题、背答案。这个心情我能理解,但方向真不太对。考试前一天背十道题,不如花一晚上弄清楚这套题背后的考察逻辑——因为大厂C方向笔试筛选的从来不是“你背过什么”,而是“你有没有用C解决过真实问题的底子”。这篇文章不打算逐题念答案,而是把这套题拆开揉碎:为什么考指针、为什么考字符串、为什么最后还有一道文件IO的场景题,以及你在考场上最容易忽略的细节都在哪里。适合正在准备校招笔试的同学,也适合那些C语言基础不牢、想系统自查的人。

1. 岗位画像决定考点:先看懂这份试卷在筛什么人

1.1 从试卷结构看:广度筛选和深度筛选是两回事

爱奇艺这类视频公司的C方向笔试,几乎都长一个骨架:单选题、多选题、填空题,最后跟一两道手写编程题。这个结构本身就有讲究,它不是随便拼的。

选择题的量一般不小,二十道上下,覆盖指针、运算符、内存分配、宏定义、结构体、字符串函数。这部分的目标是快速检验“基础广度”。换句话说,面试官想把那些只会照着教程抄代码、但一换到语法细节就懵的人筛掉。多选题更狠,它考的是“概念辨析”,比如“以下哪些写法会导致未定义行为”,少选扣分、错选扣分,想靠蒙混过去很难。

填空题通常给出一段简短的代码,让你补出关键语句,或者直接问你某行代码的输出结果。这类题考的是“对代码执行过程的精确推演”,不是“大概知道怎么回事”。举个例子,sizeofstrlen的区别,很多人嘴上会说,但真拿到一个数组名和一个指针变量,让他写出各自结果,十个里有四五个会错。这种题就是要把“感觉懂了”的人揪出来。

而最后的编程题,才是深度筛选。它要看的不是你会不会写循环,而是你有没有算法思维、能不能在限定时间内写出健壮可跑的代码、会不会分析复杂度。一场笔试下来,广度过关的人可能有一半,深度过关的往往剩下不到两成。所以你要明白:选择题决定了你能不能进下一轮,编程题决定了面试官愿不愿意跟你聊下去。

1.2 视频公司C方向的实际业务场景决定考点倾向

同样是C方向笔试,做嵌入式、做数据库内核、做视频服务,考题的出法完全不同。爱奇艺这类公司的C方向岗位,围绕的是播放内核、CDN边缘节点、音视频转码服务、基础网络组件这些方向。这类业务的共同点是:对性能极其敏感,对内存和IO的掌控要求极高,而且一旦出问题,排查成本很高。

这就解释了为什么试卷里指针和内存管理的比例那么大。一个播放器内核要处理的是来自网络的数据块、解码器输出的帧缓冲区、渲染线程共享的共享内存。谁分配、谁释放、怎么避免拷贝、怎么防止野指针,这些不是八股文,而是每天都要面对的真实问题。所以笔试里的指针题,本质上是在提前模拟岗位的日常。

另外,文件读写和网络字节序这类题也常出现。原因很简单,大量视频文件是二进制格式,转码服务要反复读写文件头、帧数据,网络传输过程里还要做字节序转换。一个人在笔试里连fread的返回值都不检查,你很难相信他能在生产环境里写出可靠的文件解析代码。把岗位需求对应到考点上,整份试卷的脉络就清楚了。

2. 指针与内存管理的连环坑:选择题才是真正的主战场

2.1 数组退化与sizeof:一道题看清C语言函数传参的本质

C方向笔试里,数组和指针的关系几乎是必考项。很多选择题表面在问sizeof,实际上考的是“数组名在函数参数里发生了什么”。这道题我觉得是最典型的一种:

#include <stdio.h> void test(char arr[]) { printf("in function: %zu\n", sizeof(arr)); } int main(void) { char buf[64] = {0}; printf("in main: %zu\n", sizeof(buf)); test(buf); return 0; }

在64位平台上,答案是648buf是真正的数组,sizeof(buf)返回整个数组占用的字节数;但一旦把它传给函数,数组名就“退化”成指向首元素的指针,sizeof(arr)只能得到指针本身的大小。我见过太多同学在这里栽跟头,不是不懂,而是做题太快,根本没注意到形参写的是数组形式。这个知识点的本质是:C语言函数传参永远是值传递,数组不可能整体传进去,传的只是地址。

为了应付这类题,你应该形成条件反射——看到sizeof,先问自己:对象是数组还是指针?如果是函数形参,不管它声明成arr[]还是*arr,一律按指针处理。这个习惯养成之后,类似的题再出现就不会错。

2.2 未定义行为与运算符优先级:遇到就别硬算

有一类选择题是专门“钓鱼”的。比如给你这样的代码:

int i = 0; i = i++ + ++i; printf("%d\n", i);

然后选项里列了一堆结果:1、2、3、4。很多同学真就在草稿纸上吭哧吭哧地推演,试图算出一个“合理”的答案。但我告诉你,这道题的正确选项应该是“未定义行为”,而不是任何一个数字。i++++i在同一个表达式里多次修改同一个变量,中间没有序列点,标准规定这就是未定义行为,编译器想给你什么结果都合法。

类似的还有逗号表达式和运算符优先级混在一起的题:

int a = 1, b = 2; int c = (a++, b += a, a + b);

这道就不是UB了。逗号表达式从左到右依次求值,整个表达式的值是最后一个逗号后面的结果。先算a++a变为2;再算b += ab变为4;最后a + b得6,所以c是6。这类题的考点是运算符优先级和求值顺序。我的经验是:考试时遇到++--混在复杂表达式里的题目,别试图硬算,先判断有没有未定义行为,如果有,直接选UB选项;如果没有,再按优先级一步步拆。

这个习惯不仅仅是应试技巧。真实工程里,代码规范明确规定禁止在表达式中对同一变量多次自增自减,就是为了避免这种不可移植的行为。笔试出这种题,其实是在暗示你:我们团队不欢迎写这种代码的人。

2.3 内存分配、释放与对齐:从选择题延伸到实战

指针和内存的考点,除了数组退化和UB,还有一块重头戏——动态内存。

char *p = (char *)malloc(100); free(p); // 此时 p 是野指针

free之后有没有把指针置为NULL,这个问题在选择题里以各种形式出现:有些题干说“free之后再free一次会发生什么”,有些说“free之后使用这块内存会发生什么”。正确答案是:这些都是未定义行为,可能在部分平台上“看起来没事”,但绝不能依赖这种运气。实际项目里,我见过因为free之后没有置NULL,导致同一块内存被释放两次、堆结构被破坏、程序在完全无关的地方崩溃的案例,排查起来极其痛苦。

再有一个高频考点是结构体对齐:

#include <stdio.h> struct A { char a; int b; char c; }; struct B { char a; char c; int b; }; int main(void) { printf("A: %zu\n", sizeof(struct A)); printf("B: %zu\n", sizeof(struct B)); return 0; }

默认4字节对齐下,第一个输出12,第二个输出8。原因很简单:结构体成员顺序不同,编译器填充的空隙就不同。把小的成员排在一起能省内存。这个知识点看起来是“填空级别”的简单题,但它直接关系到网络协议的数据封包、文件格式的解析结构体。如果你写了一个#pragma pack(push, 1),那对齐规则又会变。考试里遇到这类题,一定要先看清有没有pack关键字,再决定对齐策略,别上来就按默认规则答。

3. 字符串处理大题:边界条件比算法本身更值钱

3.1 字符串逆序的标准解与变体:双指针为什么是首选

编程题里,字符串相关题目出现频率极高,“字符串逆序输出”更是家常便饭。但很多人以为这题就三行代码的事儿,写出来却拿不到满分,原因在于边界条件处理得不够干净。我给出一个标准实现:

void reverse(char *s) { if (!s || *s == '\0') return; char *left = s; char *right = s + strlen(s) - 1; while (left < right) { char tmp = *left; *left++ = *right; *right-- = tmp; } }

这段代码里,第一行if (!s || *s == '\0')就处理了两个边界:空指针和空字符串。很多同学不写这个防御,直接取strlen(s) - 1,遇到空字符串时strlen返回0,再减1就成了-1,right指向字符串地址之前的位置,这已经是越界了。另外一个值得注意的细节是strlen的调用,它在O(n)时间内扫一遍字符串,所以整体逆序是O(n)。如果你在循环里反复调用strlen,复杂度会退化到O(n^2),这种问题面试官一眼就能看出来。

笔试里还常在这个基础上加变体,比如“按单词逆序:给定'hello world',输出'world hello'”。解法是两轮逆序:先整体逆序变成'dlrow olleh',再对每个单词单独逆序。这种题目考的是你能不能把一个复杂问题拆成两个简单步骤,而不是硬写一个复杂的循环。回答的时候,一定要在注释里先写思路,再写代码。

3.2 手写字符串函数的几个隐藏考点

字符串函数的手写题,比如手写strcpystrcatstrcmpatoi,也是笔试的老朋友。先说strcpy,经典版本:

char *my_strcpy(char *dest, const char *src) { if (!dest || !src) return NULL; char *ret = dest; while (*dest++ = *src++) ; return ret; }

这里有几个考点:第一,返回值必须是目的地址的原始值,很多同学没保存dest就直接返回,结果返回了尾部的空字符;第二,src必须用const修饰,表达“源字符串不会被修改”的语义;第三,要注意destsrc不能重叠,如果源和目标指向同一块内存且发生重叠,需要用memmove而不是strcpy。这些都是选择题的高频干扰项,也是实际工程里会踩的坑。

再比如手写atoi,这个题就更有意思了。要处理的边界条件包括:字符串前后的空格、正负号、出现非数字字符时的处理、整数溢出怎么办。我在复习的时候给自己列过一张检查表:

边界场景处理方式
空字符串返回0
前导空格跳过
正负号记录符号
非法字符停止解析并返回已解析部分
溢出返回INT_MAX或INT_MIN

这张表里的每一条,在笔试题里都可能被拆成一个单独的选择题来考。比如“字符串' -123abc'用标准atoi解析结果是?”,答案是-123,因为解析到a就停止了。你要是没看过标准库的这个行为,很容易想当然地认为返回0。这些细节就是拉开分数差距的地方。

3.3 代码补全题的实战策略

填空题里有一种常见形式:给出一段有残缺的代码,让你补上关键行。比如一个字符串去重函数,中间留了两行空;或者一个计算字符串长度的函数,用递归写法,让你补递归终止条件。这类题的答题策略,我觉得有三条。

第一条,先通读上下文,确认缺失部分的职责。缺的是终止条件、赋值语句还是指针移动,从整体逻辑判断,不要盯着局部硬想。第二条,注意类型匹配。填进去的语句要和前后变量的类型能对上,比如该填*s还是s,一眼看不出来的时候,看它参与运算的类型。第三条,用手头例子做快速验证。在草稿纸上用"hello"走一遍逻辑,比自己空想要靠谱得多。这三条用熟了,代码补全题基本能稳拿。

4. 手写链表与排序算法:阅卷人会看的得分点

4.1 链表反转的两种写法:从考卷到面试都要过关

链表题在笔试编程题里的地位,跟指针在选择题里的地位差不多——都是必考的。其中“反转链表”出场率最高。我见过太多同学被这道题考倒,不是因为他们不会写,而是因为思路停留在“画图理解”,一到手写就乱。这里给出迭代版本,我认为这是考场上最稳妥的写法:

struct ListNode { int val; struct ListNode *next; }; struct ListNode *reverseList(struct ListNode *head) { struct ListNode *prev = NULL; struct ListNode *curr = head; while (curr) { struct ListNode *next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; }

关键点在于必须先保存curr->next,否则一旦把curr->next指向prev,原本的下一个节点就找不到了。这个代码我建议每个准备笔试的人都背到肌肉记忆。不过光背还不够,你最好也掌握递归版本,因为很多面试官会在笔试通过后的技术面里问“能不能用递归实现”,想看看你是不是真的理解这个结构。

递归版的核心理解方式是:假设当前节点后面的链表已经反转好了,只需要把当前节点放到最后面。但需要注意,递归深度等于链表长度,万一链表特别长,可能会导致栈溢出,这是它的天然劣势。笔试中如果题目没有特别要求,我推荐写迭代版:效率更高、不容易栈溢出、代码也好理解,面试官看着也舒服。

4.2 快排与TopK:时间复杂度的表达比默写模板更重要

排序算法里,手写快排是另一个高频编程题,因为它的平均复杂度是O(n log n),而且不用额外空间(其实是递归栈空间,这里不较真)。我自己的手写模板是这样的:

void quick_sort(int *arr, int left, int right) { if (left >= right) return; int i = left, j = right, pivot = arr[left]; while (i < j) { while (i < j && arr[j] >= pivot) j--; if (i < j) arr[i++] = arr[j]; while (i < j && arr[i] <= pivot) i++; if (i < j) arr[j--] = arr[i]; } arr[i] = pivot; quick_sort(arr, left, i - 1); quick_sort(arr, i + 1, right); }

写这个题的时候,阅卷人最看重的是这么几件事:递归终止条件写没写、等号怎么处理(用>=<=跳过等于基准值的元素,可以避免极端情况下的死循环)、最后基准值有没有放到正确位置。还有一个必须写清楚的点是复杂度分析——平均O(n log n),最坏O(n^2)(当数组已经有序且每次基准值都取最小/最大元素时)。考试时如果你在注释里把复杂度写明白,这题就稳了一大截。

和排序紧密相关的一类场景题是TopK:“在海量数据里找最大的K个数”。高效做法是用一个大小为K的小顶堆,遍历一遍数据,每次遇到比堆顶大的元素就替换并调整堆,最后堆里就是最大的K个。复杂度是O(n log K)。笔试里如果遇到这种题,你只写“先排序再取前K个”能拿一半分,但如果能把堆的思路写出来,再补上空间复杂度,基本就是满分答案了。

4.3 编程题的“隐性格式分”

很多人不知道,编程题是存在“隐性格式分”的。阅卷人不只看最终正确性,也会看你的代码长什么样。几条我认为很重要的细节:

  • 变量命名要有语义。用ij做循环变量没问题,但链表题里用currprevnext,比用pqr清晰得多。
  • 一定要在关键步骤写注释。哪怕只是“// 保存next,防止断链”这种说明性注释,也能让阅卷人确认你理解这行代码的作用,而不是瞎碰出来的。
  • 防御性判断要保留。处理空指针、空字符串的代码要写出来,即便题目没说需要,这也是加分项。
  • 写在最后的是复杂度分析,一定要写。这个习惯在准备阶段就要养成,而不是考场临时发挥。

很多同学平时在IDE里写代码靠自动补全,缩进还靠格式化工具,一到手写就原形毕露。所以我建议你平时练习手写代码的时候,就刻意不看IDE提示,每道题都从头写到尾,模拟考场的真实状态。

5. 文件读写与底层IO:视频业务场景下的C语言延伸考点

5.1 从fopen到mmap:文件操作题的规范过程

爱奇艺这类视频公司的C方向笔试,很喜欢在最后放一道和文件读写相关的综合题。这跟业务强相关:视频转码、切片、CDN缓存回源,全都涉及大规模文件处理。文件读写的“标准答案”其实有固定套路,我以读取整个文件到内存为例:

FILE *fp = fopen("data.bin", "rb"); if (!fp) { perror("fopen"); return -1; } fseek(fp, 0, SEEK_END); long size = ftell(fp); rewind(fp); char *buf = (char *)malloc(size); if (!buf) { fclose(fp); return -1; } size_t n = fread(buf, 1, size, fp); if (n != (size_t)size) { // 处理短读 } free(buf); fclose(fp);

这个看似简单的过程里全是考点。第一,fopen之后必须判空,很多同学把这个跳过。第二,fread的返回值必须检查,因为在磁盘IO出错、文件被截断等情况下,实际读到的字节数可能小于请求的字节数。第三,malloc失败要处理,不能直接拿来就用。第四,用完的fpbuf要释放。这四条有一条没写,阅卷人就会觉得你“没有生产级代码意识”。

笔试里还可能考mmap和标准IO的对比。mmap可以把文件直接映射到进程地址空间,避免用户态到内核态的数据拷贝,读大文件时性能好很多;但它的代码写起来更复杂,还要处理映射失败、页对齐等问题。我在实际项目中处理几百MB的视频切片文件时,会优先考虑mmap;但如果笔试题目没有明确要求,用标准fopen+fread是更安全的回答,不容易被挑毛病。

5.2 那些不直接考文件、但和IO底层绑定的题

文件读写经常连带考另外两类底层知识,哪怕题目表面上只是一道“网络编程题”或“数据处理题”。

第一类是字节序。视频文件里的很多字段是多字节整数,比如一个4字节的uint32数值,在存储顺序上有大端和小端之分。C语言笔试里可能会问:网络字节序是大端还是小端?如何判断当前机器是大端还是小端?后者常用的回答是:取一个整数、取它首字节地址、判断存储的是高字节还是低字节。这类题考的实际上是“你有没有被跨平台数据交换坑过”。

第二类是整型溢出。比如有一个二分查找的经典题,很多人写int mid = (left + right) / 2,但left + right可能溢出。正确写法是int mid = left + (right - left) / 2。文件处理场景里也类似,当你拼接文件路径、计算偏移量时,如果用的是32位整数去存一个大于2GB的偏移量,就会得到负数。笔试考这些,不是因为面试官想刁难你,而是这些坑在视频处理业务里真实发生过无数次。

5.3 一个综合场景题的完整作答示范

假设笔试最后一道题是:“给定一个大文本日志文件,每行格式是时间戳 错误码 描述,统计每个错误码出现的次数,按次数降序输出。”这道题综合了文件读写、字符串解析、排序,非常适合用C实现。

我的作答思路是这样的:

#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_LINE 1024 #define MAX_ITEMS 10000 typedef struct { int code; int count; } Item; int cmp(const void *a, const void *b) { return ((Item *)b)->count - ((Item *)a)->count; } int main(void) { FILE *fp = fopen("app.log", "r"); if (!fp) { perror("fopen"); return -1; } Item items[MAX_ITEMS]; int n = 0; char line[MAX_LINE]; while (fgets(line, sizeof(line), fp)) { int code = 0; // 解析行,例如时间戳+空格+错误码 if (sscanf(line, "%*d %d", &code) != 1) continue; int found = -1; for (int i = 0; i < n; i++) { if (items[i].code == code) { found = i; break; } } if (found >= 0) { items[found].count++; } else if (n < MAX_ITEMS) { items[n].code = code; items[n].count = 1; n++; } } fclose(fp); qsort(items, n, sizeof(Item), cmp); for (int i = 0; i < n; i++) { printf("%d %d\n", items[i].code, items[i].count); } return 0; }

这份代码里我用fgets按行读取,避免了一次性加载整个文件的压力;用sscanf解析错误码,并且检查了返回值;最后用qsort排序。这些问题如果在笔试里让我挑毛病,我会说它在线性查找上太慢——如果错误码种类很多,可以用哈希表优化。但在笔试时间紧张的情况下,先写出一个正确、清晰的O(n*m)版本,再在注释里补充“可用哈希表优化到O(n)”,是性价比最高的策略,比闷头写一个哈希表然后半天调不出来要强得多。

6. 复盘几个考场上最容易翻车的瞬间

6.1 选择题耗时过多,编程题来不及做

我见过太多同学在两道有争议的选择题上死磕十分钟,最后编程题只剩十来分钟,手忙脚乱写不完。这是典型的考场策略失误。选择题再难,分值也就一两分;编程题一道就可能占二三十分。正确的时间分配,我建议是:先花两分钟浏览全卷,把编程题留出至少一半的时长。遇到拿不准的选择题,先标记,最后有时间再回头想;编程题先写主体框架,哪怕不考虑边界情况,也比留白强得多。

6.2 本地能跑、提交编译失败

笔试平台和本地环境的差异,是每年校招都有人踩的坑。最常见的问题有三个:第一,本地用的编译器默认是C++,但要求提交C代码,于是忘了C和C++在强制转换、空指针上的细微差异;第二,本地用了#include <bits/stdc++.h>这种非标准头文件,平台编译器直接报错;第三,变量名用了关键字或者是平台保留字,比如你声明一个register变量。我自己的习惯是:提交前在脑子里过一遍代码,确认所有头文件都是标准C库里的(比如stdio.hstdlib.hstring.h),确认没有使用任何平台相关的扩展。

6.3 人肉模拟执行:画内存图,别心算

编程题里有一类“写出以下代码运行结果”的输出题,很多同学直接在脑子里快速过一遍就写答案,错误率很高。我的建议是:涉及指针、数组、结构体时,一定要在草稿纸上把内存图画出来。每个变量占哪块空间、指针指向哪里、赋值之后指向有没有变化,一画就对,全靠心算十有八九要错。这跟调试线上问题的思路一模一样——空想不如看现场,手绘内存图能帮你还原代码执行的真实状态。

还有一个小细节,考前务必确认你笔试平台对C语言的标准支持。是用C89还是C99还是C11,直接影响你能不能在中途声明变量。有些在线平台默认开C99,有些则严格按C89编译,循环里声明int i都可能报错。这属于环境问题,提前摸清楚能省很多不必要的麻烦。

最后分享一点我的个人体会

刷了这么多遍C方向笔试题,我自己最大的体会是:笔试不是期末考,它不靠突击,靠平时的积累和正确的方法。C语言的好—难—坑,其实是一体的——你越熟悉指针,越能看懂内存管理;越理解内存管理,越明白为什么字符串处理要写防御代码。如果你离笔试还有两三个月,我建议把《C程序设计语言》里的习题认真过一遍,再配合在线题库每天手写两三道算法题,保持手感。笔试前一周开始刷往年真题,重点不是背答案,而是总结考点分布和你的薄弱环节,有针对性地补。最后再提醒一句:考场上稳最重要,会做的题拿满分,没把握的题拿步骤分,别让任何一道选择题拖垮整张试卷的节奏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询