
秋招笔试拼的不只是刷题量而是这几种底层能力又到了秋招季后台不少读者在问C语言方向笔试题的事。翻出去年爱奇艺2019秋招C方向笔试题A说实话这套题的难度定位很有意思不算特别难但覆盖面极广语法细节、内存模型、算法思想、代码阅读能力全都考到了。不像很多公司只盯着LeetCode刷题量这套题更看重你对C语言本身的理解深度。我当年做这套题的时候印象最深的是它不考偏题怪题所有的考点都是日常开发一定会碰到的字符串处理、指针操作、内存管理、常见排序、链表和简单图论。但就是这些“基础题”反而能拉开差距——因为越基础的题越考验你写代码的习惯和底层认知是否扎实。这篇就当一次复盘把这份真题里的核心考点逐个拆开讲清楚每道题背后的设计意图、标准解法和那些“官方答案不会告诉你”的坑。不管你是正在备战秋招的应届生还是想夯实C语言功底的在职开发这篇都值得认真读一遍。1. 这套C方向笔试题的整体设计与考核思路1.1 从题目结构看爱奇艺的C岗位人才画像爱奇艺的C方向岗位主要集中在音视频处理、推荐系统后端、基础中间件开发这几个业务线上。这些场景对C语言的要求非常明确内存要精准控制性能要压榨到极致代码要能在高并发环境下稳定跑几个月不出问题。所以你看这套笔试题的考点分布基本就是这四块C语言语法基础指针、数组、结构体、预处理器内存管理malloc/free、内存泄漏、野指针数据结构与算法字符串、链表、排序、图的最短路径代码阅读与调试能力给你一段代码让你找错误或者推输出这里有一个很多同学容易忽略的点爱奇艺这类公司的笔试题虽然看起来是考知识点但本质上考的是“你在实际工程中会不会踩这些坑”。比如考你sizeof和strlen的区别不是因为你面试时要手写这两个函数而是因为在音视频缓冲区的处理中搞错这两个值会导致缓冲区溢出这可是线上事故级别的bug。1.2 题型分布与时间分配的实战建议根据A卷的结构来看选择题约占40%填空题约占20%手写代码题约占40%。这个比例对于C方向岗位来说非常典型。我的建议是时间分配这样安排选择题和填空题控制在25分钟以内会就是会不会先标记跳过手写代码题留足40分钟以上因为这类题不仅看结果还看你的代码风格和边界处理是否完善最后留5-10分钟检查重点回看指针相关的题目因为指针题往往是翻车重灾区我在实际作答时还有个习惯拿到卷子先花2分钟把所有题扫一遍大致判断难度分布。因为C方向笔试题通常会有1-2道压轴题比如迪杰斯特拉算法实现这类提前预判能帮你合理分配精力而不是在中间的某道题上死磕到时间耗尽。2. 代码阅读与输出题这些细节决定了你能不能拿满分2.1 字符串逆序看似简单但至少有4种考法字符串逆序输出是C方向笔试的常青树也是热词榜上的高频考点。这道题虽然基础但爱奇艺这类公司会从多个维度换着花样考。先看最经典的考法——手写一个字符串逆序函数void reverse(char *str) { if (str NULL) { return; } int len strlen(str); int left 0; int right len - 1; while (left right) { char tmp str[left]; str[left] str[right]; str[right] tmp; left; right--; } }这个实现是双指针交换的思路时间复杂度O(n)空间复杂度O(1)也是标准答案。但笔试真正想考察的点在于需要注意strlen的调用是在函数开头就完成的。有些同学会在循环体内反复调用strlen白白增加O(n²)的时间复杂度。这虽然不影响结果正确性但会暴露你写代码时对性能不够敏感——在爱奇艺做音视频处理这种性能浪费是不可接受的。第二类考法是绕着弯考。比如给你一句英文要求单词内部逆序但单词顺序不变即“I love you”变成“I evol uoy”。这种题就从纯考字符串变成了考你“两次逆序”的思维void reverseWords(char *s) { int len strlen(s); reverse(s, 0, len - 1); // 先整体逆序 int start 0; for (int i 0; i len; i) { if (s[i] || s[i] \0) { reverse(s, start, i - 1); // 再对每个单词逆序 start i 1; } } }这个思路非常经典两次逆序得到目标结果。笔试中如果遇到类似变形题往这个方向想基本没错。第三类考法是让你分析已有的逆序代码输出结果这种题目考的是细心。比如代码里用了一个临时数组从尾部拷贝但没在末尾加\0输出结果就会多出一堆乱码。这种题的坑点在于很多同学一看是逆序就直接在脑子里跑了一遍“正常版”逻辑根本没注意到拷贝后少了字符串结束符。第四类考法最有迷惑性要求不申请额外空间原地逆序。如果你上来就定义了一个新数组那就直接扣分了。这种题目的考察意图是看你对“原地算法”的理解在内存受限的嵌入式场景这是一项核心能力。2.2 指针与数组爱奇艺超爱考的sizeof和strlen区别在选择题和填空题部分出现频率最高的就是sizeof和strlen的组合题。这道题看似基础但每次都能筛掉一大批人。来看一道典型原题char *str hello; char arr[] hello; char *p arr; printf(%lu %lu\n, sizeof(str), strlen(str)); // 8 564位系统 printf(%lu %lu\n, sizeof(arr), strlen(arr)); // 6 5 printf(%lu %lu\n, sizeof(p), strlen(p)); // 8 5sizeof是编译期运算符它计算的是变量本身占用的内存大小。strlen是运行时函数它计算的是从指针位置开始到第一个\0之前的字符个数。所以str是指针占8个字节64位系统所以sizeof(str)为8arr是数组数组长度包括末尾的\0所以是6个字节p是指针同样占8个字节这里有个容易混淆的点在函数参数传递时数组会退化为指针void func(char arr[]) { printf(%lu\n, sizeof(arr)); // 永远输出8而不是数组长度 }这个“数组退化”特性在实际开发中经常引发问题。爱奇艺笔试特别爱考这个点因为音视频项目里大量使用缓冲区数组如果在新手代码里用sizeof去取数组长度拿到的是指针大小而不是缓冲区实际大小轻则数据截断重则缓冲区溢出。关于strlen还有另一个隐藏考点考察你是否了解它的实现。标准库的strlen实现其实不是逐字节扫描那么简单很多版本的glibc会一次读取4个或8个字节通过位运算判断是否存在\0这在处理大字符串时性能提升非常明显。笔试题如果让你优化一个统计字符串长度的函数往这个方向答会非常加分。2.3 结构体对齐填空和选择都爱出的高频原题C方向笔试的结构体题十有八九会涉及内存对齐。爱奇艺这套题里也出了一道。typedef struct { char a; // 1字节 int b; // 4字节 char c; // 1字节 } Test; printf(%lu\n, sizeof(Test)); // 输出多少正确答案是12而不是6。因为编译器按照默认对齐规则会把这些成员排列成这样a占偏移0b需要4字节对齐所以从偏移4开始偏移1-3是填充c占偏移8结构体总大小需要是最大对齐数的整数倍这里最大对齐数是49不是4的倍数所以补到12如果换成下面这个顺序typedef struct { char a; char c; int b; } Test2; printf(%lu\n, sizeof(Test2)); // 输出8同样是三个成员换一下顺序就从12字节变成8字节了。这在实际开发中是很实用的优化技巧尤其是在定义大量结构体实例时节省的内存非常可观。爱奇艺的视频编解码模块里有大量结构体定义内存又极度敏感所以这个考点完全是从实战里提出来的非常贴近他们的业务场景。听说当年有参加面试的同学因为在笔试中做对了这道题在技术面时被面试官特别表扬了一句“内存布局意识不错”这属于典型的用笔试表现给自己加分的案例。2.4 典型代码改错题野指针与返回值陷阱这套笔试题的最后一道代码阅读题给了一段有问题的函数要求找出所有错误并修正。我记得原题大致是这样char *getString() { char str[] hello; return str; } int main() { char *p getString(); printf(%s\n, p); return 0; }这段代码犯了一个经典的错误——返回了栈内存的地址。str是getString函数内的局部数组存储在栈上。函数返回后这块内存就释放了虽然大概率不会立刻被改写但是一旦有其他函数调用栈帧会被覆盖p指向的内容就会变成乱码。这就是典型的“野指针”问题。修正方案有两种思路第一种使用静态存储区char *getString() { static char str[] hello; return str; }第二种在堆上分配内存char *getString() { char *str (char *)malloc(6 * sizeof(char)); strcpy(str, hello); return str; } // 调用方负责free(p)第一种方案的坑在于静态变量是线程共享的多线程环境下会有并发问题。第二种方案则需要调用方记得free否则就会出现内存泄漏。这也是爱奇艺这道题的核心考点不光是让你找出语法错误还要看你能不能识别出内存生命周期问题。在后端开发里这种“返回局部变量地址”的错误如果出现在线上调试起来非常痛苦因为它的表现是随机性的可能在开发环境跑得好好的一上线就偶发崩溃。3. 手写算法题从冒泡排序到迪杰斯特拉的完整解题套路3.1 冒泡排序及其优化从基础版到进阶版冒泡排序几乎是每套C语言笔试题都会出现的送分题。但爱奇艺这套题考得更深入一步要求写出“优化后的冒泡排序”。优化点很明确当某次遍历中没有发生任何交换说明序列已经有序可以提前退出。void bubbleSort(int arr[], int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped 1; } } if (!swapped) { break; // 没有任何交换提前结束 } } }这个优化在最好情况序列已有序下能把时间复杂度从O(n²)降为O(n)。笔试时如果时间允许还可以再提一个优化点记录最后一次交换的位置该位置之后的元素已经有序下一轮只需遍历到该位置即可。冒泡排序本身不是考点考点在于你是否理解排序算法的时间复杂度分析。面试官更希望看到你写出代码后能清晰地解释“为什么内层循环条件是j n - 1 - i”以及“如何通过标记位降低最好情况的时间复杂度”。这些分析过程才是拉开差距的地方。3.2 字符串逆序的多个变体与边界条件字符串逆序在手写代码题里出现光写出一个能跑的函数还不够你必须把所有边界情况都考虑进去。主流的考察形式有三种第一种不调用库函数自己实现逆序。这种题目实际上是在考察你对字符串底层结构的理解——必须遍历到结尾找到\0然后往回交换。关键是你自己找末尾时不要越界。第二种不允许定义新数组在原数组上直接操作。这就是双指针原地交换法。要注意的是中间位置的元素不需要动当指针相遇或者交错时循环终止。第三种用递归方式实现。很多同学一看到“用递归实现字符串逆序”就懵了但实际上递归思路非常清晰void reverse(char *str) { if (*str \0) { return; } reverse(str 1); printf(%c, *str); }注意这个递归版本只是逆序打印并不是真正原地逆序存储。如果题目明确要求“就地逆序”用递归会非常复杂且低效这时候应该用迭代的双指针写法。在笔试时如果遇到字符串逆序的变体题我的习惯是先写双指针的迭代版本如果时间允许再补充一个递归版本并说明二者区别。这样既保证正确性又展示了你的思维深度。3.3 迪杰斯特拉算法A卷的压轴题说实话当我在A卷最后看到迪杰斯特拉算法题时确实有点意外因为这道题在互联网公司笔试里出现的频率并不算太高更多出现在考研或竞赛题中。但爱奇艺考它也是有道理的推荐系统和CDN调度里都有大量图论模型的影子。这道题的要求通常是给定一个带权有向图用邻接矩阵存储求从源点出发到所有其他顶点的最短路径。void dijkstra(int graph[V][V], int src) { int dist[V]; int visited[V]; for (int i 0; i V; i) { dist[i] INT_MAX; visited[i] 0; } dist[src] 0; for (int count 0; count V - 1; count) { int u minDistance(dist, visited); visited[u] 1; for (int v 0; v V; v) { if (!visited[v] graph[u][v] dist[u] ! INT_MAX dist[u] graph[u][v] dist[v]) { dist[v] dist[u] graph[u][v]; } } } printSolution(dist); } int minDistance(int dist[], int visited[]) { int min INT_MAX, min_index; for (int v 0; v V; v) { if (!visited[v] dist[v] min) { min dist[v]; min_index v; } } return min_index; }这道题的易错点主要有几个方面。首先是初值的设置dist数组初始化时要填INT_MAX源点的dist置为0。其次是找到最小距离节点后要立刻将visited[u]置为1防止后续轮次重复选中它。再次是松弛操作中的条件判断不仅要检查两点是否有边相连还要检查dist[u]是否本来就是无穷大否则INT_MAX graph[u][v]会溢出成负数导致错误结果。关于时间复杂度基础版本的实现是O(V²)适合稠密图如果改用邻接表加最小堆优化可以降到O((VE)logV)。笔试时可以先用基础版保底然后在旁边简单写一句“使用堆优化可将复杂度降为O((VE)logV)”这能向面试官展示你的优化意识。3.4 链表相关的手写题反转链表与环检测链表的题目在C方向笔试中出现频率也非常高。爱奇艺这套题虽然没有把链表作为压轴但在选择题和填空题里考察了链表的基本概念比如头插法和尾插法的区别。不过我还是建议备考时把链表的几个经典手写题都练熟。最经典的一道是反转链表struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev NULL; struct ListNode *curr head; while (curr ! NULL) { struct ListNode *next curr-next; curr-next prev; prev curr; curr next; } return prev; }这里的核心思路是保存当前节点的下一个节点然后把当前节点的指针指向前一个节点。关键点在于循环内三行代码的顺序不能乱先保存next再反转指针然后移动prev和curr。如果把顺序写反链表就断了找不回后面的节点。另一个高频考点是检测链表中是否有环。最常见的解法是快慢指针int hasCycle(struct ListNode *head) { struct ListNode *slow head; struct ListNode *fast head; while (fast ! NULL fast-next ! NULL) { slow slow-next; fast fast-next-next; if (slow fast) { return 1; } } return 0; }这道题的判断技巧是快指针每次走两步慢指针每次走一步如果有环快指针一定会追上慢指针但fast-next为NULL时说明链表没有环。关键在于循环条件里要同时判断fast和fast-next不为空否则访问空指针会崩溃。4. 内存管理与文件操作C方向笔试的隐形重点4.1 C语言内存管理从堆栈分配到内存泄漏排查很多同学复习C语言笔试时把精力全放在算法题上忽略了内存管理这块。但实际上爱奇艺这类做基础架构的公司对内存管理的重视程度绝不亚于对算法的重视程度。因为这直接关系到线上服务能不能稳定运行。C语言的内存主要分几个区域栈区由编译器自动分配释放堆区由程序员手动分配释放。栈区空间有限默认一般是1MB左右所以大块数据不能放栈上容易栈溢出堆区空间大得多但需要手动管理。笔试题最常考的内存管理问题就这几类第一类是malloc和free的配对使用。每次malloc之后必须检查返回值是否为NULL因为当堆空间不足时malloc会返回NULL直接对空指针进行操作会导致程序崩溃。free之后必须把指针置为NULL防止产生“野指针”——虽然free后释放了内存但原指针还保留着地址如果再次访问就属于未定义行为。第二类是内存泄漏。所谓内存泄漏是程序反复malloc但忘记free导致可用内存越来越少。在笔试填空题中最常见的考法就是给你一段循环代码让你判断内存泄漏的位置。我的经验是看到malloc就找有没有配对的free看到循环里的malloc就格外警惕。第三类是sizeof与malloc配合使用的正确姿势即malloc(n * sizeof(int))的具体写法。有些同学会写成malloc(n * 4)这是有隐患的因为int在不同平台上的字节数并不一样不能假设为4。正确写法是用sizeof(int)这样代码跨平台编译时依然正确。4.2 C语言文件读写操作从fopen到fwrite的细节文件操作这块热词榜上的“C语言文件读写操作代码”说明有大量同学在搜这个问题。原因很简单学校课程里文件操作讲得少但笔试和面试时却经常被考到。最常考的是fopen、fread、fwrite、fprintf、fscanf这一组函数的使用以及文本文件和二进制文件的区分。先看一段标准写法FILE *fp fopen(data.txt, r); if (fp NULL) { perror(文件打开失败); return -1; } char buffer[256]; while (fgets(buffer, sizeof(buffer), fp) ! NULL) { printf(%s, buffer); } fclose(fp);这段代码里有几个细节值得注意fopen的返回值必须检查。文件打开失败的原因很多比如路径不存在、权限不足、磁盘写保护等如果忽略这个检查直接操作程序会崩溃。fgets读取文本行时会把换行符也一并读入。如果不需要换行符需要手动去掉char *pos strchr(buffer, \n); if (pos ! NULL) { *pos \0; }关于文本模式和二进制模式的区别在Windows平台尤其要注意。二进制文件用rb或wb打开文本文件用r或w打开。fread和fwrite主要用于二进制文件的读写fprintf和fscanf是格式化读写更适合文本文件。还有一道笔试高频题统计一个文件中有多少个字符、多少行。这题的本质是判断换行符。核心逻辑是逐个字符读取遇到\n行数加1所有字符数累加。int ch; long count 0; while ((ch fgetc(fp)) ! EOF) { count; } printf(总字符数: %ld\n, count);注意这里ch必须用int类型而不能用char因为EOF在stdio.h中被定义为(-1)如果char类型是有符号的且能容纳-1会导致文本模式下正常字符0xFF被误判为EOF造成提前结束读取。4.3 结合爱奇艺业务场景的内存优化思路在笔试的简答题部分爱奇艺还出了一道很有公司特色的题给出一段视频缓存模块的伪代码要求分析内存使用是否合理并提出优化方案。这种题教科书上找不到标准答案但底层逻辑并不难。核心思路就是两点减少不必要的内存拷贝以及正确管理缓冲区的生命周期。比如在缓存模块中频繁在堆上分配小块内存再释放会造成大量内存碎片。优化方案是使用内存池技术预先分配一块较大的连续内存按需切分成固定大小的小块。内存池的代码实现笔试不一定会让你完整写出但如果你能说出“用空闲链表管理内存块”这个思路就能有效区分你和那些只会刷选择题的同学。还有一点是对存储大型结构体时减少内存浪费。如果你定义了一个结构体里面包含了多个char数组但不一定每个都填满在内存紧张的环境下可以考虑改用指针加动态分配的方式。代价是每次访问多了一次解引用操作属于典型的以时间换空间的取舍。5. 常见易错点与笔试复盘清单5.1 高频易错点逐项拆解结合热词榜和真题我把C方向笔试的高频易错点整理成了一张清单每条都对应一个真实丢分场景。第一while循环和do-while循环的区别。while先判断后执行循环体可能一次都不执行do-while先执行后判断循环体至少执行一次。笔试中通常结合“输入校验”“菜单重绘”这类场景来考。比如要让用户输入一个1-100之间的数字不合法就重新输入用do-while就非常合适。第二和的混淆。在C语言中if (x 1)是合法代码但含义是“把1赋给x然后判断x是否为真”——永远为真。很多选择题的陷阱就设计在这里。我的习惯是把常量写在左边if (1 x)这样如果少写一个等号变成if (1 x)编译器会直接报错从根源上避免踩坑。第三数组越界访问。C语言本身不检查数组越界越界访问是一种未定义行为。笔试题会通过给一段越界代码让你推断结果的方式考察你是否意识到这一点。比如int arr[5] {1, 2, 3, 4, 5}; for (int i 0; i 5; i) { printf(%d , arr[i]); }这段代码最后会多输出一个arr[5]这是紧挨着数组的一块栈内存内容不确定。笔试中如果问你输出结果只能回答“未定义行为最后一个值不确定”。能答出这一句说明你是真的理解C语言的内存模型而不是死记硬背。第四全局变量和局部变量的默认初始化。全局变量和静态局部变量由编译期自动初始化为0普通局部变量不会自动初始化其值是不确定的。这个点在填空题里经常出现很多同学想当然以为所有变量默认都是0结果丢分。第五break和continue的作用范围。这两个关键字在有嵌套循环时需要格外小心。break只能跳出最内层循环如果有多层嵌套需要借助标志变量或者goto语句跳出外层循环。continue则是结束本次循环进入下一次迭代。选择题常考它们的区别即使基础很好的同学在嵌套循环的场景下也有可能选错答案。5.2 从IDE到Linux环境与工具链准备要点备考期间把自己的开发环境弄清晰是一件很容易被忽略却非常重要的事情。很多同学平时在VS Code里编译运行都没问题一到笔试环境通常是牛客网或者赛码网就各种不适应原因就是平时依赖了IDE自动完成的很多功能。先说VS Code配置C/C环境热词榜里有人搜这个说明配置过程确实容易踩坑。核心步骤其实就三步安装C/C扩展、配置编译器路径Windows是MinGW的gcc.exemacOS/Linux是系统自带或安装的gcc、配置launch.json和tasks.json用于调试和构建。其中最容易出问题的是Windows下配置minGW时路径写错或者没有把编译器所在目录加入环境变量的PATH。我建议配置好之后先用命令行验证一下能不能直接执行gcc --version如果命令行可以了VS Code里就不会有问题。再说在线笔试环境。牛客网和赛码网用的编译器通常是Linux环境下的GCC默认是C11标准。这意味着本地Windows的某些写法比如_s后缀的安全函数在Linux下会编译失败int类型在64位Linux下仍然是4字节但long是8字节和Windows不同在线OJ通常不会提供完整代码只给一个核心函数让你填写所以我的建议是备考代码题时尽量在命令行下编译运行别太依赖IDE自动补全和智能提示。平时刷题时也模拟“不看提示”的状态来写代码这样到笔试环境就不会手生。5.3 笔试后的复盘把一套真题榨干笔试结束后很多人就彻底放松了等着结果。但我的建议是笔试后立刻趁热打铁重新盘一遍题目因为这时候你对题目的记忆还热乎着复盘效率最高。复盘要做什么不是把题再做一遍对个答案就行。我一般分三步第一步把每道题涉及的考点列出来看哪些题考的是同一个知识点。比如选择题里考sizeof的那道和填空题里考strlen的那道本质上是一个考点。这样归并之后你就会发现自己真正的薄弱点在哪里而不是被一道一道孤立的题牵着走。第二步把做错的题和蒙对的题单独拿出来写出正确解法并想清楚自己错在哪个环节。是概念不清还是边界条件考虑不周建议用一个错题本或者一个mapping表把问题记录下来。第三步如果这套题里有你当场没做出来的算法题一定要在笔试结束后把它完整实现一遍并跑通测试用例。迪杰斯特拉、字符串逆序、链表反转这些题当场没写出来不代表你不行但如果你回去还不练那就永远是短板了。我自己当年笔试完的复盘习惯是当天晚上重新梳理、整理错题第二天尝试不看参考代码独立默写一遍。如果发现默写还是有卡壳说明这个知识点还没真正内化我会隔一天再重写一次。这样做几次之后很多考点的代码就变成了身体记忆真正面试时能做到“随手就写”的程度。6. 最后再分享几点关于C语言备考的体会从爱奇艺的这套真题来看C方向笔试并不是一个“刷题越多分越高”的考试它更像是一个“基本功工程敏感度”的综合检验。如果你花大量时间刷各种冷门题不如先把内存管理、指针、字符串、链表这些核心内容搞到滚瓜烂熟再来谈进阶。有几件事是当年我自己备考时踩过的坑这里换个角度提一下。一个是不要只背答案不看原理一道选择题的答案是C你记住了下次出个变体题你还是不会。另一个是手写代码不要只在纸上写一定要编译运行很多隐蔽的错误比如返回值类型不对、边界条件漏判只有真正跑起来才会暴露。还有一点是对待错题的态度。很多同学做完一套题就扔了从不回看错题这是性价比很低的做法。一套秋招真题如果你能把它考到的所有知识点都吃透并且能举一反三写出变形题的解法那它带给你的价值远超随便刷十道LeetCode。关于C语言的学习资源如果还在打基础阶段翁恺老师的C语言课程是很多人推荐过的入门选择配套的练习题也值得认真刷。等你觉得基础差不多了再找各种公司的历年笔试真题来练手会更有针对性。但一定要记得看课和看题都不等于会写真正提升你代码能力的永远是动手写到编辑器里、让它跑起来、然后看它有没有按你预期运作的过程。