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

资讯详情

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

欢聚时代2017校招C基础笔试题解析:指针、内存与字符串考点全攻克

欢聚时代2017校招C基础笔试题解析:指针、内存与字符串考点全攻克 每年到了九十月份校招笔试就像赶集一样热闹我看不少同学手里都攥着“欢聚时代2017校招笔试题目C基础类A卷”这份题在刷。这家公司当年在广州互联网圈可是响当当的YY语音、多玩游戏网、虎牙直播早期都属于欢聚时代阵营后端大量业务用C/C撑着所以校招笔试里C基础类的分量一直很足。2017年的A卷虽然过去几年了但里面的考点——指针、内存、字符串、链表——至今仍是各厂C语言笔试的标配值得认真吃透。这份卷子适合谁两类人特别需要。一类是准备校招、实习的计算机相关专业学生拿它当自测和查漏补缺的靶子另一类是已经工作但想跳槽做C/C开发、服务器开发的朋友用经典题目找回手感。它能帮你搞清楚一件事基础到底扎不扎实。很多在IDE里靠自动补全和不停调试混日子的人一上笔就露馅了。作为过来人我把自己对这套题的复盘、对考点的理解、还有当年踩过的坑一并整理出来不绕弯子直接干。1. 欢聚时代C基础笔试题的整体拆解1.1 命题风格不秀技巧专门打基础欢聚时代2017校招属于秋招季和很多大厂一样笔试筛选是第一道硬门槛。C基础类A卷整体给我的感觉是不偏不怪但处处是坑。试卷不会让你去背某个冷门库函数也不会考什么高深的算法技巧它的核心目标就一个——验证你有没有真正理解C语言里那些“老生常谈但多数人说不清”的东西。典型题型分布大概是单项选择加填空占一半考语法细节和概念辨析程序阅读题占一小半给一段代码让你说输出结果或者指出错误最后是手写编程题一般是两道链表和字符串操作居多。整套题做完最直观的感受是平时写业务代码感觉挺顺但一到这种题目上很多“我以为我会了”的地方就会翻车。为什么这么命题这和欢聚时代的业务形态有关。直播、社交、游戏平台这类业务后端要处理大量高并发连接和实时数据流C/C写服务器基础组件很常见对内存管理的要求极其苛刻。面试官不是要招一个“会背书的人”而是想招一个“写出来的代码不会把线上服务搞挂的人”。所以指针乱飞、内存泄漏、字符串越界这些问题在笔试里就会重点敲打。1.2 A卷的知识点权重哪些章节才是真正的重头戏根据我对整套题目的复盘C基础类A卷的知识点分布是有明显侧重的完全可以画出一张“高频考点地图”。考察方向出现频次典型问法权重说明指针与数组极高a1、指针数组与数组指针、函数指针核心中的核心几乎每题都沾边内存管理极高malloc/free配对、内存泄漏、栈与堆差异喜欢混在阅读题和编程题里考字符串操作高strlen与sizeof、字符串逆序、字符串拷贝编程题最常见的素材结构体与联合体中高字节对齐、sizeof计算结果选择题填空题常客宏定义与预处理中宏替换展开、#define陷阱考细节辨析的性价比之王链表操作中高反转链表、删除节点、找环编程大题或填空大题固定选手关键字辨析中static、const、volatile、extern概念题常客简单但易混位运算中低判断2的幂、交换数值、统计1的个数偶尔出现在选择和填空这个分布其实不是什么行业秘密几乎所有以C/C为主要开发语言的公司笔试基础卷都长这样。但欢聚时代的卷子有一个特点它特别喜欢把多个考点揉在一道题里。比如一道程序阅读题表面上在考指针自增运算实际还夹带了运算符优先级和函数传参方式。这也是我建议刷题时不要只看单题、要主动做“考点展开”的原因。2. 核心考点逐个击破从A卷反推复习清单2.1 指针与数组笔试中的“得分/失分区”指针和数组在C基础类A卷里是当之无愧的第一权重我甚至怀疑出题人是不是先把指针题出完了再往卷子里塞别的。重点难逃这几类第一类a1经典陷阱。比如int a[] {1, 2, 3, 4, 5}; int *p (int *)(a 1); printf(%d\n, *(p - 1));这里a是整个数组的地址类型是int(*)[5]步长是5个int所以a1直接跳到数组末尾之后。(int*)强转后再减1就回退到最后一个元素输出5。这个题每次出现都能干掉一批人核心是搞清楚“数组名a”和“a”类型上的差异。第二类数组名与指针的等价关系。a[i]等价于*(ai)。听上去简单但换成sizeof(a)和sizeof(p)很多人就懵。数组名在sizeof里代表整个数组而作为函数参数退化为指针后sizeof返回的就是指针大小。我记得当年身边就有同学直接在函数里sizeof(arr)/sizeof(arr[0])算长度结果在64位机器上算出来的数是2把数组当成了包含两个指针的结构体在处理。第三类指针运算的步长问题。p1到底跳几个字节取决于p的类型。char*跳1字节int*跳4字节某种结构体指针跳sizeof(结构体)字节。A卷里有一道填空是让算一个结构体数组里第三个元素的地址很多人算错就是因为结构体字节对齐没算明白。第四类函数指针。虽然考得没前几种多但偶尔会出一两个选择比如void (*func)(int)和void *func(int)的区别。记一个要点函数指针是“指向函数的指针”最外层的括号不能丢。我的建议是复习指针不要死记例子把“类型决定步长”这五个字刻在脑子里再往后刷题就顺很多。另外一定要自己在编译器里跑一遍这些代码亲眼看结果印象深得多。2.2 字符串与数组处处是越界地雷字符串题在A卷里出现频率极高而且不管选择题还是编程题都爱用。最经典的对比就是sizeof和strlenchar str[] hello; printf(%lu %lu\n, sizeof(str), strlen(str));sizeof(str)是6因为包含结尾的\0strlen(str)是5。如果写成char *p hello;那么sizeof(p)在64位系统上是8因为p是指针。这个点太基础了但年年考、年年有人错而且错得五花八门。字符串逆序是A卷编程题里十分可能出现的一道题也确实是搜索热词里的高频内容。给个经典写法void reverse(char *s) { if (s NULL) return; char *left s; char *right s strlen(s) - 1; while (left right) { char tmp *left; *left *right; *right tmp; left; right--; } }这里面有一个细节我特别想强调right初始化的位置是strlen(s)-1千万不能写成sizeof(s)-1否则尾部的\0会被挪到开头字符串就变成空串了。还有就是边界检查left right而不是left ! right防止空字符串或者奇数长度时指针交错。字符串拷贝和拼接也是一样A卷可能不会直接让你手写strcpy但会在程序阅读题里埋一个“缓冲区溢出”的坑。常见的隐患就是strcpy(dest, src)之前不检查dest的空间够不够。曾有一道题给了一个极小的char dest[6]然后往里面拷贝一个明显更长的字符串问运行结果很多人选了“正常输出”正确思路应该是“未定义行为可能崩溃也可能刚好运行取决于内存布局”。笔试考的就是你有没有这层安全敏感度。2.3 结构体、联合体、字节对齐算不对的字节数结构体这块A卷的考察重点非常明确——算大小。看起来简单实际上字节对齐的规则一展开一堆人就麻了。核心规则有三条第一结构体第一个成员的偏移量为0第二每个成员的对齐数取“自身大小”和“编译器默认对齐数”的较小值这个默认对齐数常见的是4或8第三结构体总大小必须是最大对齐数的整数倍。举个例子struct demo { char a; // 偏移0占1字节 int b; // 对齐4偏移要补到4 char c; // 偏移8占1字节 };a后面空3字节b占4字节c占1字节最后总大小补齐到最大对齐数4的整数倍也就是12。而如果调整顺序写成“int b; char a; char c;”总大小就变成8。这就是一位欢聚时代的后端工程师当年跟我说的结构体字段顺序影响内存占用不是玄学是高并发服务里抠内存的基本功。联合体则相反所有成员共享同一块内存大小取最大成员的长度。但注意如果要算对齐还得考虑最大对齐数。比如union u { char buf[9]; int n; };buf占9字节int对齐4所以union本身对齐数是4总大小补到12。A卷如果出这种题就是故意让你在“9”和“12”之间纠结。字节对齐不是C标准强制规定而是编译器行为不同平台可能不一样。所以备考时务必按目标平台x86_64 Linux默认对齐一般是8来算。别拿32位机器的经验直接套64位这也是很多答案对不上的原因。2.4 宏定义与关键字辨析基础得让人大意A卷在宏定义上的考题风格极其统一给一个宏让你写出替换展开后的代码然后问输出是什么。经典陷阱是#define SQUARE(x) x * x int result SQUARE(2 3);展开后是2 3 * 2 3结果是11而不是25。正确写法是#define SQUARE(x) ((x) * (x))。这个题反复出现其实就是为了强调宏是文本替换不是函数调用。记住之后这类送分题就真成送分题了。关键字辨析里static和const是高频。static修饰局部变量时变量只初始化一次且延长生命周期static修饰全局变量和函数时限制为当前文件可见。const修饰指针最烦记住一个判断技巧看const和*的相对位置。const int *p是“p指向的内容不可变”int *const p是“p本身不可变”。A卷里出现过这样的题int value 10; int *const p value; *p 20; // 合法吗答案是合法因为p本身不能改但p指向的内容可以改。这个很多人记反了。volatile这个关键字也偶尔出现。它告诉编译器别优化对这个变量的访问必须每次都从内存重新读取。典型场景是硬件寄存器、多线程共享变量虽然现在多线程更推荐用原子操作。比较有意思的是A卷爱让它和const放一起辨析一个变量能不能同时是const和volatile答案是能比如只读的硬件状态寄存器程序不能改它但它会被外部硬件改变每次都该重新读。3. 典型真题还原与答题思路参考3.1 程序阅读题优先级和指针运算的混合双打有一道题印象深刻是一段“看起来什么都没做”的代码让你写出最终输出#include stdio.h int main(void) { int a[] {10, 20, 30, 40, 50}; int *p a; printf(%d\n, *(p)); printf(%d\n, *p); printf(%d , *p); printf(%d\n, *p); return 0; }第一个输出是*(p)这里先用后加所以输出a[0]即10然后p指向a[1]。第二个输出是*p因为后缀优先级高于解引用*等价于*(p)输出a[1]即20然后p指向a[2]。第三个是*p先解引用得到a[2]也就是30再自增变成31输出31。第四个是*p先自增指向a[3]解引用输出40。这题坑在哪很多人卡在第三个空不知道是给指针加还是给值加其实只要看清和*的相对位置就能拆解*p相当于(*p)处理的是值*p相当于*(p)处理的是指针。这类题我建议在草稿纸上画出数组和指针的指向变化一步一画不容易乱。另外一道常见的程序阅读题是函数传参问题void modify(int *p) { p (int *)malloc(sizeof(int)); *p 100; } int main(void) { int *ptr NULL; modify(ptr); printf(%d\n, *ptr); // 运行时可能崩溃 return 0; }很多新手以为通过modify(ptr)就能让ptr指向malloc的内存实际上函数参数是值传递ptr和p只是拷贝关系函数内部修改p不会影响外部的ptr。正确写法是传二级指针int **p或者返回分配好的指针。这种题在笔试里频频出现背后的核心考点就是“C语言只有值传递”这一句话。3.2 程序填空题链表的反转与删除链表填空在A卷属于拉开差距的题。链表本身不难难在写代码时容易断链、漏判断空指针。反转链表是出现率极高的一题我给出标准的三指针写法并且尽量把每一步的意图讲清楚struct node { int value; struct node *next; }; struct node *reverse_list(struct node *head) { struct node *prev NULL; struct node *curr head; struct node *next NULL; while (curr ! NULL) { next curr-next; // 先保存下一个节点防止反转后丢失 curr-next prev; // 当前节点指向前驱 prev curr; // 前驱前移 curr next; // 当前节点前移 } return prev; // 当curr为空时prev就是新链表的头 }填空题一般会抽掉其中一两行让你补齐。你要记住的不是代码本身而是三个指针的分工prev保存已反转部分的前驱curr是当前正在处理的节点next是临时保存的后续节点。缺哪个都不行。还有一种删除链表节点的题给定单链表的头指针和一个节点指针要求在O(1)时间内删除该节点不遍历链表找到它的前驱。经典解法是“偷梁换柱”将待删除节点后继的值复制到当前节点然后让当前节点指向后继的后继。翻译成代码就是void delete_node(struct node *target) { if (target NULL || target-next NULL) { return; } struct node *p target-next; target-value p-value; target-next p-next; free(p); }这个解法有个注意点如果待删除节点是尾节点后继为NULL还得老老实实遍历。所以严格来说O(1)解法只对中间节点有效。笔试时如果题目没限定最好把边界情况说明一下这也是加分项。3.3 手写编程题字符串与二分的实战演练编程大题里除了前面提到的字符串逆序还有一个我高度怀疑A卷会出现的题是“不使用第三方库实现字符串拷贝”char *my_strcpy(char *dest, const char *src) { if (dest NULL || src NULL) { return NULL; } char *ret dest; while (*src ! \0) { *dest *src; } *dest \0; return ret; }注意return ret不能省因为函数返回原始目标地址是为了支持链式操作比如printf(%s, my_strcpy(a, b))。另外src加const修饰是本分习惯能防止误改源字符串笔试阅卷人很吃这一套。另一个常客是手写二分查找。基础版int binary_search(int arr[], int n, int target) { int left 0; int 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; }写这题时大多数人都能写对主体但有一处细节值得留意mid的计算用left (right - left) / 2而不是(left right) / 2。前者可以有效防止left和right都很大时整型溢出。虽然笔试环境不一定能测出溢出但写上这行往往能看出你有没有写过海量数据处理场景的代码这种细节正是校招面试官想看到的素质。如果题目要求再高一点可能是“求旋转数组的最小值”或者“找目标值在有序数组中的左右边界”这就要在二分框架上加各种条件判断。A卷如果是普通投递批次的题目难度应该还是以基础二分居多。4. 备考方法论用一套卷子带动整个知识体系4.1 三轮复习法基础、刷题、模拟很多同学拿到一套题就直接开做做完对答案错了再看一眼解析然后换下一套。这种刷法效率其实很低。更推荐用“三轮复习法”来备考欢聚时代这类C基础笔试第一轮是“知识清扫”。别碰题先花两到三周把所有C语言基础章节过一遍重点放在指针、数组、字符串、结构体、内存管理、链表这几块。参考书目可以不限于经典教材翁恺老师的C语言视频、网上各种C语言练习题集都够用关键是每个知识点都要能用自己的话讲明白。第二轮是“专题刷题”。按知识点刷比如今天只刷指针相关题目明天只刷字符串相关题目。刷的过程中把错题归类是概念不清、代码写错还是边界遗漏。每个专题至少刷20道以上形成肌肉记忆。第三轮是“整套模拟”。找周末整块时间按真实笔试时长一般60到90分钟做整套卷子做完立刻批改。这轮的目的不是学新知识而是适应节奏让自己在时间压力下保持稳定发挥。用2017年A卷模拟就非常合适因为它难度适中、考点全面适合当试金石。三轮复习最忌讳的是只走第一轮就上考场因为编程题和阅读题需要大量练习才能练出“手感和眼力”。我在复习期间就吃了这个亏概念背得滚瓜烂熟一到手写链表反转就卡壳最后只能靠临时回忆硬写虽然过了笔试但面试环节被深挖实现细节时差点露馅。4.2 考场答题节奏先拿稳分再啃硬骨头做C基础类A卷这类题时间分配是有讲究的。我自己习惯的节奏是选择题和填空题控制在25分钟以内这类题每题分值相对密集而且很多是“一锤定音”的送分题前面做得越快后面程序阅读和编程题的心态就越稳。程序阅读题别着急看代码先看题目问什么——是问输出、问错误、还是问优化空间带着问题去读代码会比从头到尾硬读高效很多。遇到指针运算、运算符优先级这种容易看错的在草稿纸上画出每一步的变量变化不要凭直觉。手写编程题是拿分重点建议留30分钟以上。写之前先想清楚两个问题输入是什么输出是什么。然后用自然语言描述算法步骤哪怕写伪代码都行最后再翻译成C语言。写代码时注意几个细节变量命名清晰、缩进规范、处理空指针和边界条件。阅卷人看重的是“思路正确、代码可读”而不是某个炫技写法。如果编程题没思路千万别空着。写一个最朴素的暴力解法哪怕时间复杂度高一点也比白卷强。因为校招笔试的评分不是只看结果步骤分、思路分也是存在的尤其是手写代码题逻辑框架清晰的话哪怕有小问题也能拿到大半分数。4.3 从真题延伸出去把知识面撑开一套题刷完之后只对答案就结束那就太可惜了。我拿到一套校招真题做法是“每道题都要往外扩三步”这道题考的知识点是什么这个知识点还能衍生出哪些考法现实中写代码会怎么用到它。举一个具体的例子。如果碰到sizeof(结构体)的题不要只算完大小就完事可以延伸思考为什么要有字节对齐结构体字段怎么排才省内存缓存行对齐对高并发性能有什么影响把这些延伸问题弄明白哪怕面试官从笔试题目里挑一道追问你也能接得住。再比如碰到链表反转顺手把“判断链表是否有环”“找到环的入口”“两个链表找交点”这些同类型题目做了因为这些都是链表题里的高频变种。我当年刷完反转链表后又花了两个晚上专门做链表环相关问题结果面试时真的被面到了环形链表的入口节点问题当场心里就有底了。这就是“用一套题打下一片知识面”的杠杆效应。5. 从笔试到面试这套题隐藏的进阶线索5.1 笔试考基础面试问实战欢聚时代的校招流程一般经过笔试筛选接下来是技术面试。笔试题目里很多知识点仅仅是面试官深挖的“引子”。比如笔试考了指针与数组的关系面试可能让你手写一个“字符串连接函数”并分析内存分配问题笔试考了结构体对齐面试可能追问“如果我要让结构体按1字节对齐怎么做”对应的是#pragma pack(1)或__attribute__((packed))这类编译器扩展。所以我有一个很实在的建议笔试结束后千万别把卷子扔一边回去把每道错题整理成“面试可能追问问题”然后自己模拟回答一遍。这个过程比单纯背八股文有效得多因为问题是从你自己的错题里长出来的针对性最强。另外笔试涉及的“内存管理”在面试环节极可能升级成“内存泄漏排查”。面试官可能会问线上服务内存只涨不降你怎么定位我当年被问过类似的题当时的回答思路是先用top或ps看进程内存趋势再用valgrind检测泄漏点或者写脚本长时间监控。虽然欢聚时代当年的面试没有深入到具体工具但你能说出排查思路就已经比大多数人强了。5.2 从真题反推企业技术栈通过一份笔试题目其实可以倒推出不少公司的技术倾向。欢聚时代C基础类A卷重视指针、内存、字符串、链表这说明他们核心后端肯定离不开C/C而且大概率是在做高并发网络服务、音视频流媒体传输这类底层组件。对于拿到这份卷子的候选人来说如果通过笔试进入面试不妨在准备阶段顺便了解下公司的核心业务思考C语言在其中的用武之地。比如直播业务的海量用户同时在线连接状态管理用什么数据结构弹幕和聊天消息的广播字符串处理和缓冲区管理怎么设计音视频数据的编解码与传输对内存拷贝效率和字节序有什么要求。这些思考不需要多深但能体现你的岗位匹配度和技术热情面试时很容易成为加分项。我自己在准备时把“高并发服务器需要关注什么”梳理了一遍连接的建立与管理、内存池的分配与回收、多线程之间的数据同步、网络协议的处理结果发现这些话题全部绕不开C基础功底。这也从侧面说明A卷这份题虽然看起来“基础”背后的要求一点都不基础。5.3 刷透旧题吃透新变化2017年的题目放到现在部分内容依然适用但也有一些知识点需要结合当前技术环境做补充。比如嵌入式开发火起来之后C语言在资源受限环境下的考题越来越多位运算、内存对齐、volatile的使用场景会被反复追问。再比如Linux环境下C开发编译链接的过程预处理、编译、汇编、链接和相关命令也是近几年爱考的方向。另外现在的笔试越来越多的放到在线平台上进行代码需要在线编译运行这对手写代码的规范性要求更高因为平台会真的编译跑用例。不像当年的纸质卷写个大概思路或许能蒙混过去。所以刷2017年A卷时建议每道编程题都在本地环境敲一遍确保能编译通过、能跑通再谈其他。我个人的一个习惯是每道手写编程题不光写一种解法还会尝试第二种、第三种解法然后比较它们的优缺点。比如字符串逆序除了双指针法还能用递归吗能但递归在字符串很长时会有栈溢出风险。链表反转除了迭代还能用递归吗能但空间复杂度会变成O(n)。这些对比思考在面试中非常加分因为面试官最爱问的一句话就是“还有别的实现方式吗各自的优缺点是什么”5.4 简历之外的“软实力”同样重要准备笔试刷题很重要但我想多说一句诚然技术基础是门槛但校招不仅仅是技术考试。欢聚时代这类公司的校招笔试只是筛选的第一关之后还会有面试官综合考察沟通能力、解决问题的思路、团队协作意识。我见过不少技术很强的同学笔试分数很高但面试时由于表达过于紧张或者思路跳跃最终没能拿到offer。反过来也有笔试表现平平但面试交流顺畅、思路清晰的候选人被顺利录取。所以准备技术笔试的同时记得给自己留一些时间做模拟面试练习。找同学当面试官或者对着镜子把自己做过的题讲一遍。不要只讲答案要讲清楚思考过程为什么这么想有没有其他的路选这条路有什么代价。这种“可解释性”是面试官最看重的也是工作中写代码写注释、参与代码评审时真正需要的能力。我自己的经验是笔试是“入门的资格赛”面试才是“真正的决赛”。别把精力全部压在刷题上适度准备一些项目经历、实习经历里与C语言相关的细节比如你在某个项目里用到了链表还是数组、为什么选择这个数据结构、有没有遇到过内存问题、怎么解决的。这些真实故事比背一百道题都更打动人。说到底一份2017年的笔试题目能带给你的远不止几十道题的答案而是一套系统梳理C语言基础、研究一家公司技术倾向、补齐自身短板的方法。把眼光放长远把这份卷子当成跳板你会发现自己收获的不仅是一份offer还有扎实的功底和解决问题的底气。
返回列表