尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

爱奇艺秋招C++笔试复盘:指针、内存与算法基础

爱奇艺秋招C++笔试复盘:指针、内存与算法基础 2019年秋天我坐在学校机房的笔试页面前面打开邮件里的链接看到试卷标题写着“爱奇艺2019秋招C方向笔试题B”。说实话第一眼是有点懵的因为这套题没有想象中那种大而全的Java框架题而是扎扎实实地在考C语言和C的基础指针、字符串、内存、排序、文件读写最后还有一道图算法。整理这份复盘是想给正在准备C方向校招的同学一些参考尤其是那些和我一样习惯性刷LeetCode、却容易在语言细节上翻车的人。这套B卷虽然来自2019年但里面考的基本功在今天依然适用而且很能反映视频技术类公司对C工程师的底层要求。1. 从B卷看爱奇艺秋招C方向的出题逻辑1.1 我印象里的卷面结构先说卷子本身。不同年份、不同批次的笔试题目肯定有变动我按自己的回忆和一些考友的面经把B卷的题型框架还原成了下面这张表题型大致数量考察方向单选题10题左右C语言基础、指针、数组、运算符优先级多选题5题左右C特性、内存管理、STL使用细节填空题5题左右程序输出结果、宏定义展开、sizeof与strlen编程题2题字符串处理、排序或图算法问答题1题文件读写、程序编译链接过程等单选题里很少出现“茴香豆的茴有几种写法”这种偏题更多是让你看一段简单代码判断输出、判断是否编译报错、判断运行期会不会崩溃。多选题则是把C的语法细节和STL容器的底层行为揉在一起考比如vector扩容、迭代器失效、map底层红黑树。填空题基本是程序输出题和计算题给你一段代码让你填最终打印的结果。编程题只有两道但分值占比很高。一道字符串处理题几乎是所有校招笔试的标配另一道则偏向算法B卷当时考了图的最短路径。问答题反而最意外考的不是“谈谈你对面向对象的理解”而是直接让你描述C语言文件读写的完整操作流程甚至在代码层面补全接口。1.2 这份卷子为什么值得反复做很多人觉得2019年的题太老没必要看。我反而觉得爱奇艺这套B卷的出题逻辑特别适合用来摸底。视频类公司的C岗位核心业务往往集中在播放器、客户端、音视频处理、CDN调度这些方向这些场景对底层的指针操作、内存生命周期、性能敏感度要求非常高。所以笔试题不会去考“Spring Boot怎么配置”这种框架题而是把C语言和C的底层能力放在最前面。换句话说这套题其实是在筛选“对内存有敬畏心”的候选人。你LeetCode能Accepted几百道不代表你能说清楚一个局部变量返回后为什么不能用你能把红黑树背诵如流不代表你手写冒泡排序的时候不会忘记处理边界。这套B卷的价值就在于它能逼你把C/C语言本身的地基重新打一遍。准备这套题的过程比刷十套Java面试题更能让你看清自己离一个合格的C工程师还差多远。2. 选择题里的C语言陷阱指针、字符串与生命周期2.1 指针的“值传递”和“地址传递”怎么区分B卷选择题里有一类高频题核心就是问“这段代码能交换这两个变量吗”。很多人第一眼觉得能因为看到了指针但实际上指针也是值传递。看这段经典代码void swap(int a, int b) { int tmp a; a b; b tmp; }调用swap(x, y)之后x和y不会变。因为参数a和b是实参的一份拷贝函数内交换的是副本。正确写法是用指针传递变量的地址void swap(int *a, int *b) { int tmp *a; *a *b; *b tmp; }调用时写成swap(x, y)函数通过地址访问并修改原始变量才能达到交换效果。如果是在C里还可以用引用void swap(int a, int b) { int tmp a; a b; b tmp; }选择题不会只考这一层它还会进一步问如果要在函数里修改外部指针的指向比如让一个char *p指向新分配的内存那参数应该是什么答案是二级指针char **p或者C里的char *p。很多人在这一步开始糊涂根源在于没有理解“指针本身也是一个变量也有地址”。一级指针传递的是指针变量的值函数内修改的只是指向二级指针传递的是指针变量的地址函数内才能改外部指针本身。2.2 字符串那些“看上去对跑起来崩”的写法字符串是这套选择题的重灾区。爱奇艺B卷里有一道让我印象很深的题问下面两行代码哪个会崩溃char *p hello; char arr[] hello; p[0] H; // 这里可能崩溃 arr[0] H; // 正常运行第一行char *p hello里的字符串是字符串字面量保存在只读的常量区修改它会触发未定义行为很多环境直接段错误。第二行char arr[] hello是在栈上申请了一个数组把内容拷贝进来修改数组元素是合法的。这个点的本质是“指针指向哪里”和“字符串存储在哪里”的问题而不是“字符指针能不能修改”。还有一道题是考sizeof和strlen的区别char arr[] hello; printf(%zu %zu\n, sizeof(arr), strlen(arr));输出是6 5。sizeof(arr)计算的是整个数组的字节大小包含末尾的\0所以是6strlen数到\0就停所以是5。这个区别很多人笔试前背过但到了代码题里写memcpy(dst, src, strlen(src))时还是会忘记把\0一起拷过去。爱奇艺的选择题经常把这两个函数放在一段内存操作代码里混合考本质就是看你有没有吃透字符串的结尾机制。2.3 内存管理堆、栈、全局区、常量区多选和填空里内存管理出现频率非常高。考法通常是给一段代码问变量分别存在哪个区域或者问程序运行的结果是什么。我把几个区域整理成一张表笔试前直接背内存区域存放内容生命周期典型例子栈局部变量、函数参数函数调用期间int a 1;堆动态分配的内存malloc/free或new/delete管理malloc(10)全局/静态区全局变量、static变量程序开始到结束static int cnt;常量区字符串字面量等程序开始到结束helloB卷里出现频率最高的错误代码是返回局部变量地址char *func() { char buf[64]; strcpy(buf, hello); return buf; }这里的buf是栈上数组函数返回后栈帧被回收内存内容在理论上已经不合法了。虽然有时候碰巧还能读出hello但这是未定义行为随时可能被其他函数调用覆盖。正确做法是使用static局部变量、传入外部缓冲区或者在堆上malloc然后由调用方负责free。笔试里只要看到“返回局部数组名”这种写法基本可以直接判定为错误选项。还有一个配对问题malloc对应freenew对应deletenew[]对应delete[]。C和C混用时千万不能用free去释放new出来的对象否则析构函数不会调用在C里就是各种诡异的内存错误。3. 编程题字符串逆序、排序与图算法的考场实现3.1 字符串逆序的三种写法及考点爱奇艺B卷编程题的第一道大概率是字符串处理。“字符串逆序输出c”这种基础题几乎是送分题但越是送分题越容易在边界条件上丢分。我拿到题后第一反应是写原地双指针void reverse(char *s) { if (s NULL) return; int left 0; int right strlen(s) - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } }这里有两个容易踩的坑。第一个是没有判空如果传入NULL直接调用strlen会段错误。第二个是题目如果给的是字符串字面量比如char *s hello你传进去后在原地修改会崩溃。所以写题之前一定要确认输入是字符数组还是字符指针。有的笔试会要求用递归实现。递归版本也不难但要注意递归终止条件和交换逻辑void reverse_recursive(char *s, int left, int right) { if (left right) return; char tmp s[left]; s[left] s[right]; s[right] tmp; reverse_recursive(s, left 1, right - 1); }如果笔试环境允许用C也可以直接用std::reverse(s.begin(), s.end())但面试官往往更希望你展示自己处理边界的能力。我当时的策略是先写一个双指针版本然后在注释里补充递归版本的思路这样既保底又能展示思考深度。3.2 冒泡排序为什么是面试官最爱B卷编程题如果不考字符串就非常容易考排序。冒泡排序在“热门搜索词”里常年霸榜不是没有原因的。它算法简单、稳定、便于手写而且非常适合用来考察你对循环边界和提前终止的理解。我按最标准的写法给大家一个可以直接抄的版本void bubble_sort(int a[], int n) { if (a NULL || n 1) return; for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (a[j] a[j 1]) { int tmp a[j]; a[j] a[j 1]; a[j 1] tmp; swapped 1; } } if (!swapped) break; } }这里的swapped标志是我特别想强调的优化。如果某一轮遍历没有发生任何交换说明数组已经有序可以提前退出。最好情况下数组本来就有序时间复杂度是O(n)最坏和平均都是O(n^2)。稳定性方面冒泡排序是稳定排序相同元素的相对位置不会改变。笔试做题时我建议先写一个有标志位的版本然后在注释里说明“如果不加标志位就是普通冒泡复杂度最坏O(n^2)”。这样能向阅卷人传递出你不仅会写代码还理解复杂度和优化点。另外有些考题会问“冒泡排序和选择排序的区别”核心区别就在于冒泡排序的交换可能发生在相邻元素之间而选择排序每轮只找到最值后交换一次所以冒泡排序是稳定的选择排序通常不稳定。3.3 如果考到最短路迪杰斯特拉的C实现套路B卷编程题的第二道我印象里是图的最短路径。当年“迪杰斯特拉c”这类搜索词特别多说明很多人都在临时抱佛脚。迪杰斯特拉Dijkstra算法的核心是贪心每次从未访问的节点中选一个距离源点最近的节点然后松驰它的所有邻边。因为笔试环境通常不会给复杂的大图用邻接矩阵实现最直观也最容易保证正确性#include bits/stdc.h using namespace std; const int INF 0x3f3f3f3f; void dijkstra(int n, vectorvectorint graph, int src, vectorint dist) { vectorbool visited(n, false); dist.assign(n, INF); dist[src] 0; for (int i 0; i n; i) { int u -1; int minDist INF; for (int j 0; j n; j) { if (!visited[j] dist[j] minDist) { minDist dist[j]; u j; } } if (u -1) break; // 剩下的节点不可达 visited[u] true; for (int v 0; v n; v) { if (!visited[v] graph[u][v] ! INF) { if (dist[u] graph[u][v] dist[v]) { dist[v] dist[u] graph[u][v]; } } } } }这里graph[u][v]存的是边的权值不存在就设为INF。每次循环里先找当前距离源点最近且未访问的节点u再更新所有和u相邻的节点v。如果u -1说明剩下的节点都不可达可以提前结束。这个版本的复杂度是O(n^2)在一般校招笔试的数据范围下足够用。如果你还有余力可以写一个用优先队列优化的版本复杂度是O((VE)logV)。但在B卷这种考察基础的场合先把邻接矩阵版本写对远比强行写堆优化更重要。我当年就是因为想炫技堆优化写到一半发现优先队列里存的pair排序搞反了反而耽误了时间。考场上的第一原则是先拿稳分再谈优化。4. 文件读写与程序运行细节容易丢分的问答4.1 C语言文件读写操作代码模板问答题考文件读写时很多人第一反应是“这也太基础了吧”但真到写的时候不是忘了判空就是忘了用返回值。C语言文件读写操作的标准流程其实很固定先用fopen打开文件检查是否成功然后用fread/fwrite读写最后用fclose关闭。我给大家一个可以直接套用的模板#include stdio.h #include string.h int copy_file(const char *src_path, const char *dst_path) { FILE *src fopen(src_path, rb); if (src NULL) { perror(fopen src); return -1; } FILE *dst fopen(dst_path, wb); if (dst NULL) { perror(fopen dst); fclose(src); return -1; } char buf[1024]; size_t n; while ((n fread(buf, 1, sizeof(buf), src)) 0) { size_t written fwrite(buf, 1, n, dst); if (written ! n) { perror(fwrite); fclose(src); fclose(dst); return -1; } } fclose(src); fclose(dst); return 0; }这段代码有几个细节值得注意。第一fopen失败后一定要处理不要在空指针上继续操作。第二打开文件时用rb和wb二进制模式在Windows环境下可以避免换行符被自动转换在Linux环境下没有区别。第三fread的返回值是实际读取的元素个数用while循环读到文件末尾。第四fwrite写出的字节数可能不足所以要检查返回值。问答题通常还会追问如果文件很大一次性读取到内存再写入有什么问题答案是不能一次性读入应该用缓冲区循环读写避免内存被超大文件耗尽。这个点你只要在模板代码里体现出来就已经比其他考生高了一截。4.2 宏定义、typedef和const的边界B卷的填空题喜欢考宏定义展开不是让你背“宏和函数的区别”而是让你写出展开后的结果。最经典的是这个#define SQUARE(x) x * x int a SQUARE(3 1);很多人直接算成16但宏是纯文本替换替换后变成3 1 * 3 1按优先级计算结果是7。正确写法是给参数和整个表达式都加括号#define SQUARE(x) ((x) * (x))这样SQUARE(3 1)展开成((3 1) * (3 1))结果才是16。还有一类题是区分const限定的是指针还是指针指向的对象。我提供一个速记方法从右往左读const修饰的是谁。const char *p表示*p是const即不能通过p修改指向的内容但p本身可以指向其他地方char * const p表示p本身是const即不能修改指针的指向但可以通过p修改内容。笔试里如果给出一堆const在不同位置的变量让你判断哪个是“指针常量”、哪个是“指向常量的指针”用这个方法可以避免混淆。4.3 程序从源码到运行编译链接过程最后一道问答题也可能考“C程序从源代码到可执行文件经历了哪些过程”。答案很简单预处理、编译、汇编、链接。但阅卷人想看的不是这四个词而是每个阶段做了什么。预处理阶段把#include头文件展开、替换宏、处理条件编译编译阶段把预处理后的文件翻译成汇编代码检查语法错误汇编阶段把汇编代码转成机器码生成目标文件.o或.obj链接阶段把多个目标文件和库文件合并成一个可执行文件解决符号引用。日常里常见的“未定义引用”错误就是在链接阶段找不到函数实现“重复定义”错误是同一个符号在多个目标文件里都出现了。笔试里如果时间允许可以在答案里补充一个实际的操作命令会显得更有经验gcc -E main.c -o main.i # 预处理 gcc -S main.i -o main.s # 编译 gcc -c main.s -o main.o # 汇编 gcc main.o -o main # 链接这里需要注意实际做题时不要只写概念最好能把它和C开发环境联系起来。很多同学用VSCode配置C/C环境失败本质也是对编译流程不理解以为按一个F5就能跑实际上中间涉及了编译器路径、头文件路径、链接器配置一堆问题。能把编译链接过程说清楚的人一般在环境配置上也不会太稀里糊涂。5. 按照这套题准备一份可复用的笔试复盘清单5.1 还原考场做题顺序和时间分配在线笔试的时间一般在一个半到两个小时题目数量看起来不多但每个空、每段代码都可能藏着坑。我建议按下面的节奏来分配亲测比较稳单选题10题控制在20分钟以内。多选题5题控制在15分钟以内。填空题5题控制在15分钟以内。编程题2题每题20分钟共40分钟。问答题10分钟。剩余20分钟回填没把握的选择题检查编程题的边界条件。这里的关键是一定不要在一道选择题上死磕。在线笔试系统往往不允许回头改答案或者即使可以也很容易影响心态。遇到不确定的先用排除法排除掉两个明显错误的选项然后选一个相对合理的标记到草稿纸上等全部做完再回来想。5.2 我踩过的坑和后来总结的答题模板第一次做爱奇艺B卷的时候我在编程题上吃了大亏。字符串逆序那道题我直接用char *s hello测试本地运行没有崩提交到系统里却提示运行时错误。后来才发现本地的编译器可能把它放到了可修改区域但线上环境是只读的。这个经历让我养成了一个习惯拿到字符串题第一件事判断输入类型而不是急着写循环。还有一个坑是手写排序时忘记考虑空数组和单元素数组。比如冒泡排序如果数组长度为0或1外层循环应该直接跳过否则可能出现数组越界。很多在线判题系统的测试用例会专门卡这种边界你的主代码逻辑再对边界不过就是0分。我给自己总结的答题模板是第一步写清楚输入输出和函数签名第二步判空和边界第三步实现核心逻辑第四步在注释里写复杂度和优化点。这样做的好处是即使核心逻辑中有一两行写错阅卷人也能看到你的思考过程线条和思路的分不会全丢。5.3 刷题之外的资料建议如果只靠刷LeetCode很难覆盖爱奇艺B卷这种风格的题目。我用过并且觉得有效的资料组合是先刷一遍《C Primer Plus》里的指针、数组和字符串章节把课后题手写一遍然后看《Effective C》里关于资源管理的条款建立“谁分配谁释放”的意识最后在牛客网上刷C/C方向的历年真题重点做那些“给一段代码判断输出”的题。语法的坑可以通过刷题避免但底层的内存意识需要靠多写原生C/C代码来积累。我特别推荐大家用VSCode配置好C/C环境把本地的编译调试跑通然后刻意练习用gdb查看变量的地址和值。当你亲眼看到局部变量的地址在函数返回后变成一堆乱码时比背十遍“悬垂指针”都管用。爱奇艺这套B卷最扎心的地方就在于它考的东西没有一道超纲但你如果不亲手踩过内存的坑就是做不对。最后再分享一个我自己的真实体会。这套题考完当天我记了满满两页错题后来在另一家视频公司的面试里又遇到了几乎同样的字符串和指针问题。那次我很从容因为我被爱奇艺B卷狠狠教育过已经把char *和char []的差别、malloc和free的配对彻底刻进脑子里了。所以如果你也正在准备C校招别嫌这套题老静下心把每道题背后的知识点吃透远比盲目追求新题、偏题更有价值。笔试现场如果遇到文件读写题先写fopen判空再写业务逻辑这个好习惯能帮你救回不少分。
返回列表