作为2019年参加过秋招的人,爱奇艺这套C方向笔试题(A)我印象还挺深的。那时候刷题刷到头秃,拿到卷子一看,倒不是难到无从下手,而是很多题看着眼熟,真要手写代码或者辨析概念,却处处是坑。这套题放在今天回头看,依然是C语言方向求职笔试里很有代表性的一套,覆盖面广、基础考得细、还特别爱在边界条件上做文章。无论你是准备校招的应届生,还是想系统检验一下自己C语言功底的开发者,这套题都值得认真过一遍。
我尽量把题目涉及的考点、解题思路、容易踩的坑都拆开讲清楚,也补了一些当年我自己在笔试和面试中总结的经验。内容偏实战,直接照着复习和练习就行。
1. 这套C方向笔试题到底在考什么
1.1 笔试风格的三个典型特征
爱奇艺这套C方向笔试题(A)给我的第一感觉是:不偏不怪,但非常考验基本功。和某些公司喜欢出偏题、怪题不同,它的命题思路更偏向“把基础知识点问透”,尤其喜欢在大家最熟悉的地方埋坑。
- 覆盖面广,但单题分值大:选择、填空、编程题都有,每个考点都不算超纲,但组合起来能筛掉很大一批人。
- 重概念辨析,轻死记硬背:比如指针数组和数组指针、堆和栈的区别、结构体对齐这类题,光背概念没用,得真正理解底层原理。
- 喜欢在边界条件上做文章:字符串处理、数组越界、空指针、内存泄漏这些场景反复出现,考察的就是工程习惯和代码敏感度。
这套题最值得做的原因,恰恰在于它的“不偏不怪”。你不需要准备什么偏门技巧,把C语言的核心基础打牢,把常见的数据结构和算法题练熟,就能应对大部分题目。
1.2 从题目反推C语言岗的能力要求
透过这套题,可以清晰看到企业招聘C方向程序员时最看重的四种能力:
- 内存与指针的理解深度。这是C语言的灵魂,也是区分“会写C”和“懂C”的分水岭。笔试中几乎必有指针运算、指针与数组关系、内存分配与释放的题目。
- 数据结构基本功。链表、二叉树、栈队列这“老三样”必须手到擒来,尤其是它们的增删改查和常见变形题。
- 算法与复杂度意识。虽然题目不会要求你写出红黑树,但排序算法的选择、二分查找的边界处理、递归转迭代的思路,都能看出你有没有算法思维。
- 工程习惯与代码规范。变量命名、资源释放、异常输入处理,这些不会直接给你加分,但会在编程题和面试追问环节暴露你的水平。
我当时做完这套题最大的感受是:刷题不是目的,理解才是。面试官真正想看到的,是你面对一个具体问题时,能不能用C语言写出健壮、高效、可读的代码。
2. 核心考点深度拆解与应对思路
2.1 指针与内存管理:最容易拉开差距的部分
指针这部分,爱奇艺的题目比较经典的是:
给定一段代码,让你判断输出结果或者指出错误。比如:
#include <stdio.h> int main() { char *p = "hello"; p[0] = 'H'; printf("%s\n", p); return 0; }这道题考的是字符串字面量存储在只读区,试图修改会触发段错误。很多人一看到char *p = "hello"就以为是可修改的字符数组,实际上它指向的是只读内存,运行时直接崩溃。
还有一类高频题是:
// 判断下列代码哪里有问题 char *get_string(void) { char str[] = "hello"; return str; }这里str是栈上的局部数组,函数返回后栈帧被回收,返回的指针成了“悬浮指针”,再访问就是未定义行为。
应对这类题,我的经验是脑子里要有一张“内存地图”:
| 内存区域 | 存放内容 | 生命周期 | 易错点 |
|---|---|---|---|
| 栈 | 局部变量、函数参数 | 函数调用期间 | 返回栈地址、栈溢出 |
| 堆 | malloc分配的内存 | 手动释放前一直存在 | 内存泄漏、重复free、悬挂指针 |
| 全局/静态区 | 全局变量、static变量 | 整个程序运行期间 | 多线程竞争、初始化时机 |
| 只读区/代码段 | 字符串字面量、代码 | 整个程序运行期间 | 试图修改字符串字面量 |
| 常量区 | const修饰的常量 | 整个程序运行期间 | 通过指针篡改常量 |
笔试时遇到指针和内存相关的题目,先停下来问自己三个问题:这个变量存在哪个区域?它的生命周期到什么时候结束?会不会被外部修改或释放?把这三个问题想清楚,大部分坑都能避开。
2.2 字符串与标准库函数的坑
字符串处理是C方向笔试的另一个重头戏。爱奇艺的题里有不少是基于strcpy、strlen、strcmp这些标准库函数来做文章的。
先看一个经典陷阱:
#include <stdio.h> #include <string.h> int main() { char src[] = "hello"; char dest[5]; strcpy(dest, src); printf("%s\n", dest); return 0; }dest数组长度只有5,而"hello"加上结尾的'\0'一共需要6个字节,strcpy不检查目标缓冲区大小,直接越界写入,这就是典型的缓冲区溢出。笔试里经常让你判断这段代码是否有问题,或者问strcpy和strncpy的区别。
另一个高频考点是sizeof和strlen的区别:
char str[] = "hello"; // sizeof(str) 是多少?strlen(str) 是多少?sizeof(str)是整个数组占用的字节数,即6(5个字符加1个'\0');strlen(str)是字符串长度,即5。这个考点看似基础,但在涉及指针时特别容易出错:
char *p = "hello"; // sizeof(p) 是多少?strlen(p) 是多少?sizeof(p)在64位系统下是8(指针本身占8字节),strlen(p)是5。这两个放在一起考,能筛掉一大半概念不清的候选人。
字符串这块我建议复习时重点关注:
- 字符数组与字符指针的区别
sizeof与strlen的区别strcpy/strncpy/memcpy的区别和各自的安全隐患- 字符串逆序的高效写法
- 指针版本和数组版本的字符串遍历
2.3 数据结构的链表、二叉树与栈
爱奇艺这套笔试题在数据结构部分很典型,链表反转、链表判环、二叉树遍历、栈和队列的互相实现这些都是“必背题库”。
链表相关的题,核心考察指针操作的基本功。比如单链表反转,很多人笔试时卡住,不是不知道思路,而是一上手写代码,指针指来指去就乱了。
二叉树这块,最常见的就是前序、中序、后序遍历的递归和非递归实现。递归写法大家都会,但非递归写法和层序遍历就能拉开差距。我见过不少候选人递归写得飞起,一问非递归就卡壳,这是因为对栈的运用不够熟练。
栈和队列的互转题考的是抽象思维能力:
- 用两个栈实现队列
- 用两个队列实现栈
这类题面试官也很喜欢追问,因为代码量不大,但逻辑清晰的人能快速写对,逻辑混乱的人会陷入各种边界情况。
我在刷这类题时有一个习惯:先在纸上画出数据结构的变化过程,再写代码。比如链表反转,先画出原链表,标出每一步的prev、current、next指向,再动手写,准确率会大大提高。
2.4 典型算法题:排序、查找与动态规划
算法题部分,爱奇艺的难度适中,但考察面很全。
排序算法几乎是必考的。要求不仅会写冒泡、快排,还要知道它们的稳定性、时间复杂度和适用场景。一个常见的出题方式是给一组数据,问稳定排序和不稳定排序的结果差异,或者问在几乎有序的数据下,哪种排序最快。
二分查找也是高频考点,但考得比较细:
// 二分查找的循环条件应该写 left < right 还是 left <= right? // 中间值计算应该写 (left + right) / 2 还是 left + (right - left) / 2?(left + right) / 2有整数溢出风险,left + (right - left) / 2更安全。这个细节,笔试中不会直接问,但在编程题中使用时,面试官会看你是否意识到这个问题。
动态规划的题一般不会出太难,常见的有:
- 斐波那契数列的DP写法
- 爬楼梯问题(一次可以爬1或2阶)
- 最大子数组和
- 最长递增子序列
这些题只要练熟了套路,基本能做到拿到题就条件反射地写出状态转移方程。如果对DP不太熟,我建议先把“斐波那契数列”“爬楼梯”“最大子数组和”这三道搞定,它们覆盖了DP最核心的思想:状态定义、状态转移、边界条件。
3. 几类必考编程题的手写实现
3.1 字符串逆序:基础中的基础
字符串逆序在爱奇艺的笔试里属于送分题,但送分题也最容易丢分。我见过不少人把题目理解错,写成了逆序打印,而不是原地逆序。
原地逆序的标准写法是双指针:
#include <stdio.h> #include <string.h> void reverse_string(char *s) { if (s == NULL) return; int left = 0; int right = strlen(s) - 1; while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } int main() { char str[] = "hello world"; reverse_string(str); printf("%s\n", str); return 0; }注意这里必须用char str[] = "hello world",不能写成char *str = "hello world",因为字符串字面量是只读的,逆序操作会导致段错误。这就是很多人本地编译没问题,一提交就崩溃的原因。
更进阶一点,题目可能会考“反转单词顺序”,比如输入"I am a student",输出"student a am I"。思路是先整体逆序,再对每个单词单独逆序。
#include <stdio.h> #include <string.h> void reverse_range(char *s, int left, int right) { while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } void reverse_words(char *s) { if (s == NULL) return; int len = strlen(s); // 第一步:整体逆序 reverse_range(s, 0, len - 1); // 第二步:对每个单词逆序 int i = 0; while (i < len) { while (i < len && s[i] == ' ') i++; int start = i; while (i < len && s[i] != ' ') i++; reverse_range(s, start, i - 1); } }这种“先整体后局部”的思路在字符串题里非常常见,练熟一道,很多变体都能套。
3.2 单链表反转的迭代与递归
链表反转是爱奇艺笔试编程题里的常客,也是面试时手写代码的高频题。迭代写法是核心,必须做到闭着眼睛都能写对:
#include <stdio.h> #include <stdlib.h> struct ListNode { int val; struct ListNode *next; }; struct ListNode *reverse_list(struct ListNode *head) { struct ListNode *prev = NULL; struct ListNode *curr = head; struct ListNode *next = NULL; while (curr != NULL) { next = curr->next; // 暂存下一个节点 curr->next = prev; // 反转指针方向 prev = curr; // 指针整体后移 curr = next; } return prev; // 此时prev指向新链表的头 }这段代码的关键是理解三指针的移动过程。我在教别人的时候喜欢用一个比喻:想象你拿着一串珠子要把它倒过来,你一次只能操作一颗珠子,每操作一颗都要先用眼睛盯住下一颗,防止珠子散落。next = curr->next就是这个“先盯住下一颗”的动作。
递归写法虽然不常用,但面试官偶尔会问,也写一下:
struct ListNode *reverse_list_recursive(struct ListNode *head) { if (head == NULL || head->next == NULL) { return head; } struct ListNode *new_head = reverse_list_recursive(head->next); head->next->next = head; head->next = NULL; return new_head; }递归写法的核心是“先反转后面的部分,再把当前节点接到最后”。理解起来稍微难一点,但代码更简洁。如果笔试时间紧张,优先保证迭代写法正确,递归写法作为加分项。
3.3 手写字符串拷贝函数
手写strcpy也是C方向笔试的经典题目。别以为这是送分题,其实里面藏了不少陷阱。
基本版本:
char *my_strcpy(char *dest, const char *src) { if (dest == NULL || src == NULL) { return NULL; } char *ret = dest; while ((*dest++ = *src++) != '\0') { ; } return ret; }这个版本能处理大多数情况,但有一个严重的隐患:内存重叠。如果src和dest指向的内存区域有重叠,直接拷贝会出问题。比如:
char str[] = "hello world"; my_strcpy(str + 1, str);这里dest和src重叠了,直接从前向后拷贝会覆盖掉还没拷贝的源字符。正确的做法是使用memmove,或者自己判断重叠方向并决定从前向后还是从后向前拷贝。
笔试中这个知识点通常以选择题形式出现:问memcpy和memmove的区别。答案是:memcpy不处理重叠,memmove处理重叠。这也是为什么很多公司要求你手写memmove而不是memcpy。
标准memmove实现的核心逻辑:
void *my_memmove(void *dest, const void *src, size_t n) { char *d = (char *)dest; const char *s = (const char *)src; if (d == s) { return dest; } if (d < s) { // 从前向后拷贝 for (size_t i = 0; i < n; i++) { d[i] = s[i]; } } else { // 从后向前拷贝 for (size_t i = n; i > 0; i--) { d[i - 1] = s[i - 1]; } } return dest; }笔试时如果遇到这类题,先判断题目有没有说“处理重叠”,再决定写哪种版本。如果题中没有明确要求,最好在代码里加上重叠判断,展示你的工程意识。
3.4 文件读写综合题
C语言的文件操作在笔试中不会单独出大编程题,但会以填空或简答的形式出现,比如:
fopen有哪几种打开模式?fread和fscanf的区别?- 文本文件和二进制文件的读写有什么不同?
- 如何判断文件是否读取到末尾?
这里有一个很值得注意的细节:判断文件结束,不要用feof()作为循环条件。因为feof()只有在尝试读取越过文件末尾时才会返回真,如果文件刚好在最后一个数据处结束,feof()还没被触发,会导致多读一次。正确的做法是先调用读取函数,再判断返回值。
#include <stdio.h> #include <stdlib.h> int main() { FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { perror("fopen failed"); return -1; } int num; // 先用fscanf读取,再判断返回值 while (fscanf(fp, "%d", &num) == 1) { printf("%d\n", num); } fclose(fp); return 0; }文件操作的题目,在笔试中更侧重考察的是“意识”:是否记得检查文件是否成功打开、是否记得在程序结束前关闭文件、是否区分了文本模式和二进制模式。这些习惯代码量不大,但体现了真实工程中是否可靠。
4. 笔试现场高频错误与排查方法
4.1 编译阶段错误
爱奇艺的笔试系统用的是在线评测环境,很多错误在本地编译没问题,一提交就报编译错误。最常见的几个:
- 头文件缺失。用了
strlen却没#include <string.h>,用了malloc却没#include <stdlib.h>。本地编译器可能因为间接包含了头文件而侥幸通过,但在线评测系统会严格检查。 - 变量未初始化。局部变量不初始化就用,在gcc下默认可能给了0,但在线评测环境的标准可能不同,行为未定义。
- main函数返回值类型写错。有些入门教材写
void main(),这在ANSI C标准里是错的,应该是int main(void)。
这类错误在笔试中很冤枉,明明思路对了,却因为格式问题丢分。我建议提交前做一次“编译清单检查”:头文件有没有带全、变量有没有初始化、函数声明有没有写在调用之前、有没有漏掉分号。
4.2 运行阶段错误
运行时最常见的错误就是段错误,也就是访问了不该访问的内存。笔试时出现段错误,优先检查以下三件事:
- 数组越界:循环边界写错,访问了
a[n]而不是a[n-1]。 - 空指针:没有判断
malloc的返回值,或者链表遍历时没有检查当前节点是否为NULL。 - 修改了只读内存:对字符串字面量做写操作,这在前面已经讲过,是很多人的“致命伤”。
还有一个容易被忽视的问题:死循环。笔试环境通常有CPU时间限制,如果代码陷入死循环,超时会被直接判为0分。常见死循环场景是快排的partition函数写错,导致左右指针永不交汇;或者二分查找的边界更新逻辑写反,导致区间永远不缩小。
4.3 边界条件遗漏
评审编程题时,面试官会重点考察边界条件。我整理了一个高频检查表:
| 输入场景 | 需要检查的问题 |
|---|---|
| 空字符串 | 长度是否为0,逆序是否还能正常执行 |
| 单字符字符串 | 反转后是否保持不变 |
| 链表只有一个节点 | 反转后是否还能正确返回 |
| 链表为空 | 是否直接返回NULL而不是段错误 |
| 数组长度是1或2 | 排序、查找是否还能正确处理 |
| 数值为INT_MAX/INT_MIN | 加减是否会溢出 |
| 除数为0 | 是否有保护判断 |
| 指针为NULL | 是否先做判空再使用 |
我见过太多人因为漏掉空指针判断而丢分。笔试和面试真正拉开差距的,往往不是会不会写核心逻辑,而是能不能把边界情况想全。
5. 针对这套题的备考节奏与刷题方法
5.1 时间分配建议
如果你手头有这套卷子或者类似的C方向笔试题,我建议按照“7-3-2”法则分配复习时间:七成时间打牢基础,三成时间刷题巩固,两成时间做模拟笔试。
打个比方,基础就像房子的地基,刷题就像装修。地基不牢,装得再好看也没用。所谓基础,是指:
- 指针和内存:花时间把“内存地图”啃下来
- 字符串函数:每个常用函数的返回值、行为、安全隐患都搞清楚
- 链表和二叉树:能手写增删改查、遍历、反转、判环
- 排序和查找:能手写冒泡、快排、归并、二分查找
地基打牢的基础上,再集中刷题。刷题不是盲目刷,要有分类意识:字符串类、链表类、二叉树类、动态规划类,一类一类过,每类吃透几道代表题,比海量刷题效率更高。
5.2 做题顺序策略
真正坐到笔试现场时,做题顺序也会影响你最终能拿多少分。我的建议是先做熟悉的、分值高的、代码量少的题。
笔试题型和分值分布通常有规律:选择题和填空题量大但单题分值低,编程题数量少但分值高。如果时间有限,优先保证编程题完成,因为它一道题的分值可能抵得上十道选择题。
编程题里也有优先级:先做思路清晰、代码量小的题,比如字符串逆序、链表反转、数组去重;再啃复杂的算法题,比如动态规划、树形结构操作。避免在一道题上卡太久,尤其是编程题,一旦卡住超过20分钟,赶紧跳到下一题。
5.3 心态与临场技巧
笔试考的不只是知识储备,还有临场发挥。总结几个实战技巧供你参考:
- 先读完整题目再动手。爱奇艺的题有时候会在题目最后一段补充限制条件,比如“不能使用额外空间”“时间复杂度为O(n)”。不读完就动手,很容易方向就错了。
- 手写代码前先在注释区写思路。这道题需要几步、每个步骤在做什么,写清楚再动手。这样即使代码写不完整,评审也能看到你的思路。
- 代码里多写判空、判边界。这既是给评审看的,也是给自己减少调试成本。
- 遇到不确定的选择题,标注“有疑惑”,先把会做的做完,再回头处理。不要在单个题上耗时太久。
秋招笔试的时间通常很紧张,一套卷子可能只有90分钟,但题目量不小。真正的考试状态是“会的不丢分,难的不恋战”。
6. 复盘这套题之后的几句实话
把爱奇艺这套2019秋招C方向笔试题(A)从头到尾过一遍后,我最深的感受是:它能火起来是有原因的。它不像某些公司的题偏到需要常年刷竞赛题库才能应付,也不像另一些公司只考选择题完全看不出代码水平。它的难度曲线是合理的、区分度是真实的,做得好的人是真的懂C语言。
我当年笔试时在“字符串逆序”这道题上栽过跟头,因为用了char *str = "hello",结果本地跑得好好的,线上评测直接段错误。后来把这个教训写在错题本上,面试时凡是涉及修改字符串内容的题,我都会下意识检查是否用了可修改的字符数组。这个习惯陪我走过了很多场面试。
如果你正在准备秋招,或者想系统提升一下C语言功底,我真心建议把类似的笔试题做三遍:第一遍独立完成不看答案,第二遍对照答案复盘思路,第三遍在限定时间内模拟笔试。三遍下来,你对C语言核心知识点的掌握会有质的提升。很多看似基础的东西,真正做到“闭着眼睛都能写对”时,面试里会自信得多。