
刚把爱奇艺2020校招C方向的第二场笔试题完整刷了一遍考点和解法花了一整个周末整理成笔记。这篇不搞标准答案式的流水账按我实际做题的顺序来写说清楚每一类题到底在考什么、为什么这么考以及考场上让你少丢分的关键细节。爱奇艺这种视频类互联网公司的C/C岗位笔试风格和纯互联网工具类公司不太一样。业务涉及视频编解码、播放器、CDN调度、推荐系统、大数据处理所以对候选人的底层功底和并发处理能力要求很扎实。选择题覆盖面广编程题偏向工程性的场景题。准备投爱奇艺或者想拿它当C/C校招笔试练兵场的这篇都值得花十分钟细看。我把题目构成拆开再把高频考点逐条展开最后放两道编程题的完整实现过程。1. 题目整体构成与出题思路分析1.1 为什么爱奇艺的C方向笔试这么考看完整套题最直观的感受是这是一份“给真正写过代码的人”准备的卷子不是背书就能过的。选择题里大量考察C语言的内存模型、指针运算、结构体对齐这类底层细节编程题则集中在链表操作、字符串处理、缓存设计、线程安全这些工程里天天会碰的东西。爱奇艺的C/C岗位主要面向播放器内核、后端服务、CDN节点、音视频处理等方向这类岗位的共同特点是对性能敏感、对稳定性要求高、对并发场景处理能力要求强。所以笔试不会去考那种“八股文式”的背诵题而是会把语言特性、操作系统原理、数据结构揉在一起用一套综合题筛选出基础扎实的人。另外有一个细节值得注意整套题的选择题部分错误选项设置得很“真实”。不是那种一眼就能排除的错误而是把容易混淆的写法都混进去。比如指针和数组的关系、strcpy和memcpy的区别、malloc和calloc的差异这些如果平时只是“看过”而没有自己上手写过很容易在这类选项上翻车。1.2 整套试卷的知识点分布从题目覆盖范围来看爱奇艺这套C方向笔试题大致可以分成四个模块C语言基础与内存管理指针、数组、字符串函数、结构体对齐、malloc/free、内存泄漏数据结构与算法链表、哈希表、栈与队列、排序、动态规划、双指针操作系统与网络进程线程区别、锁、死锁、堆栈区别、TCP连接状态、select/epollLinux与工程实践常用命令、gdb调试、编译链接过程、多文件工程组织四个模块不是一个平均分配的状态。从我做题的感受和后来对照的面经来看C语言基础部分占比最高大概四成以上数据结构与算法其次三成左右操作系统和网络再占两成Linux和工程实践占剩下的一成。这个比例和很多视频类互联网公司的校招题目分布基本一致核心逻辑是先确认你的语言基本功够不够扎实再确认你能不能把数据结构和算法用于解决真实问题最后确认你有没有基本的工程素养。2. C语言基础题真正的送分题里藏着坑2.1 指针和数组最容易被绕进去的考点指针和数组的关系几乎每次C方向笔试都会出现爱奇艺这套也不例外。它不会直接问你“指针是什么”而是给一段代码让你判断输出结果。比如#include stdio.h int main() { int a[5] {1, 2, 3, 4, 5}; int *p a; printf(%d %d %d\n, sizeof(a), sizeof(p), *(p 3)); return 0; }这道题最关键的点在于sizeof(a)和sizeof(p)的结果完全不同。a是数组名在sizeof运算符里代表整个数组所以结果是5 * sizeof(int)而p是指针在64位系统下固定是8字节不管你指向的是什么类型。这里如果对“数组名在什么场合退化为首元素地址”这个规则不熟很容易把两个结果都答成一样的。更狠的考法是把二维数组和指针结合起来int a[3][4] {0}; int *p a[0][0];然后问你*(p 5)是多少、a[1]和*(a 1)是否等价这类问题。这背后考的是C语言里“多维数组本质上是数组的数组”这一特性以及指针算术运算的步长规则。记住一个关键点a 1移动的是一个“包含4个int的一维数组”的长度而p 1移动的是一个int的长度。这俩如果搞混题就没法做了。再补充一个我当年踩过的坑用int **pp a;这种写法去指向二维数组。编译阶段可能只是警告但运行起来访问就会出错因为a的类型是int (*)[3][4]和int **是完全不一样的东西。笔试中如果遇到这种选项直接判定为错误即可。2.2 字符串处理最容易丢分的细节都在这里C语言字符串的题表面上考的是库函数实际上考的是对“以\0结尾的字符数组”这个底层事实的理解。爱奇艺这套题里字符串相关的选择题至少有三四道而且每道都跟内存操作有关。第一类常考的是strcpy、strncpy、memcpy、memmove的区别。比如给出这么一段代码char src[] hello; char dst[10]; strcpy(dst, src);问你这样写有没有问题。如果函数原型是strcpy(char *dest, const char *src)那么上面这个调用其实是可以正常工作的因为数组名会退化为指针传入。但如果把dst声明为char *dst;而没有分配空间再执行strcpy那就是典型的缓冲区未初始化错误运行时会崩溃。第二类常考的是字符串逆序。这个考点在热词里反复出现说明确实是笔试高频题。实现字符串逆序的核心思路是双指针一个指向字符串头一个指向字符串尾交换字符并向中间移动。需要注意的点是不能直接用strlen之后往原字符串尾部写因为字符串常量区的内容是不可修改的必须先复制到可写缓冲区再操作。第三类容易丢分的是中文编码相关的问题比如在UTF-8编码下用strlen统计中文字符串长度得到的结果和字符数完全不同。这类题在普通公司笔试里不一定出现但爱奇艺这类有内容业务的公司的题库里反而会有因为实际工程里文本处理是常态。2.3 内存管理堆、栈与内存泄漏的判断C语言的内存管理是笔试的必考区域也是面试官判断候选人是否真正写过C/C的重要指标。这套卷子在这一块出了好几道题覆盖面从堆栈区别到野指针到内存泄漏。堆和栈的区别是最基础的一题栈由编译器自动分配和释放存放局部变量和函数调用信息分配速度快但空间有限堆由程序员手动分配和释放空间大但分配速度慢且必须配对使用malloc和free。如果把一个局部变量的地址返回给调用方调用方再通过这个地址去访问数据就是典型的悬垂指针错误因为函数返回后栈空间已经被回收。另一种高频题是判断一段代码有没有内存泄漏void func() { char *p (char *)malloc(100); if (some_condition) { return; } free(p); }当some_condition为真时函数提前返回p指向的内存没被释放这就产生了内存泄漏。笔试中判断内存泄漏的核心思路就是检查每一条return路径上是否都执行了对应的释放操作。这个考点也可以反过来考释放了两次同一块内存怎么办答案是double free属于未定义行为可能直接导致程序崩溃也可能让堆管理器出错。2.4 结构体对齐看着简单一算就错结构体对齐的题我每次做都会提醒自己不要想当然。这套笔试题里有一道典型的结构体大小计算题struct test { char a; int b; char c; };在默认对齐规则下sizeof(struct test)是多少很多人会直接算成1 4 1 6但实际上在32位和64位系统下通常都是12。原因是编译器会把b放在4字节对齐的地址上a后面会填充3个字节c后面再填充3个字节最终整个结构体大小是成员中最大对齐数的整数倍这里是4的倍数。如果我把成员顺序调整一下struct test { char a; char c; int b; };结果就变成8了因为a和c连续存放在一起b只需要对齐到偏移4的位置即可。这个知识点在笔试里考察的不仅是计算能力更是在考察你是否理解“性能换空间”和“空间换性能”的取舍。实际工程中如果你要写一个需要大量传输或者存储的结构体成员的排列顺序直接影响内存占用和带宽消耗。我还遇到过一道更进阶的题是关于位域的对齐以及#pragma pack指令的使用。#pragma pack(1)可以强制结构体按1字节对齐这样结构体的大小就完全等于成员大小的和适合在读写二进制文件或网络协议时使用。但它会导致非对齐访问在某些平台上性能下降甚至触发异常笔试里如果问到这个答案是“按需使用不能无脑压缩对齐”。3. 数据结构与算法题笔试的区分度所在3.1 链表操作题反复出现的“老朋友”爱奇艺这套笔试题里链表相关的题目出现了好几次这是C方向笔试的特色。因为链表天然适合考察指针操作和理解而指针操作又是C语言的核心能力。题目类型集中在三块链表反转、链表去重、双链表插入删除。链表反转是最高频的题目没有之一。迭代法实现的核心代码很简洁struct node *reverse(struct node *head) { struct node *prev NULL; struct node *curr head; while (curr) { struct node *next curr-next; curr-next prev; prev curr; curr next; } return prev; }这段代码的易错点在于在修改curr-next之前必须先把原来的next保存下来否则原链表就断了。笔试中经常会在这一步埋坑比如让你判断一段已经写好的反转代码哪里有bug很多人的注意力都在循环条件上反而忽略了这最关键的一步。链表去重考察的是对“节点删除”操作的理解删除节点时前驱节点的next必须指向被删除节点的后继。这里有一个链表题通用的解题技巧引入一个dummy哨兵节点作为新的链表头可以避免处理“头节点本身就是重复节点”这种特殊判断。这个技巧在笔试和面试中都非常实用值得反复练习直到形成肌肉记忆。爱奇艺的链表题还有一个特点喜欢把链表和哈希表结合考。比如“删除链表中的重复元素要求时间复杂度O(n)”最优解就是用一个哈希表保存已经出现的值遇到重复节点直接删除。这比嵌套循环的O(n^2)解法优秀得多也体现了候选人是否有复杂度意识。3.2 哈希表与缓存设计贴近业务场景的考题视频网站的业务场景里缓存无处不在用户信息缓存、播放进度缓存、推荐结果缓存。所以爱奇艺笔试里出现缓存设计相关的题目完全在意料之中。其中最有代表性的就是手写LRU缓存。LRULeast Recently Used最近最少使用算法的核心思想是当缓存满时优先淘汰最久没有被访问的数据。实现上需要两个数据结构配合哈希表负责O(1)查找双向链表负责O(1)删除和移动。哈希表的key对应缓存项的键value指向双向链表中的节点每次访问某个key时就把对应节点移动到链表头部当缓存满了需要淘汰时删除链表尾部的节点并同步删除哈希表中的记录。这道题在笔试中考察的不仅是数据结构知识还有你对“组合数据结构”的理解能力。单一哈希表可以做到O(1)查找但不能维护访问顺序单一链表可以维护顺序但查找是O(n)只有两者结合才能同时满足要求。这种思路正是工程中解决性能问题的常见方式也是面试官希望看到的思维模式。3.3 动态规划与贪心绕不开的经典模型爱奇艺笔试题里的动态规划题目难度适中但很有代表性。其中一道比较典型的是“最长回文子串”的变体考察的虽然是经典问题但对状态定义和递推关系的理解要求很高。定义状态dp[i][j]表示子串s[i]到s[j]是否为回文串。状态转移方程是当s[i] s[j]且dp[i1][j-1]为真时dp[i][j]为真。需要注意的边界条件是长度为1的子串一定是回文长度为2的子串只需要判断两个字符是否相等。实现时要从长度短的子串开始遍历而不是从i从小到大遍历因为dp[i][j]依赖的是更短的子串状态。贪心算法那边常考的是区间调度问题给定一系列区间求最多能选多少个互不重叠的区间。经典解法是按照区间的结束时间排序然后依次选择结束时间早且与当前已选区间不重叠的区间。这背后其实有一个很关键的反证法逻辑为什么选择结束时间早的更优因为结束时间早留给后面区间的空间就更大。笔试中不仅考察你能不能写出代码更考察你能不能说出这个选择策略为什么是对的。4. 操作系统、网络与Linux从语言到工程的一道坎4.1 进程、线程与并发控制视频服务的特点是并发量极高所以爱奇艺笔试里操作系统相关的题目重点落在并发控制上。选择题中关于进程和线程的区别是必考题进程是资源分配的基本单位线程是CPU调度的基本单位。同一个进程内的多个线程共享地址空间、文件描述符等资源但每个线程有独立的栈和寄存器上下文。有一道经典的辨析题我印象很深多个线程并发执行时如果对全局变量进行自增操作最终结果是否一定等于线程数答案是不一定。因为count在底层不是一条原子指令而是“读取-修改-写入”三步操作。两个线程可能同时读取到同一个旧值各自加1后写回导致结果比期望值少1。这个题目考察的是对竞争条件race condition的理解也是引出锁和原子操作的最好切入点。锁的方面重点考察互斥锁和读写锁的区别读多写少的场景适合读写锁读写均匀或者写多的场景互斥锁反而更简单高效。死锁的四个必要条件互斥、持有并等待、不可剥夺、循环等待是笔试高频题通常会让你分析某段加锁代码是否会产生死锁。4.2 网络编程基础TCP连接与I/O模型网络题的分量虽然没有C语言基础那么重但每套C方向笔试题里都会有几道。最基础的是TCP三次握手的过程很多人的误区是把三次握手理解成“客户端发一次、服务端回一次、客户端再确认一次”就完事了但没有想清楚每一步的作用。三次握手其实是双方确认各自的发送和接收能力都正常的过程第一次握手服务端确认客户端的发送能力第二次握手客户端确认服务端的接收和发送能力第三次握手服务端确认客户端的接收能力。笔试中如果遇到“为什么需要第三次握手”这种问题回答里一定要包含“防止已经失效的连接请求报文突然又传到服务端导致错误建立连接”这一层。I/O多路复用是爱奇艺这类高并发服务笔试的进阶题目一般会考select和epoll的区别。select有三个核心限制文件描述符数量上限、每次调用都要把fd集合从用户态拷贝到内核态、每次返回后要遍历整个fd集合才知道哪些就绪。epoll通过红黑树维护fd集合、通过回调机制将就绪fd放入就绪队列解决了这三个问题。当时我看到爱奇艺笔试中有一道题是“epoll为什么比select高效”本质上就是要你说清楚这三点。4.3 Linux常用命令与调试基本功这套笔试题里关于Linux的部分占比不高但有一道题我觉得很能反映工程素养给出了一个程序编译报错信息让你判断是什么问题。这类题考察的不只是命令记忆而是对编译链接过程的理解。C程序从源码到可执行文件要经历四个阶段预处理、编译、汇编、链接。预处理阶段处理#include和#define编译阶段生成汇编代码汇编阶段生成机器码的目标文件最后链接阶段把多个目标文件和库文件合并成可执行文件。有一个经典错误是声明了函数但没有定义编译时通过了链接时却报undefined reference错误。这个问题的排查思路很明确先检查函数名有没有拼写错误再检查对应的源文件有没有参与编译最后检查有没有链接对应的库。gdb调试的基本命令也是C方向笔试的常客break设置断点、print打印变量值、backtrace查看函数调用栈、next和step的区别next不进入函数内部step会进入。还有排查内存问题的工具valgrind它的--leak-checkfull参数可以详细报告每一处内存泄漏的位置笔试里如果问“如何检测内存泄漏”回答这个工具比只背概念要加分得多。5. 编程题实战还原手写代码的完整过程5.1 题目一实现字符串逆序并统计字符出现次数这道题综合性很强把字符串处理、指针操作和哈希表应用放在了一起。题目要求是输入一个字符串输出它的逆序字符串同时统计每个字符出现的次数并按出现次数从高到低排序输出。核心实现思路分三步走。第一步用双指针法逆序字符串注意要原地操作。第二步用数组模拟哈希表统计字符频率因为字符最多只有256种用一个int count[256]就够了这是C语言里替代哈希表最常用也最高效的方式。第三步把字符和频率封装成结构体排序。完整参考代码如下#include stdio.h #include string.h #include stdlib.h typedef struct { char ch; int cnt; } CharCount; void reverse_str(char *s) { int left 0; int right strlen(s) - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } } int cmp(const void *a, const void *b) { return ((CharCount *)b)-cnt - ((CharCount *)a)-cnt; } int main() { char s[1024]; printf(请输入字符串: ); scanf(%s, s); reverse_str(s); printf(逆序结果: %s\n, s); int count[256] {0}; for (int i 0; s[i] ! \0; i) { count[(unsigned char)s[i]]; } CharCount items[256]; int n 0; for (int i 0; i 256; i) { if (count[i] 0) { items[n].ch (char)i; items[n].cnt count[i]; n; } } qsort(items, n, sizeof(CharCount), cmp); printf(字符频率统计(降序):\n); for (int i 0; i n; i) { printf(%c 出现 %d 次\n, items[i].ch, items[i].cnt); } return 0; }这段代码里有几个关键细节值得说明。第一统计时把char强制转换成unsigned char避免ASCII码大于127的字符在作为数组下标时变成负数。这个问题我当年写代码时踩过一旦输入包含扩展ASCII字符程序就会越界访问输出乱码甚至崩溃。第二qsort的比较函数返回值要从大到小排列所以用b-cnt - a-cnt而不是a-cnt - b-cnt。第三逆序处理的时候字符串末尾的\0不能动所以right从strlen(s) - 1开始不会错误地把结束符交换到字符串头部。5.2 题目二手写一个线程安全的LRU缓存这道编程题是整套卷子里最有区分度的一题考察了数据结构组合、指针操作、线程安全三个层面的能力。题目要求实现一个容量固定的LRU缓存支持get和put两个操作并且要保证在多线程环境下安全访问。我的参考实现思路是用一个双向链表配合哈希表。在C语言里没有标准库的哈希表所以需要一个简单的哈希函数和链表节点数组配合。这里我用到的方法叫“数组模拟双向链表”提前分配好节点数组每个节点有一个前驱下标和后继下标这样既避免了大量malloc/free带来的性能开销也能让内存管理变得简单可控。线程安全方面最简单的方案是给整个缓存加一把大锁这种做法在get和put操作上都是互斥的。面试官可能会进一步问“能不能用细粒度锁优化”这时候要能说出读写锁的思路读操作可以并发写操作必须独占。下面是我在笔试中会写出的核心代码结构#include stdio.h #include stdlib.h #include pthread.h #define CAPACITY 16 #define HASH_SIZE 256 typedef struct Node { int key; int value; int prev; int next; } Node; Node pool[CAPACITY]; int pool_count; int head, tail; int hash_table[HASH_SIZE]; pthread_rwlock_t rwlock PTHREAD_RWLOCK_INITIALIZER; unsigned int hash(int key) { return (unsigned int)key % HASH_SIZE; } int find_node(int key) { int idx hash_table[hash(key)]; while (idx ! -1) { if (pool[idx].key key) { return idx; } idx -1; // 这里用拉链法时保存next简化实现时用顺序查找 } return -1; } void move_to_head(int idx) { if (idx head) return; // 从当前位置摘除节点然后插入到head之前 int p pool[idx].prev; int n pool[idx].next; if (p ! -1) pool[p].next n; if (n ! -1) pool[n].prev p; if (idx tail) tail pool[idx].prev; pool[idx].prev -1; pool[idx].next head; if (head ! -1) pool[head].prev idx; head idx; } int cache_get(int key) { pthread_rwlock_rdlock(rwlock); int idx find_node(key); if (idx -1) { pthread_rwlock_unlock(rwlock); return -1; } int value pool[idx].value; pthread_rwlock_unlock(rwlock); pthread_rwlock_wrlock(rwlock); move_to_head(idx); pthread_rwlock_unlock(rwlock); return value; }这里我简化了哈希冲突处理用find_node做了一个线性查找。但要注意一个问题在cache_get里第一个读锁释放之后、第二个写锁获取之前其他线程可能修改这个节点所以这种写法严格来说还需要进一步加锁保护比如用一个读锁把查找和move合并起来或者直接用互斥锁保证一致性。笔试中如果时间紧张直接上一把互斥锁是最安全的不必为了追求高性能而引入复杂同步逻辑导致bug。实际生产中肯定要设计得更精细但笔试考察的更多是你有没有意识到并发访问的问题。6. 备战时踩过的坑和我的建议6.1 时间分配与做题顺序爱奇艺这套题的选择题数量不算少加上后面的编程题考试时间其实很紧张。我建议的做题顺序是先花5分钟快速浏览全卷把编程题的题目要求看一遍心里先有个大概思路然后从选择题开始做遇到卡壳的题不要死磕先标记一下跳到下一题最后留出充足时间写编程题。编程题一定要先写注释再写代码。注释里写明思路、数据结构设计、时间复杂度和空间复杂度即使代码没能完整跑通阅卷人也能看到你的思考过程。这道题考察的不仅是结果更是分析过程我见过不少因为注释写得清晰而拿到高分的情况。优先级上字符串逆序和链表反转这类基础题应该保证100%做对LRU这类综合题能做多少是多少写出核心数据结构和基本框架就能拿到一半分数。6.2 容易忽略的失分点整理这套题的过程中我总结了几个反复出现的失分点想单独列出来提醒一下。第一个是函数签名问题。笔试编程题经常要求你自己定义函数接口很多人在get、put这类函数命名和参数设计上随性发挥最后虽然逻辑对了但因为没有按题目要求实现被判为不通过。考前一定要仔细读题看清楚函数的输入输出格式、变量命名是否要求统一、返回值的含义是什么。第二个是边界条件。反转链表时链表为空怎么办、缓存容量为1时怎么办、字符串为空字符串怎么办这些测试用例在笔试的评测系统里基本都会覆盖。我习惯在写完核心逻辑后花一分钟把空值、单元素、满容量这三种情况在脑子里跑一遍能提前堵住大部分bug。第三个是内存释放。C语言编程题最容易扣分的就是内存泄漏。只要用了malloc就要想清楚在哪个分支上free。如果提交的代码在循环里反复分配内存但从不释放评测系统即使不报错也会因为内存占用过高而超时。这是C方向笔试和Java、Python方向笔试最大的不同也是很多从其他语言转过来的同学最不适应的地方。这套爱奇艺2020校招C方向第二场笔试题整体难度对认真刷过题的同学来说是中等偏上没有特别偏门的题目但每道题都考察得足够细。准备的思路其实很明确C语言基础部分用《C程序设计语言》和刷题网站反复练习指针和内存相关的题数据结构部分把链表、哈希表、队列的基本操作练到能盲写操作系统和网络按面试高频题背诵关键点并理解原理最后一定不要忽略Linux和gdb的基本功。把这些都准备扎实了不仅这套题能拿高分后面面试手写代码的环节也会轻松很多。