公司动态

快手2020秋招工程C笔试考点全解析:从C语言基础到系统编程

📅 2026/8/31 11:49:09
快手2020秋招工程C笔试考点全解析:从C语言基础到系统编程
快手2020校园招聘秋招笔试的工程C试卷在当年那一届求职者里算是讨论度比较高的一套题。原因很简单它不像有些大厂笔试那样通篇堆算法题而是把C语言基础、内存管理、数据结构、系统编程这些工程底子混在一起考题目本身不难但区分度很高。你如果只是刷题刷出来的选手遇到这套卷子会有点难受反过来平时真的写过C项目、被段错误和内存泄漏折磨过的人反而会觉得每个考点都似曾相识。这套试卷适合谁参考一是准备投递快手工程方向、客户端/后端/基础架构类岗位的在校生二是所有把C/C当主语言的应届生三是想检验自己C语言真实水平的工作两三年的开发。说白了它不考偏题怪题考的是一个工程师最基本的代码素养。这篇文章我就按自己的回忆和理解把这份试卷的考点逻辑、核心题目思路、容易踩的坑还有当时的备考复盘完整拆一遍。1. 快手工程C笔试的整体定位与考察逻辑先说结论快手2020秋招工程C这套笔试卷整体难度在互联网大厂校招中属于中等偏上但它的中上不是靠题目数量堆出来的而是靠考察维度拉开的。整套卷子给我的感觉是出题人很清楚自己要筛什么样的人——不是要算法竞赛选手而是能直接上手写业务代码、能排查线上问题、能理解底层原理的准工程师。1.1 一个务实的基础能力筛选器为什么我说它“务实”因为你在面经里经常看到的那些“手撕红黑树”“写个内存池”之类的题在这份试卷里基本没有。它更多是在考察这些维度C语言语法细节是否扎实尤其是指针、数组、结构体、位运算这些用得极其频繁的东西内存管理是否真正理解堆栈区别、malloc/free配套、内存泄漏的成因字符串处理能力这是C笔试永恒的C位考点基础数据结构与简单算法的手写能力链表、二叉树、排序、查找系统编程常识进程线程、文件I/O、编译链接过程。这个考察逻辑和快手的业务特点是对得上的。快手的服务端大量使用C客户端工程也离不开C/C这套技术栈加上视频推荐、直播、存储这些业务对性能和稳定性要求很高所以招人时特别看重候选人“能不能写出稳的代码”。这里的“稳”字就是整套卷子的隐藏主题。1.2 工程指向从题目看快手要什么样的人我当时考完和几个同学对答案大家有个共同的感受这套卷子如果光看单个题目每一道都能在《C Primer Plus》或者牛客网的题库里找到影子但组合在一起就很有快手的味道。举个例子试卷里涉及字符串逆序、链表反转这类题本身是LeetCode easy级别但它在笔试里出现时会加一些限制条件比如“要求空间复杂度O(1)”“不能使用库函数”“考虑字符串为空或全空格的边界”。这种出题方式明摆着是在考工程习惯你做功能的时候有没有考虑异常输入有没有关注资源开销代码能不能在review阶段经得起追问所以我不建议任何人只靠背题来应付这套试卷。你背得下某一道题的答案但你背不下出题人对“工程素养”的执念。最好的准备方式就是老老实实把手里的C代码写干净、写稳。2. 核心考点拆解C语言基础与内存管理C语言基础是这份试卷的绝对大头。按照我当时考完的回忆大概有三分之一到一半的题目都和C语言本身的细节有关而其中最核心的就是指针和内存。如果你这两块不扎实整套卷子基本就垮了。2.1 指针与数组笔试里最不讲情面的部分指针和数组在C里是一对纠缠不清的概念。试卷里很典型的一类题是让你写出下面代码的输出int a[5] {1, 2, 3, 4, 5}; int *p a; printf(%d\n, *(p)); printf(%d\n, *p); printf(%d\n, (*p)); printf(%d\n, *p);这道题的答案是1、3、3、4。如果你平时写代码习惯性地“凭感觉”用指针很容易在第一和第二个printf上栽跟头。*(p)是先取值再移动指针所以输出1*p是指针先自增再取值p此时指向a[2]输出3(*p)是取p指向的值然后自增所以先输出3但数组里a[2]已经变成4了最后一句*p输出4。这种题目本身不复杂但它要求你脑子里有一个非常清晰的“指针指向哪表达式求值顺序是什么”的模型。我的建议是备考阶段凡是遇到指针相关的表达式别靠猜把运算符优先级表打印出来贴在桌上对一遍。优先级和结合性是C笔试的送分题也是送命题关键看你有没有真正理解。另一个高频考点是数组名和指针的差异。比如sizeof(a)和sizeof(p)的区别a作为数组名时sizeof返回整个数组的字节数而p是指针不管指向什么在64位系统上sizeof(p)通常就是8。很多人在笔试里随手写sizeof(a)/sizeof(a[0])算长度这个没问题但如果把a传进函数再在函数里sizeof(a)那结果就是8而不是20了。这就是数组作为函数参数时退化为指针的特性。2.2 内存布局与动态内存怎么答才不丢分内存管理相关的题目快手这份试卷也考得很细。大致有这几类一个C程序的内存分区代码段、数据段、BSS段、堆、栈哪些变量分别放在哪里全局变量、静态局部变量、普通局部变量的生命周期和作用域区别malloc/free配合的注意事项还有“free之后指针要置NULL”这种习惯问题经典的悬空指针和野指针成因分析。这里我要重点说一下内存泄漏的考法。笔试不会让你去写一个检测内存泄漏的工具它会给你一段代码让你找哪里可能泄漏。典型场景像这样char *get_str() { char *buf (char *)malloc(64); sprintf(buf, hello %s, world); return buf; } int main() { char *s get_str(); printf(%s\n, s); // 没有调用 free(s); return 0; }如果你只是在牛客网的选择题里判断“有没有语法错误”你可能觉得这段代码没问题。但面试官视角下main里少了free(s)就是一次明确的内存泄漏。这是工程C笔试和普通C语言考试最大的不同它默认你知道“谁分配谁释放”这条铁律也默认你有义务在代码里落实这一点。还有一类常见的坑是返回局部变量的地址。比如在函数里定义局部数组返回数组名然后外部去用。这在语法上编译会警告运行时大概率会得到脏数据因为局部数组在函数返回后栈帧就销毁了。正确做法要么用static修饰要么用malloc在堆上分配让调用方负责释放。关于内存对齐试卷偶尔也会来一道结构体大小的题目struct Node { char c; int i; short s; };在常见的64位Linux环境下默认对齐方式下这个结构体的大小不是1427而是12。因为int按4字节对齐char后面会填充3个字节short后面再填充2个字节保证结构体整体大小是最大对齐数的倍数。这类题考的是你对编译器行为的理解虽然实际开发中你可以用#pragma pack改变对齐但笔试默认就是标准情况。我个人建议备考时亲手写几段代码用printf(%zu\n, sizeof(struct ...))验证一下把结果记在心里。靠死背表格也能应付选择题但如果你能讲清楚“为什么会有padding”面试环节会加分不少。3. 字符串处理与经典笔试题型字符串是C语言笔试里占比最高的题型之一。快手这套试卷也不例外至少有一道完整的编程题是围绕字符串展开的。它之所以这么重要是因为C语言里没有原生的字符串类型一切字符串操作都建立在字符数组和指针的基础上稍不注意就是越界、乱码、空指针。3.1 字符串逆序的三种写法字符串逆序几乎是C笔试的“开胃菜”但它很能看出代码功底。同样是逆序不同人写出来的代码风格完全不一样。我总结三个层次第一个层次直接用下标首尾交换void reverse(char *s) { int len strlen(s); for (int i 0; i len / 2; i) { char tmp s[i]; s[i] s[len - 1 - i]; s[len - 1 - i] tmp; } }这是最容易想到的写法只要能正确处理下标边界就行。第二个层次用指针方式实现void reverse(char *s) { char *left s; char *right s strlen(s) - 1; while (left right) { char tmp *left; *left *right; *right-- tmp; } }这个写法更“C味”也体现了对指针运算的熟悉程度。第三个层次考虑边界和性能。比如strlen会遍历一遍字符串如果你要逆序的同时还要判断字符串是否为空就要注意空字符串时right s - 1这种越界风险。另外有些题目要求“原地逆序且空间复杂度O(1)”就不能申请新数组有些题目则要求“单词级逆序”比如把“hello world”变成“world hello”那就需要先整体逆序再逐单词逆序。我在备考时习惯把常见的字符串操作都手写一遍包括逆序、查找子串、去空格、判断回文、数字字符串与整数互转。这些题单独看不难但组合在一起就是快手笔试的难度所在——你不仅要对还要写得快、写得稳。3.2 常见字符串笔试陷阱字符串题最坑人的地方不是算法本身而是C语言的字符串结尾符\0。笔试里经常会出现这种代码char str[10]; strcpy(str, hello); printf(%s\n, str); // 问str数组的内容是什么答案是前6个字符是hello\0后面4个字符是未初始化的垃圾值。如果用strlen(str)结果正常是5因为strlen遇到\0就停了。但如果用printf(%s)打印遇到\0也停所以输出是正常的hello。真正容易出错的是“自己拼接字符串”的情景。比如你想把两个字符串拼接后放入一个缓冲区如果直接写char buf[16]; char *a hello; char *b world; strcpy(buf, a); strcat(buf, b);这个代码如果a和b长度之和超过缓冲区大小就会缓冲区溢出。在笔试里这类题目会以“判断代码是否有问题”的形式出现你需要指出strcat可能造成越界并给出修复方案如使用snprintf或先计算长度再分配空间。另一个常见的坑是字符串常量与字符数组的差异。char *p hello和char arr[] hello是完全不同的p指向只读的字符串常量区修改p[0]是未定义行为运行时大概率崩溃arr是栈上的可写数组修改arr[0]没问题。很多同学在这上面翻车是因为面试官问“下面哪个字符串可以修改”时凭直觉以为指针和数组等价。还有一个好用的细节char *p hello用sizeof(p)是8指针大小sizeof(hello)是6包含末尾的\0而sizeof(arr)也是6。这些数字看起来琐碎但在笔试里就是分。4. 数据结构与算法题工程C的进阶关卡快手工程C的笔试并没有把算法题压到LeetCode hard级别但数据结构的基本功是绕不开的。链表、二叉树、排序和查找这几样东西是C工程师日常打交道最多的数据结构。C语言因为没有STL这种现成容器所有结构都要你自己建、自己销毁所以笔试非常喜欢考“手写实现”。4.1 链表和二叉树必须手写熟练链表题在快手笔试和面试里出现频率极高因为它的操作能同时考察指针理解、边界处理和代码严谨性。比如单链表反转这是最经典的题。一个标准实现struct ListNode { int val; struct ListNode *next; }; struct ListNode *reverseList(struct ListNode *head) { struct ListNode *prev NULL; struct ListNode *cur head; while (cur ! NULL) { struct ListNode *next cur-next; cur-next prev; prev cur; cur next; } return prev; }这个代码看起来简单但每次笔试都有人写错。常见错误是没保存next就改了cur-next导致链表断掉还有一个是循环结束条件写成cur-next ! NULL结果最后一个节点没有被反转。我的建议是这种基础操作当场在纸上画一下节点变化图画三步基本就清楚了。二叉树相关的考点也很集中比如前序/中序/后序遍历的递归与迭代实现、层序遍历、求树的高度、判断平衡二叉树。这些题目本身不难但用C语言写的时候有一些小坑比如递归前要判断root NULL迭代遍历时用数组模拟栈要注意容量问题。我备考时专门把二叉树的常见操作都写成C代码不是为了比赛而是为了让自己习惯在没有STL的环境里也能组织代码。快手的业务比如评论区的数据结构、关系链的存储结构底层很多时候都有树的影子。笔试中考二叉树某种程度上也是在考察你有没有理解这种基础结构的本质。4.2 笔试题的算法难度边界据我回忆快手工程C这套笔试的算法题主要集中在排序、二分查找、双指针、简单动态规划这类“中规中矩”的题。它不太会出那种需要非常精巧数学推导的题更注重基础算法在C语言框架下的实现能力。比如排序它会问你“快速排序怎么实现”“手写一个归并排序”“排序算法的时间复杂度对比”。快速排序的实现有很多细节比如基准值的选择、递归的终止条件、交换元素时指针移动的时机。笔试里让你写quick_sort不要只背模板要理解它为什么能把元素放到正确的位置上。二分查找也是高频考点。标准写法要注意int binary_search(int *arr, int n, int target) { int left 0, right n - 1; while (left right) { int mid left (right - left) / 2; if (arr[mid] target) { return mid; } else if (arr[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }这里我特别推荐用left (right - left) / 2而不是(left right) / 2因为后者在极端情况下可能整数溢出。虽然笔试里的数据量不一定触发溢出但这是一个很好的“工程思维”加分点面试官会注意到你写代码时在考虑极端情况。动态规划在快手这套试卷里通常不会太难可能是一道经典的爬楼梯、最大子数组和或者背包问题的简化版。C语言写DP题时主要注意数组大小和初始化。我记得有一道题是求最长递增子序列用O(n^2)的DP就能过但需要你开一个dp数组并正确设置初值1。这类题考点其实是你能不能把一个动态规划过程想清楚而不是考你多高深的优化技巧。5. 系统编程与工程化细节如果说前面那些还属于“通用C语言考试”的范畴那这份试卷里让我觉得最有区分度的部分其实是系统编程相关的题目。这部分直接对应到快手的后端和客户端业务场景也是你光刷LeetCode练不出来的东西。5.1 进程、线程与并发常见考点快手工程C笔试涉及了不少进程和线程的基础题。比如进程和线程的区别进程间通信的方式有哪些管道、消息队列、共享内存、信号量、Socket等线程同步的机制互斥锁、条件变量、读写锁、信号量什么是死锁产生死锁的四个必要条件是什么多线程程序中C语言的全局变量访问为什么可能出问题。这类题在笔试里往往以选择题或简答题形式出现。但你别以为记住定义就够了它会给你一段多线程代码让你指出数据竞争问题。比如int counter 0; void *increment(void *arg) { for (int i 0; i 100000; i) { counter; } return NULL; }如果同时创建两个线程执行increment最终counter不一定等于200000。因为counter不是原子操作它在底层可能是“读取-修改-写回”三步两个线程并发执行时会出现覆盖写。你需要指出这个问题并说出修复方案比如用pthread_mutex_t加锁或者用__atomic_add_fetch原子操作。这种题目考的是并发编程的直觉。我的建议是不只要知道“有锁”这个概念还要能大致理解锁的开销和临界区。如果你在笔试里能写出“锁的粒度尽量小避免在加锁区域做耗时的I/O操作”这类工程经验会非常加分。5.2 文件操作与I/O文件读写也是工程C笔试的一个常客。C语言里最经典的是用fopen、fread、fwrite、fclose这套标准库函数。考题经常会问fopen以什么模式打开文件如“r”“w”“a”的区别为什么每次fopen之后一定要检查返回值是否为NULL文件读取到一半出错怎么办文本文件和二进制文件在读写时的差异大文件读取时是一次性读入内存还是用缓冲区逐块处理。其中“逐块处理”这个考点很值得展开一下。如果让你实现一个程序把一个文件复制到另一个文件最高效的方式是设置一个缓冲区比如char buf[4096]循环调用fread和fwrite。千万别写成while ((c fgetc(in)) ! EOF) fputc(c, out);这种逐字符读写效率很低因为每次调用都有函数调用开销。笔试里如果给你一段逐字符复制的代码让你优化你能想到用缓冲区这就是得分点。更高一层的思路是使用操作系统级别的sendfile或mmap不过在C标准库层面试卷一般不会要求到那么深。关于文件I/O还有一个考点是与标准输入输出相关的重定向和管道。比如命令行执行./a.out input.txt output.txt程序内用scanf和printf就会自动从文件读取和写入。这个知识点在笔试中通常结合进程通信来考需要你有Linux环境下调试程序的实际经验。5.3 编译链接与程序运行的过程我觉得很多校招生容易忽略的一块内容是程序从源码到可执行文件的过程预处理、编译、汇编、链接以及编译和链接分别做什么事情。快手这份试卷里出现过不少相关的选择题和填空题。比如头文件中的#include在预处理阶段被展开宏定义在预处理阶段被替换编译阶段生成汇编代码汇编阶段生成目标文件.o链接阶段把多个目标文件和库文件合成可执行文件声明和定义的区别为什么头文件里一般只放声明不放定义static修饰全局变量和函数的作用是什么extern是干什么的。这些知识看起来简单但如果你只是在IDE里点“运行”按钮完全没研究过编译过程遇到这类题会有点蒙。我的备考建议是实际用gcc -E、gcc -S、gcc -c分别看预处理、汇编和编译生成的文件亲手过一遍就全明白了。还有一个必备技能看懂编译器报错和警告。笔试不会考你背错误码但代码改错题里经常出现“数组越界”“使用了未初始化的变量”“返回局部变量地址”这类问题其实就是编译警告里常见的类型。你在日常练习时养成“编译不开警告不看输出”的习惯做这种题就会非常顺手。6. 笔试中的高频陷阱与排查技巧实录最后这部分我想重点还原一下真实考试过程中容易出的问题。这些问题不仅在快手笔试里会遇到在几乎所有大厂C/C笔试里都是通用的。6.1 编译运行环境与答题注意事项快手2020秋招用的在线笔试平台编程题是可以在线编译运行的环境以Linux为主编译器是GCC。这听起来没什么但有几个细节很重要第一在线笔试的main函数怎么写。有些题目要求你实现一个函数比如“完成reverse函数”那你就只写函数体不要自己再加一个单独的main去调用否则可能和平台预置的测试框架冲突。但也有题目要求你写完整的可运行程序此时main函数一定要写标准形式int main(void)或int main(int argc, char *argv[])别用void mainGCC下虽然能编译但会显得不专业。第二注意头文件的引入。笔试平台往往已经预置了一些常用头文件但保险起见用到什么就写什么。比如用了strlen就要#include string.h用了malloc就要#include stdlib.h。有的同学在本地环境IDE自动补全了头文件一上平台就报“implicit declaration of function”全是因为漏了头文件。第三不要输出多余内容。有些同学为了调试方便在代码里加了很多printf交卷前忘记删掉结果输出格式不对被判0分。在线笔试是“严格输出比对”的多一个空格、多一行换行都可能出错。我的习惯是提交前把代码里所有的调试输出注释掉再跑一遍样例。第四数据范围一定要看清楚。C语言里int的范围、数组开多大、是否需要使用long long这些直接决定你是否能通过隐藏用例。我记得有一道题数据范围较大如果用冒泡排序会超时必须换成快排或归并。这类题就是考你对复杂度有没有概念。6.2 常见笔试“翻车”问题速查我整理了一个自己在备考阶段总结的“笔试翻车清单”分享给大家。基本覆盖了C语言笔试最容易出问题的地方问题类型典型表现解决思路指针悬空free之后没有置NULL二次freefree后立即置NULL或者重新设计所有权逻辑数组越界字符串拷贝时缓冲区不够使用snprintf或者先计算所需长度再分配返回值错误函数直接返回局部数组名改为static数组或malloc动态分配符号优先级*p和(*p)傻傻分不清牢记后缀自增优先级高于解引用括号优先头文件缺失隐式声明报错每个用到的库函数都确认对应头文件死循环二分查找left和right更新错误每次循环用简单用例手动走一遍整数溢出(leftright)/2在极限处溢出统一写成left (right - left) / 2动态规划初始值DP数组初值没设对先明确状态含义再初始化边界状态多线程数据竞争多个线程同时修改全局变量加互斥锁或使用原子操作文件打开失败fopen后不判NULL直接使用每次打开后立刻判断失败则处理错误这张表我建议打印出来笔试前半小时看一眼非常有用。尤其是指针悬空和数组越界这两类问题在快手这份试卷的选择题里反复出现本质上是同一个工程思维你要对自己的内存负责。我还要特别提一个容易被忽略的坑——scanf的返回值。笔试里让你“读入多个整数直到EOF”时正确写法是int x; while (scanf(%d, x) ! EOF) { // 处理 x }有些同学写while (scanf(%d, x))这样当读入0时循环也会退出因为scanf返回的是成功匹配的参数个数。这个细节很基础但确实每年都有人栽。6.3 我的备考复盘与一点建议因为这份试卷的特殊性我当时的备考策略并不是“刷更多的题”而是“把C语言重新学一遍”。具体做了四件事第一把《C Primer Plus》里的指针、数组、字符串、结构体、文件I/O、预处理器这几章重新精读了一遍每一章后面的习题都动手写。这本书对C语言的细节讲得非常透彻比直接刷LeetCode对笔试的帮助更大。第二把曾经写过的所有C语言小项目翻出来重新检查每一处内存分配和释放、每一个数组边界。这个过程非常痛苦但也让我真正理解了“谁分配谁释放”在代码里应该是什么样子。第三用牛客网和LeetCode的题库刷了大概100道C语言相关的题重点不是题量而是每道题都尝试写出最稳的版本然后对比题解看看有没有更好的边界处理方式。第四找同学互相出题考。两个人都按照“快手工笔风格”给对方出选择题和编程题。这种方式效果意外地好因为出题的过程逼着你去思考考点答题的过程又检验了你真实的反应速度。最后说一句实在话快手这套工程C笔试它不浪漫不炫技甚至有些题目显得“老套”。但它考察的内容恰恰是C语言工程师日常工作中天天要用的东西。如果你能把这份试卷的每道题都吃透那你的C语言底子基本上就过关了。就算最终没有去快手这份扎实的基本功也会伴随你整个技术生涯。对于正在备考的读者我的建议很简单先把手里的C语言教材过一遍把每个例子亲手敲一遍然后找近三年的快手笔试真题限时模拟最后针对错题整理自己的避坑清单。这个过程走完你再去考任何一家大厂的C/C笔试都会从容很多。