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

资讯详情

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

C语言基础笔试A卷全解析:从指针内存到字符串与循环考点

C语言基础笔试A卷全解析:从指针内存到字符串与循环考点 开头2017年春天我坐在欢聚时代笔试的机房里面前是一张A卷满屏的C语言基础题。说实在的当时心里有点慌——指针、字符串、内存、文件操作这些平时写业务代码时感觉都会可真到笔试卷子上才发现很多概念只是用熟了并没有想透了。笔试结束后我专门花了一周时间把整张卷子复盘了一遍又找了几位一起参加校招的同学对照答案才发现C基础类笔试的考察逻辑和学校的期末考试完全是两码事。这篇文章我想把这类C基础笔试卷的底层逻辑拆开讲清楚。不聊具体是哪一年的真题而是从题目类型、考察重点、手写代码的踩分策略、以及备考时间分配四个维度完整还原一张典型的C基础类A卷应该怎么应对。无论你是正在准备校招的应届生还是想补C语言基本功的开发者这篇内容都值得你花20分钟认真读一遍。1. C基础类笔试卷到底想从一百人里挑出谁1.1 为什么互联网公司到今天还考C基础先说一个很多人困惑的问题欢聚时代以直播、音视频、社交产品为主笔试为什么要考C基础而不是直接考Java或者前端答案其实很简单——2017年前后移动直播和音视频服务进入了高速扩张期底层要处理推流、拉流、编解码、网络传输、服务器高并发这些场景恰恰是C和C的主场。C基础不是老技术而是音视频、嵌入式、高性能服务端的底层语言。从笔试筛选逻辑来看C基础类A卷的目的不是考察谁会用C写出多牛的项目而是用最短的时间筛掉两类人一类是简历上写着精通C/C实际连指针和数组的关系都说不清的人另一类是只会调库、对底层机制完全没有概念的人。换句话说这份卷子考察的不是上限而是下限——你能不能达到一个合格C程序员的底线。1.2 题型分布与考察分级的常见模式根据我当时考完后的复盘以及和同批参加笔试的同学对照C基础类A卷的题型结构基本遵循这样一个规律选择题约20道覆盖语法细节、运算符优先级、类型转换、存储类别程序阅读题约4到5道给一段代码让你写出输出结果或指出错误编程题约2到3道通常是一道排序或字符串处理、一道文件读写或链表操作。这三类题型并不是平均用力而是有明显的梯度设计。选择题考的是你有没有踩过这些坑比如sizeof和strlen的区别、结构体对齐、宏定义的陷阱程序阅读题考的是你能不能顺着代码逻辑走到底比如递归调用、指针自增的先后顺序、静态局部变量的生命周期编程题考的是你能不能把思路用规范的代码落地这往往是最拉分的地方。补充一句A卷相对于B卷如果有的话通常更偏向基础语法和内存管理而B卷可能偏算法和数据结构。所以如果你拿到的是A卷反而说明公司更想知道你的C语言基本功是否扎实而不是你的算法竞赛水平有多高。1.3 考试节奏与时间分配策略这种卷子一般给90到120分钟时间看起来充裕但实际做题时你会发现前面的选择题很容易让人反复纠结。我的建议是选择题每道不超过1分钟拿不准的先标记跳过去程序阅读题每道控制在5到8分钟把执行过程一步一步写下来再作答编程题每道留出至少15到20分钟因为手写代码不仅要想清楚逻辑还要留出检查边界条件的时间。我见过不少同学在前面选择题上花了太多时间导致最后一道编程题匆匆写了几行就交卷。实际上编程题在总分中的权重往往高于选择题一道完整的、能跑通的编程题可能抵得上五道选择题的分值。所以时间分配的顺序应该是编程题保底程序阅读题求稳选择题拼广度。2. 指针与内存占比最高、也最容易露怯的两块2.1 指针的基础辨析const修饰的三种形态指针是C基础类笔试试卷里当之无愧的C位几乎每一道选择题都可能围绕指针展开。最常见的考法就是const关键字和指针的组合。给你三个声明让你判断哪个是指针本身不能变哪个是指向的值不能变const int *p; // p指向一个const intp本身可变*p不可变 int *const p; // p本身不可变p指向的值可变 const int *const p; // 两者都不可变这个知识点本身不难但考场上特别容易混淆。我有个记忆技巧看const靠近谁谁就不能变。const int *p中const靠近int说明int即指向的值不能变int *const p中const靠近p说明指针变量本身不能变。把这条规律记牢选择题基本不会丢分。2.2 指针与数组数组名不等于指针变量另一个高频考点是指针和数组的关系。笔试最喜欢的题目是问你sizeof(arr)和sizeof(p)有什么区别其中arr是数组名p是指向该数组首元素的指针。int arr[10]; int *p arr; printf(%lu\n, sizeof(arr)); // 40假设int占4字节10个元素 printf(%lu\n, sizeof(p)); // 864位系统上指针本身占8字节很多基础不扎实的同学会写两个都是40这就是没搞懂数组名在大多数表达式中会退化为首元素指针但在sizeof中不会退化这个关键区别。类似地arr和arr[0]的值相同但类型不同前者是int (*)[10]后者是int *笔试也经常拿这个设陷阱。2.3 二级指针、函数指针与指针运算A卷的选择题里还可能涉及二级指针和函数指针。二级指针常见于在函数中修改传入指针的值场景比如你想在函数内部为指针分配内存就必须传指针的地址也就是二级指针void alloc_memory(int **p, int size) { *p (int *)malloc(size * sizeof(int)); }函数指针则常用于回调场景笔试可能让你写出函数指针的声明语法或者判断int (*p)(int, int)和int *p(int, int)的区别——前者是一个指向返回int值、接受两个int参数的函数的指针后者是一个返回int *类型指针的函数声明。这种题只要见过一次就能记住但没见过就很容易写反。指针运算也是常客。p、p、(*p)、*(p)这四者的区别如果不拿笔在草稿纸上推演一遍很容易出错。核心是分清两个维度指针本身移动了没有指针指向的值变化了没有。2.4 内存管理的必考三件套malloc、free、野指针说到指针就一定绕不开内存管理。A卷的编程题或程序阅读题里malloc/free的使用规范几乎必考。最经典的考察点是malloc申请的内存是否检查了返回值为NULLfree之后是否立即将指针置为NULL是否存在内存泄漏即malloc和free是否成对出现是否存在重复释放或释放栈上变量其中free之后不置空是一个特别典型的坑。实际上free只是释放了堆上的内存指针变量本身仍然保存着原来的地址这个地址指向的内存已经被释放继续使用就会产生悬空指针。所以规范的写法是free(p); p NULL;笔试改卷时我看到很多同学free之后没有置空这在严格一点的批改里是要扣分的。虽然置空不改变程序的功能但它体现的是你对悬空指针这个风险的意识而这种意识恰恰是校招考察的核心素质。3. 字符串与数组手写题技术含量不高但区分度极高3.1 字符串处理函数的高频考法字符串处理在C基础类试卷中的出现频率非常高几乎每一份A卷都有一道和字符串相关的题目。考察形式可能有三种选择题里考strlen和sizeof的区别、程序阅读题里考strcpy和strcat越界的问题、编程题里让你手写strlen或字符串逆序。strlen和sizeof的区别是我见过的最基础的送分题但也是错误率最高的题之一。strlen是函数返回字符串的长度不包括末尾的\0sizeof是运算符计算的是变量或类型所占内存的字节数对字符数组来说会包括末尾的\0。如果是char str[] hellosizeof(str)是6strlen(str)是5。这几乎是笔试必考的题目没有之一。3.2 字符串逆序输出的完整实现从热词搜索量来看字符串逆序输出c和字符串逆序c语言pta是很多学习者正在找的内容说明这个题目在校招、PTA作业、编程练习中出现的频率都非常高。笔试最常见的字符串逆序题有两种变体一种是要求原地逆序不能额外开辟新数组另一种是要求写一个函数实现逆序并返回新字符串。原地逆序的核心思路是双指针一个指向字符串头部一个指向尾部交换字符并向中间移动char *reverse_str(char *s) { if (s NULL) return NULL; int i 0; int j strlen(s) - 1; while (i j) { char temp s[i]; s[i] s[j]; s[j] temp; i; j--; } return s; }这个写法有几个隐藏的踩分点。第一必须判断输入是否为NULL第二必须用strlen计算长度而不是用sizeof第三循环条件是i j还是i j虽然结果一样但i j逻辑上更严谨第四如果字符串为空串strlen(s) - 1会导致下溢为很大的无符号数所以最好先判断j 0就直接返回。这些细节如果你在笔试卷子上都写出来阅卷人对你的印象会明显不一样。3.3 数组越界字符串拷贝中的经典陷阱字符串拷贝strcpy的越界问题也是A卷的常客。试题一般会给一段代码char dest[5]; strcpy(dest, hello world);问你有什么问题。答案是dest只有5个字节而源字符串包括\0需要12个字节strcpy会越界写入造成缓冲区溢出这是典型的未定义行为。进一步追问可能是如何安全地拷贝字符串答案是使用strncpy并手动确保末尾有\0strncpy(dest, hello, sizeof(dest) - 1); dest[sizeof(dest) - 1] \0;注意一个细节strncpy在拷贝指定长度后如果源字符串长度不足会补\0但如果源字符串长度等于或超过指定长度它不会自动补\0。所以使用strncpy后手动置\0是必须的这个知识点的区分度很高会的人不多。3.4 字符数组与字符串常量的本质区别另一个让很多人栽跟头的点是字符串常量能否修改。笔试可能会给出这样一段代码char *p hello; p[0] H;问输出结果或是否存在错误。这个代码的问题在于hello是字符串常量存储在只读数据区p[0] H试图修改只读内存会导致运行时错误或未定义行为。正确写法应该是char p[] hello; p[0] H;这里的p是字符数组在栈上分配了可修改的内存。选择char *p还是char p[]去接收一个字符串字面量看起来差不多实际内存语义完全不同。笔试考的是你能不能看出这一行代码背后的存储布局差异。4. 流程控制和循环细节while与do-while只是开胃菜4.1 while与do-while的执行时机差异从热搜词c语言while和do-while区别能看出这个知识点是初学者的高频疑问也是笔试选择/程序阅读题喜欢出题的地方。核心区别只有一句话while是先判断后执行循环体可能一次都不执行do-while是先执行后判断循环体至少执行一次。笔试最容易出的一种题是给你一段读入用户输入直到输入非法的代码问你用while还是do-while更合适。正确思路是如果至少要读一次才能判断就用do-while。比如处理菜单选择先显示菜单再读入用户选项然后根据选项决定是否继续这种情况下do-while比while更自然。int choice; do { printf(请输入选项: ); scanf(%d, choice); } while (choice ! 0);4.2 for循环中分号的省略与陷阱for循环的变体也是A卷喜欢出的程序阅读题。例如int i 0; for (; i 5; i) { // ... }这是一种合法的写法省略了初始化表达式因为变量i在外面已经初始化了。再比如for (;;) { // 无限循环 }三个表达式全部省略等同于while(1)。笔试可能会问这两种写法是否是同一种东西答案是它们在语义上不完全相同——while(1)在编译器角度和for(;;)的优化处理可能存在差异但在语言标准层面两者都表示无限循环具体性能差异由编译器决定。还有一道非常经典的题for循环里使用逗号表达式。比如for (i 0, j 10; i j; i, j--) { printf(%d %d\n, i, j); }这里初始化表达式和迭代表达式都用了逗号运算符执行顺序是从左到右。笔试常考的是逗号表达式的值等于最后一个表达式的值这个规则在for循环里同样适用。4.3 break、continue与switch的隐藏规则break和continue的区别很简单break跳出整个循环continue跳过本次循环的剩余语句进入下一次迭代。但很多笔试题目不会直接问你定义而是给你一段嵌套循环让你数清楚跳出了哪一层。for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (j 1) break; printf(%d%d , i, j); } }输出结果是00 10 20还是00 01 02 10...答案是00 10 20因为当j 1时break跳出了内层循环外层i继续递增。如果你想让break跳出外层循环需要使用goto或者额外的标志变量这个话题很多笔试不会深入但偶尔会出现在附加题里。switch语句还有一个容易忽略的规则case标签后的代码如果没有break会继续执行下一个case也就是穿透fall-through。A卷很可能给出一段没有break的switch代码问你输出什么。这种题没有技术难度考的是细心程度和是否真正理解switch的执行机制。4.4 运算符优先级最容易在选择题白丢分的地方C语言的运算符优先级表格有15级笔试选择题几乎必考一题。最常见的陷阱组合包括*p到底等价于*(p)还是(*p)答案是前者因为后缀自增的优先级高于解引用a b 2到底是(a b) 2还是a (b 2)加法的优先级高于移位所以是前者*p.a到底是*(p.a)还是(*p).a成员访问运算符.的优先级高于解引用所以是*(p.a)这在结构体指针中经常被误用正确写法是(*p).a或p-aint *arr[5]和int (*arr)[5]的区别前者是指针数组后者是数组指针中括号的优先级高于解引用我的建议是不要死记硬背整张优先级表而是记住几条容易混淆的规则同时在代码中尽量用括号明确表达意图。笔试中如果你拿不准优先级就按最保守的方式在草稿纸上加括号推演不要赌自己的记忆。5. 编程题的踩分逻辑从冒泡排序到文件读写怎么写出让阅卷人愿意给分的答案5.1 为什么冒泡排序是A卷编程题的常客很多人不理解校招笔试题为什么还考冒泡排序这种简单的算法。其实这个选择有很现实的考量笔试现场手写代码时间有限、环境紧张冒泡排序能考出你三个层面的能力——写代码的熟练度、对边界条件的敏感度、以及基础的算法理解深度。面试官根本不指望你写出什么高深算法他们要的是一次写对这个确定性。冒泡排序的最简实现是双重循环void bubble_sort(int arr[], int n) { for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } }这个写法有四个关键细节也是阅卷时候的踩分点内层循环的终止条件是n - 1 - i而不是n - 1因为每一轮冒泡后最大的元素已经沉到末尾无需再比较第二个细节是i从0到n - 2如果写成i n虽然也能跑但不专业第三个细节是交换时必须用临时变量不能直接arr[j] arr[j1]然后arr[j1] arr[j]因为这样会丢失原值第四个细节是如果能在内层循环里用一个标志位判断本轮没有发生交换就提前结束会显得你考虑得更周全优化后的代码复杂度从最好O(n^2)降为O(n)。5.2 手写代码的四个踩分步骤注释、边界、命名、验证根据我后来参与校招面试、和几位出题人交流的经验编程题的评分不是要么满分要么零分而是按踩分点给分。哪怕你的代码不能完整跑通只要你能展示出正确的思路也能拿到不少分。所以手写编程题时有一个固定的节奏很重要。第一步先写注释说明思路。笔试卷子不是在线评测系统阅卷人是真人他看到你有清晰的思路描述即使代码有小错误也更容易给分。第二步先处理边界条件再写主逻辑比如指针参数是否为NULL、数组长度是否为0、字符串是否为空。第三步写主逻辑注意变量命名要见名知意不要用a、b、c这种无名变量i、j作为循环变量可以接受。第四步检查一遍代码逻辑把可能越界的地方标注出来。这套步骤看起来简单但在考场上真正能坚持做完的人不多。大部分人是拿到题就开始闷头写代码写完就交。实际上在代码开头写两行注释在函数入口判断一下空指针这种防御性编程的习惯本身就是C基础类笔试想要筛选的素质。5.3 文件读写操作从fopen到fread的标准答案写法文件读写操作在A卷的编程题中也很常见因为文件操作涉及指针、内存、错误处理等多个C语言的综合能力。完整的标准写法应该是这样void read_and_process(const char *filename) { FILE *fp fopen(filename, r); if (fp NULL) { printf(文件打开失败\n); return; } char buffer[1024]; while (fgets(buffer, sizeof(buffer), fp) ! NULL) { // 对每一行内容进行处理 } fclose(fp); }这个代码有四个得分点一是fopen之后必须检查返回值二是用fgets按行读取时sizeof(buffer)告诉函数缓冲区的大小防止越界三是循环结束的条件是fgets返回NULL而不是用feof来判断因为feof只有在读取失败之后才会置位逻辑上容易出错四是函数结束前必须fclose否则会造成文件句柄泄漏。如果题目要求读二进制文件可能就要用到fread和fwrite。这种题有一组必考的参数组合size_t bytes_read fread(buf, sizeof(char), count, fp);第一个参数是目标缓冲区第二个参数是单个元素的字节大小第三个参数是要读的元素个数第四个参数是文件指针。返回值是实际读取的元素个数可能小于请求值所以必须检查返回值。笔试考文件读写时最容易丢分的点不是不会用函数而是忘记检查返回值、忘记关闭文件、或者把二进制模式和文本模式搞混。5.4 链表操作与结构体A卷中的进阶加分项如果说排序和字符串是保底题那链表操作和结构体相关的题目就是加分项。A卷有时候会给出一道单链表反转或链表中查找第k个节点的问题考察结构体指针的熟练度。单链表反转的经典三指针法struct ListNode { int val; struct ListNode *next; }; struct ListNode *reverse_list(struct ListNode *head) { struct ListNode *prev NULL; struct ListNode *curr head; while (curr ! NULL) { struct ListNode *next curr-next; curr-next prev; prev curr; curr next; } return prev; }这里最关键的一行是提前保存curr-next否则在修改curr-next指向后原来的后继节点就找不到了。这个思路如果不提前想清楚写出来的代码在第三行就断了。链表题在整个C基础类笔试中属于中等偏上难度如果前面的基础题你都能稳定拿分可以花点时间准备这类题目它们是区分基础扎实和基础扎实且综合能力强的关键。6. 考完复盘时间分配、复习重点和那些我踩过的坑6.1 复习优先级排序先保指针和字符串再攻文件和链表关于C基础类笔试的复习策略我的建议是按频率-性价比两个维度排序。第一优先级是指针与内存、字符串与数组这两块几乎占了卷面50%的分值而且题型覆盖选择题、程序阅读题、编程题全部三类投入产出比最高。第二优先级是流程控制、运算符优先级和结构体这些考点出现在选择题的概率高但只要刷过一些练习题失分率并不高。第三优先级是文件操作和链表编程题可能涉及但难度波动大建议在有扎实基础的前提下再准备。不建议一上来就刷算法题比如迪杰斯特拉这类图论算法在校招笔试的C基础类A卷中出现频率很低因为A卷的定位是基础不是算法。如果你在某道题上花了超过10分钟还没有思路果断放弃把时间留给后面更有把握的题。6.2 用输出驱动代替看书驱动来复习复习C基础最忌讳的就是捧着教材从头翻到尾。笔试考的是你能不能现场写出来不是你能不能看懂。所以我推荐用刷题驱动手写代码的方式来复习——每看完一个知识点立刻在纸上或编译器里写一个对应的最小示例。比如看完while和do-while的区别就写一个程序同时用两种循环实现同一个功能对比执行结果看完指针和数组就写一个程序分别打印sizeof(arr)和sizeof(p)以及arr 1和arr 1的差异看完内存管理就写一个malloc-free配套程序然后故意制造一次内存泄漏用工具观察内存变化。这种亲手验证一遍的复习方式比任何教材都管用因为笔试要考的正是你脑子里的可调用经验而不是听说过但没试过的知识。6.3 考场上的几个实操细节最后分享几个我在考场上的实操细节看起来不起眼但关键时候能救命。第一拿到卷子先花一分钟通读全部题目标注哪些是必拿分、哪些是可能失分、哪些是时间不够可以放弃的。第二草稿纸上提前画出运算符优先级表、字符串函数区别表、const指针三种形态的对比表做题时随时参考避免临时推导浪费时间。第三编程题先写函数签名和注释再填主逻辑不要一上来就写代码。第四如果选择题某道题卡住了用排除法先把明显错误的选项划掉在剩余选项里选择比空着强得多。第五也是我最想强调的一点答题时保持代码缩进和变量命名规范。阅卷人是真人面对几百份卷子一份缩进清晰、命名规范的代码哪怕有小瑕疵给分也会倾向宽松。反过来一份变量名全是a、b、c、缩进混乱的代码阅卷人很难产生好感。编程题考的不只是逻辑还有工程习惯。6.4 笔试之后的下一关面试里C基础怎么被追问如果你通过了笔试面试中大概率还会被追问C基础相关的问题。最常见的追问方式是你笔试中写到在free之后将指针置为NULL解释一下为什么需要这样做或者你手写了一个字符串逆序如果字符串非常长你会怎么优化。这些追问的本质是想看你是否真正理解了你在笔试中表达的内容以及你是否有能力把知识迁移到新场景中。所以我建议笔试结束后趁记忆新鲜把卷子里的每一道题都在编译器里重新实现一遍把每一道选择题的陷阱都总结成自己的笔记。这个过程比单纯刷题的价值高很多因为你能把考试场景转化为持久能力。我当年在欢聚时代笔试后整理的那份复盘笔记后来也帮我在几家公司的面试中通过了类似的C基础考察。说到底C基础类笔试是一座桥桥的另一头是真正考验工程能力和底层思维的项目与面试。把桥走稳未来才有机会去看更远的风景。
返回列表