简介:面向准备C语言笔试与校招基础面试的开发者,这份文档式资料把常见考点整理为问答与代码分析题,适合刷题前系统梳理概念,也便于考前快速自测。内容覆盖基础概念、数据类型、运算符、控制结构、函数、数组、指针、结构体、文件输入输出与预处理器等章节,并围绕static关键字、引用与指针差异、全局与局部变量的内存位置、平衡二叉树、堆栈溢出、虚函数限制、冒泡排序复杂度、浮点数零值比较、TCP/IP分层与ARP地址转换、IP地址编码等高频问题展开,附有循环链表报数、switch参数类型、变量重名与extern引用等典型题目的解析。资源包共1个doc文件,约225KB,篇幅紧凑,便于离线查阅与打印标注。已有259人学习,说明其在C语言基础复习场景中具备一定参考价值。读者可逐项核对概念、理解代码输出与边界条件,形成覆盖语法、内存、数据结构与网络基础的复习清单。
1. 开篇:笔试里最容易丢分的,往往不是难题
C语言笔试最反直觉的一点是:把你刷下去的一般不是最后那道算法大题,而是靠前那几道你扫一眼就觉得“这不就是基础吗”的小题。sizeof(struct)算成 48 而不是 52,&a+1再减一次得到 2 而不是 5,把一个字符串strcpy到一个字符变量的地址上,这些题单题三五分,一份卷子里连错四五道,分数就这样被拉没了。这张卷子实际只考三件事:你对存储方式与生命周期的判断、你对指针和数组类型含义的判断、你在有限时间里把错找出来的能力。刚过完语法、想知道企业从哪几个角度下手的备考者,和工作几年、写代码靠编译器兜底、想补回底层认知的面试者,都适合照着考点表一路练下去。
2. 考点分布:五个模块与它们在卷子里的分量
一张典型的 C语言笔试基础卷,分值大致这样铺开:关键字与存储类约占两成,内存分区与sizeof计算约两成,指针数组字符串约三成,算法与数据结构小题目约两成,剩下的零碎分给网络、操作系统、编译链接。拉开差距的是第二和第三块,因为它们不能靠背结论蒙,必须理解类型和存储。下面按这三块的最常考形式逐个拆。
2.1 关键字场:static、const、volatile 的三连问
static几乎是必考题,而且面试官喜欢一次问全三种用法。答题时不要只说“静态”,要分三个场景各给一句结论:修饰全局变量时它改的是作用域,让变量只在定义它的那个源文件里有效,别的文件连extern也链不上;修饰局部变量时它改的是生存期,变量只初始化一次,函数退出后值还在,下次进来接着上次的值;修饰函数时同样是把可见性锁在本文件内,适合写内部辅助函数。
最容易失分的是把“改作用域”和“改存储方式”说混。全局变量本身就是静态存储,加static并不改变它存在静态区这个事实,改的是链接属性;局部变量加static才是真正从栈搬到了静态区。用一张表对比着记最稳:
| 对比对象 | 存储位置 | 作用域 | 初始化次数 |
|---|---|---|---|
| 普通全局变量 | 静态区 | 整个程序,可跨文件 | 一次 |
| static 全局变量 | 静态区 | 仅本源文件 | 一次 |
| 普通局部变量 | 栈 | 本函数本次调用 | 每次进入都初始化 |
| static 局部变量 | 静态区 | 本函数,跨调用保留 | 仅一次 |
| 普通函数 | 代码区 | 可跨文件调用 | 不涉及 |
| static 函数 | 代码区 | 仅本文件内可见 | 不涉及 |
const的考点几乎固定在三种指针写法的辨认上:const char *p和char const *p是一回事,指向的内容不能改,指针本身可以改指向;char * const p是指针本身不能改,内容可以改;const char * const p两头都锁死。答题时按“const 在星号左边管内容、在星号右边管指针”这一句口诀展开,再补一句“const变量只是编译期的只读约束,运行时它仍可能被改动”,这一分就稳了。
volatile常被追问“什么时候必须加”。给三个场景就够:一是映射到硬件寄存器的变量,值会被硬件改;二是中断服务程序里修改、主循环里读取的标志位;三是多线程共享的状态标志。说到底它只是告诉编译器“别把这个变量的读优化掉”,它不解决原子性,这一点答出来是加分项。
2.2 内存分区:栈、堆、静态区,和那道 52 分的 sizeof 题
内存分区的标准问法就一句话:“局部变量、全局变量、动态申请的数据分别在哪?”答案是栈、静态区、堆。接着会追问栈和堆的区别,按“谁分配谁释放、空间大小、生长方向、分配效率”四个维度答:栈由系统自动分配释放、空间小、向下生长、分配快;堆手动申请手动释放、空间大而零散、向上生长、分配慢且容易产生碎片。
真正拉分的是sizeof计算题。给一个联合体和一个结构体:
typedef union { long i; /* 4 字节(32 位下) */ int k[5]; /* 20 字节,联合体里最大的成员 */ char c; /* 1 字节 */ } DATE; struct data { int cat; /* 4 字节 */ DATE cow; /* 20 字节 */ double dog; /* 8 字节 */ } too; DATE max; /* sizeof(struct data) + sizeof(max) = 32 + 20 = 52 */解题步骤必须写出来,不然阅卷只看到数字:先看联合体,联合体所有成员共用一块空间,大小取最大成员并按最大对齐数补齐,int k[5]是 20 字节最大,所以DATE是 20。再看结构体,各成员独立占位,4 + 20 + 8 = 32,刚好满足double的 8 字节对齐,不用补。最后两项相加 52。如果卷面提示是 16 位编译器,int是 2 字节,DATE变成 10,结构体变成 2 + 10 + 8 = 20,结果 30——这个分支答出来,说明你不是背答案。
还有一道配菜必须知道:char szstr[10]; strcpy(szstr, "0123456789");会出什么事。"0123456789"连同结尾的\0一共 11 个字节,塞进 10 字节的数组,越界一个字节,结束符没地方放,printf会一直往后读直到撞见随机零,行为不可预期,运气好只是乱码,运气不好直接崩。答案要落在“少算了\0”这个点上。
至于“堆栈溢出一般由什么引起”,标准答法是:递归层数过深、函数内定义了超大局部数组、以及堆上动态申请的内存没有释放导致可用空间被耗尽。三条都写上,比只写一条安全。
2.3 指针与数组:sizeof、退化与那组 0 0 1 1
指针和数组的第一道坎是sizeof。数组名在绝大多数表达式里会退化成指向首元素的指针,但在sizeof和取地址&两种场合不会退化。所以在函数内部写sizeof(str),无论实参传进来多大的数组,拿到的都是一个指针的字节数,32 位机器上就是 4。这也是“指针是多少位”那道题的答案来源:看地址总线宽度,32 位地址总线对应 4 字节指针,跟int占几个字节无关。
第二道坎是字符串数组和字符指针的比较题,八个变量清一色"abc",问四个比较式的输出。答案是 0 0 1 1。前四个是数组,每个数组都在自己的栈空间里放了一份副本,地址必然不同;后四个是指针,指向同一块只读常量区,地址相同。答题要把“数组有自己的内存、指针只存地址”这句点出来,再补一句“把"abc"赋给char *在标准 C 里是不推荐的写法,正确写法是const char *”,这是拿了基础分之后的加分动作。
第三道坎是复杂声明的读法。int (*s[10])(int)拆法是从名字往里读:s先和[]结合,是一个长度为 10 的数组;数组元素是(*...)形式的指针;指针指向参数为int、返回int的函数。合起来就是函数指针数组。拆这类声明的顺序永远是“先找名字,再看右边,再看左边,遇括号掉头”。
3. 高频考点:指针、字符串、位运算的真题演练
这一块是最能体现“理解与否”的地方,题目本身都不长,但每一道都埋了一个具体的坑。下面四道是出现频率最高的,答题思路按“先说结论、再说原因、最后说怎么改”的顺序走,阅卷和面试官都吃这一套。
3.1&a+1那道题:指针类型决定步长
#include <stdio.h> int main(void) { int a[5] = {1, 2, 3, 4, 5}; /* &a 的类型是 int(*)[5],+1 跨过整个数组,指向 a[5] */ int *ptr = (int *)(&a + 1); /* *(a+1) 是 a[1];ptr 是 a+5,ptr-1 退一个 int,落在 a[4] */ printf("%d,%d\n", *(a + 1), *(ptr - 1)); return 0; }输出是2,5。这里的关键参数是“步长”:a是首元素地址,类型int *,加 1 走 4 个字节;&a是整个数组的地址,类型int (*)[5],加 1 走 5 个int,也就是 20 个字节。强制转换只改变了变量ptr的静态类型,让它减 1 的时候按int走,并不改变它实际指向的位置。
这道题的常见追问是“a和&a的值一样吗”,答案是一样但含义不同,一个是首元素地址、一个是数组地址。还有一个陷阱是int *ptr = (int *)(a + 1)少写一个&,那ptr - 1就落回a[0],输出变成2,1。写答案时把这两者的类型差异写清楚,比只写结果分高。
3.2 函数指针与 typedef:那道输出 110 的题
#include <stdio.h> typedef int (*FUNC1)(int); /* 一元函数指针类型 */ typedef int (*FUNC2)(int *, int *, int *); /* 三元函数指针类型,结果写回第三个参数 */ int inc(int a) { return ++a; } int multi(int *a, int *b, int *c) { return (*c = *a * *b); } void show(FUNC2 fun, int arg1, int *arg2) { FUNC1 p = inc; /* 函数名即地址,等价于 p = &inc */ int temp = p(arg1); /* 先自增:temp = 11 */ fun(&temp, &arg1, arg2); /* 11 * 10 = 110,写进 *arg2 */ printf("%d\n", *arg2); } int main(void) { int a = 0; show(multi, 10, &a); /* 输出 110 */ return 0; }答案是 110。三步必须写清楚:第一步inc是前置自增,10 进去 11 出来;第二步multi拿到的是两个地址,取到的值是 11 和 10,乘积 110 通过*c写回调用方;第三步printf打印的是a,也就是 110。
这道题真正考的是typedef的读法。typedef int (*FUNC2)(int *, int *, int *)里FUNC2是一个类型名,不是变量,所以后面可以写FUNC2 fun当形参。如果漏了*写成typedef int (FUNC2)(...),那FUNC2是函数类型,形参写成FUNC2 fun会被编译器自动调整成函数指针,能编过但语义不清楚,这种“能跑但说不清”的地方面试官最爱追问。
3.3 字符串倒序改错:五处错误逐条对
原题给了一段把"hello,world"倒序的代码,错误密集。逐条列出来,比自己重写一遍更能拿分:
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { const char *src = "hello,world"; /* 错1:指向常量却用 char*,字面量应只读 */ int len = (int)strlen(src); char *dest = (char *)malloc(len + 1); /* 错2:只申请 len,没给 '\0' 留位置 */ if (dest == NULL) return 1; /* 错3:malloc 可能失败,必须判空 */ char *d = dest; const char *s = src + len - 1; /* 错4:原写法 src[len] 越界,应指向末字符 */ while (len-- > 0) *d++ = *s--; *d = '\0'; /* 错5:尾部必须补结束符 */ printf("%s\n", dest); free(dest); /* 错6:用完不释放,内存泄漏 */ return 0; }逐条对照:"hello,world"是 11 个字符,malloc(len)只给 11 字节,倒序完成后没有位置放\0,printf会继续打印栈上的垃圾;src[len]指向的是结束符本身而不是最后一个字母,所以起点必须是src[len-1];free漏掉是最常见的扣分点,动态申请和释放必须成对出现。
卷面上如果只写“有错”不写“为什么错”,基本不给分。每处错误按“错在哪、为什么、怎么改”三句话写,五处写完刚好一页,节奏也好看。
另外还有一种不申请堆内存的原地解法,把str定义成可写数组,用首尾交换双指针走len/2次。这种写法在面试里更受欢迎,因为它不涉及内存管理,只要注意别把"hello,world"直接赋给可写指针就行。
3.4 位运算三连:判 2 的幂、按位取反、无临时变量交换
/* 1. x 是否为 2 的若干次幂:二进制里只能有一个 1 */ int is_pow2(unsigned int x) { return x != 0 && (x & (x - 1)) == 0; /* 必须排除 x = 0 */ } /* 2. 把 x 从第 p 位开始的 n 位取反 */ unsigned int intvert(unsigned int x, int p, int n) { unsigned int mask = 0; for (int i = 0; i < n; ++i) mask |= (1u << i); /* 造出低 n 位全 1 的掩码 */ return x ^ (mask << p); /* 异或等于取反,掩码左移到起始位 */ } /* 3. 不用第三个变量交换两个整数 */ void swap_xor(int *a, int *b) { if (a == b) return; /* 同一地址会被清成 0 */ *a ^= *b; *b ^= *a; *a ^= *b; }第一题的坑在x = 0:0 & -1也是 0,不判空会把 0 判成 2 的幂。另外这题只对无符号数成立,负数右移是实现定义的,答题时把参数类型写成unsigned int就是隐式加分。
第二题用到输入里那道原题x=0b00010001, p=4, n=3就是把第 4 到第 6 位取反得到0b01100001。掩码的思路是“先造低位、再移位、最后异或”,三步缺一不可;用循环造掩码比写((1<<n)-1)更安全,因为n等于 32 时移位会溢出。
第三题要主动说出swap_xor的两个前提:一是同一地址会失效,二是形参必须是指针。用算术加减也行,但要提一句“a + b可能溢出,异或法没有这个问题”,面试官基本就在等这句话。
4. 数据结构与算法:必考的那几道小题
这一块题目难度不高,但要求“当场写出来并且能跑”,所以练的时候不能只看思路,必须手写一遍。常考的四类:两个栈模拟队列、约瑟夫环、数组里找重复、链表找环。每类都有固定套路,记住套路比背代码有效。
4.1 两个栈实现队列:为什么平摊是 O(1)
#define MAXN 1000 typedef struct { int data[MAXN]; int top; /* 指向栈顶的下一个空位,空栈时为 0 */ } Stack; void push(Stack *s, int v) { s->data[s->top++] = v; } int pop(Stack *s) { return s->data[--s->top]; } int empty(Stack *s) { return s->top == 0; } /* A 只管入队,B 只管出队 */ void enqueue(Stack *A, Stack *B, int v) { push(A, v); } int dequeue(Stack *A, Stack *B) { if (empty(B)) { /* B 空了才从 A 整批倒过来 */ while (!empty(A)) push(B, pop(A)); } return pop(B); /* 调用前应先判 B 是否为空 */ }思路是“进队永远压 A,出队永远从 B 弹”。B 空了才把 A 里的元素全部倒过去,倒一次顺序就正过来了。参数上top用“栈顶下一格”的约定,push先写后加、pop先减后取,两个动作就对称了。
复杂度是这道题的追问重点:单次出队最坏是 O(n),但每个元素一生只会被搬两次(从 A 搬到 B 一次,出 B 一次),所以 n 次操作总共 O(n),平摊下来每次 O(1)。答题时要特意区分“最坏”和“平摊”这两个词,只说 O(1) 会被追问。
容易翻车的地方有两个:一是在 A 不为空时就把元素倒进 B,导致顺序错乱;二是 B 非空时又倒了一遍,把已经排好的顺序打乱。记住“B 不空不许倒”这一条,基本不会错。
4.2 约瑟夫环:循环链表与取余两种写法
题目是“从 1 到 N 顺序数,每数到 M 输出该数,直到全部输出”。循环链表是最直白的解法:
#include <stdio.h> #include <stdlib.h> typedef struct Node { int id; struct Node *next; } Node; void josephus(int n, int m) { if (n <= 0 || m <= 0) return; Node *head = NULL, *tail = NULL; for (int i = 1; i <= n; ++i) { /* 建表 */ Node *p = (Node *)malloc(sizeof(Node)); p->id = i; p->next = NULL; if (head == NULL) head = p; else tail->next = p; tail = p; } tail->next = head; /* 首尾相连,成环 */ Node *prev = tail, *cur = head; while (cur->next != cur) { /* 只剩一个节点时停 */ for (int i = 1; i < m; ++i) { /* 走 m-1 步,因为起点自身算一个 */ prev = cur; cur = cur->next; } printf("%d ", cur->id); prev->next = cur->next; /* 摘掉出列节点 */ free(cur); cur = prev->next; } printf("%d\n", cur->id); free(cur); }prev和cur一起走是链表删除的标准姿势,少了prev就没法把节点从环上摘下来。循环m-1次而不是m次是最常见的错,因为当前节点本身就算“1”。总时间 O(n·m)。
如果卷面写着“用取余实现”,那就开一个数组当标记位,idx = (idx + m - 1) % n定位下一个人,标记已出列的人跳过,每轮把n减一。要点是每输出一个人后,下一轮起点就是当前位置,而不是+1,这一点写错就全盘错。
4.3 数组找重复与链表找环:O(N) 的两个套路
“长度为 N 的数组里放了 1 到 N-1,其中一个数重复一次,O(N) 找出它”,最省事的做法是求和相减:
int do_dup(const int a[], int N) { long long sum = 0; /* 用 long long 防溢出 */ for (int i = 0; i < N; ++i) sum += a[i]; return (int)(sum - (long long)(N - 1) * N / 2); }思路是整数范围内没有别的缺失,重复一个必然少一个,总和减掉1..N-1的和就是重复值。时间 O(N)、空间 O(1)。答题时必须补一句“累加用long long,否则 N 稍大就溢出”,这是这道题最常见的失分点。另一种解法是下标映射原地标记(把a[i]当索引取负做记号),复杂度也是 O(N),但要求数组可写,卷面条件不允许时就不能用。
链表找环入口用快慢指针:快指针每次两步、慢指针每次一步,相遇说明有环;相遇后把其中一个指针放回头节点,两个指针都改成每次一步,再次相遇的位置就是环入口。推导可以让面试官自己看,答题时把“相遇点到入口的距离等于头到入口的距离”这个结论说清楚就行,这是从输入里“一个递增一、一个递增二,指向同一节点即为环入口”那句延伸出来的标准答法。
4.4 排序、平衡二叉树与栈队列对比
冒泡排序的最坏和平均时间复杂度都是 O(n²),最好情况加了“本轮无交换就提前结束”的优化后是 O(n),空间 O(1),稳定。回答这题时把这三个量一起报,比只报 O(n²) 完整得多。平衡二叉树的定义要一字不差:左右子树都是平衡二叉树,且左右子树深度差的绝对值不超过 1。它是用来把查找从链表退化态拉回 O(log n) 的结构,答完定义补这一句,说明你知道它为什么存在。
栈和队列的区别几乎每张卷子都有:栈后进先出,队列先进先出。往深一层可以补“栈适合括号匹配、表达式求值、函数调用;队列适合任务排队、广度优先遍历、消息缓冲”。
顺带会被问的还有“哪些函数不能声明为虚函数”,标准答案是构造函数。理由是虚函数的调用依赖对象里的虚表指针,而虚表指针本身要在构造过程中才被建立,构造函数执行时对象还不完整,多态无从谈起。静态成员函数、内联函数、友元函数也不能是虚函数,一起答出来更稳。
4.5 网络与操作系统小题:层次、ARP 与死锁四条件
基础卷里必配几道网络题。TCP/IP 的层次回答四层或五层都行,但要说清是哪一套:应用层、传输层、网络层、数据链路层、物理层。IP 地址由网络号和主机号两部分组成,但要和子网掩码按位与之后才能划分哪些位是网络位。物理地址到 IP 地址的转换由 ARP 完成,反过来由 RARP 完成,别写反。TCP 建立连接是三次握手,端口的作用是区分同一台主机上的不同应用程序。TCP 与 UDP 的对比按“连接、可靠性、流量控制、首部开销、适用场景”五点列成表最快,视频、语音这类允许丢包但怕延迟的场景用 UDP,文件传输、网页这类不能出错用 TCP。
操作系统这一块的高频答案是死锁四个必要条件:互斥、请求并保持、不可剥夺、循环等待。处理策略分四类:鸵鸟策略、预防、避免、检测与解除。进程调度常考四种:先来先服务、优先级、时间片轮转、多级反馈队列。进程与线程的区别按“调度单位、拥有资源、并发性、切换开销”四条答,线程共享进程的地址空间但有自己的栈和寄存器上下文。进程通信列举共享内存、消息传递、管道即可,其中管道基于文件系统,共享内存最快但需要额外同步。
5. 易错题:五个年年出现、年年有人踩错的失分点
这些题的共同特点是“看得懂、答不对”,错法几乎每年一模一样。把每条按“错在哪、为什么错、怎么答对”记下来,比多刷十道新题有用。
5.1 错在以为传指针就能改指针
void getmemory(char *p) { p = (char *)malloc(100); strcpy(p, "hello world"); } int main(void) { char *str = NULL; getmemory(str); /* 传的是值,str 仍是 NULL */ printf("%s\n", str); /* 解引用空指针,崩 */ free(str); /* free(NULL) 无害,但上面的 printf 已经出事 */ return 0; }错在哪:以为把指针传进去就能让外面的str指向新内存。为什么错:C 只有值传递,形参p是str的一个副本,p被改成新地址,str一动不动,仍然是 NULL;而且那块 malloc 出来的内存在函数返回后没人指向它,直接泄漏。
怎么答对:要么传二级指针void getmemory(char **p),函数里写*p = malloc(100),调用处写getmemory(&str);要么让函数返回指针char *getmemory(void),调用处str = getmemory()。答题时把“值传递只复制了指针本身,没复制它指向的东西”这句话写出来,是这道题的核心得分句。
5.2 错在把 sizeof 当成运行时求长度
最典型的错法是函数里写for (i = 0; i < sizeof(str) / sizeof(str[0]); ++i)去遍历一个传进来的数组。为什么错:形参char str[]在编译器眼里等同于char *str,sizeof拿到的是指针大小 4,除以sizeof(char)还是 4,循环只走了前四个字符,剩下的原样输出。数组在函数参数位置必然退化成指针,这是语法规定,不是编译器的错。
怎么答对:长度必须在数组还没退化的地方算好再传进去,函数签名改成void upper(char *s, size_t len),或者用strlen(只对字符串有效)。同一类错还有sizeof与strlen混用:char s[10] = "abc"的sizeof(s)是 10,strlen(s)是 3,一个算容量一个算长度,答题时把两个值都写出来再解释,比只给一个数字清楚。
5.3 错在把浮点数和 0 直接比
写成if (x == 0.0)是这道题的经典错解。为什么错:浮点数在内存里是二进制近似表示,0.1这样的十进制小数根本存不准,若干次运算之后结果可能是1e-8而不是严格的 0,等号比较永远不成立。
怎么答对:用一个足够小的误差范围来判断,if (x > -1e-6 && x < 1e-6)表示“近似为零”,if (x > 1e-6)表示“明显大于零”。答题时把精度阈值的选取说一句“阈值取决于业务精度要求,通常取 1e-6”,这一句能拉开差距。不要把fabs(x) < 1e-6和直接比较混着写,前者要引<math.h>,卷面没给头文件时就老老实实写两个不等式。
5.4 错在宏的副作用和边界
两处高频翻车。第一处是#define SQUARE(a) ((a)*(a)),然后写b = SQUARE(a++)。为什么错:宏是文本替换,展开成((a++)*(a++)),a被自增两次,而且两次取值之间没有序列点,结果未定义。怎么答对:宏参数必须加括号,同时说明“带副作用的表达式不能传给宏,这种情况用inline函数”;顺带答出“C 里短小频繁调用的函数用宏,C++ 里用 inline”,这正好是配套的追问。
第二处是#define Max_CB 500配unsigned char ucCmdNum写for (ucCmdNum = 0; ucCmdNum < Max_CB; ++ucCmdNum)。为什么错:unsigned char最大 255,加到 255 再自增回绕成 0,条件永远成立,死循环。怎么答对:把循环变量改成int或unsigned int,或者把上限改到类型能表示的范围内。答题时点出“无符号数回绕是定义良好的行为,所以编译器不会警告,这类 bug 只能靠人看”,比单纯说“死循环”有价值。
5.5 错在动字符串常量
char *s = "AAA"; s[0] = 'B';这段代码在有些平台上跑得好好的,在另一些平台直接段错误。为什么错:"AAA"存放在只读的常量区,s只是指向它的指针,通过指针写只读区是未定义行为,具体表现取决于编译器和操作系统,所以“我这儿能跑”完全不能作为正确性依据。
怎么答对:想改就必须自己有一份副本,写成char s[] = "AAA";,数组会把常量复制到栈上;不想改就写const char *s = "AAA";,让编译器帮你挡住写操作。同一类错还有char a; char *str = &a; strcpy(str, "hello");,一个字符的空间塞六个字节,越界写会踩坏栈上的其他变量,答案要落在“没有为目标字符串分配足够空间”上。
6. 答题技巧:三遍法怎么在考场上省下二十分钟
笔试的时间分配比知识点更容易被忽视。我的建议是把整张卷子切成三遍过:第一遍只做能一眼出答案的题,包括关键字解释、栈队列区别、复杂度、网络层次,这些题目标就是快,一分钟一道,先把能拿的分全部落袋;第二遍做需要动笔算或者写代码的题,sizeof计算、指针运算、改错题、算法小题都属于这一档,每题给自己设一个上限,超过就跳过;第三遍回头处理空着的题,先挑“写两行就能拿一半分”的,比如改错题里明确写出“这里少分配了一个字节”即使没写完整个函数也有分。
具体到题型,有两条硬习惯值得养成。
第一条是计算题必须写出过程。sizeof那道题,只写 52 很可能被判成蒙的,写成“联合体取最大成员 20,结构体 4+20+8=32,合计 52”就是完整推导,即便某个环节算错,过程分也在。指针题同理,把&a的类型int (*)[5]写在旁边,阅卷的人一眼就知道你懂。
第二条是代码题先写框架再填细节。改错题和算法题都适用:先把函数签名、变量声明、循环骨架摆上去,再往里面填,最后统一补\0、判空、free这三样。这三样是阅卷时最先扫的地方,缺一个扣一档。
| 题型 | 建议用时 | 得分要点 | 高频失分动作 |
|---|---|---|---|
| 关键字解释 | 1 分钟 | 分场景给结论 | 只说“静态”,不说改作用域还是改生存期 |
| 内存与 sizeof | 3 分钟 | 写出每一步字节数 | 只给结果,不写推导 |
| 指针运算 | 3 分钟 | 标出指针类型和步长 | 混淆a+1与&a+1 |
| 改错题 | 6 分钟 | 每处按“错在哪/为什么/怎么改”三句写 | 只圈错误不解释 |
| 算法小题 | 10 分钟 | 先写边界判断再写主体 | 漏掉空输入、漏掉释放内存 |
| 网络与操作系统 | 1 分钟 | 层次、协议名、四个条件写全 | ARP 和 RARP 写反 |
最后留十分钟做一件事:把自己写的代码当别人的代码读一遍,专门找四样东西——数组有没有多留一格给结束符、malloc有没有配free、循环边界是<还是<=、有符号数有没有在移位。这四样能挡掉大部分“思路对但跑不起来”的扣分。
从那以后我每次带人练这套题,都强制走一遍“先列考点分布、再刷高频真题、最后专攻五个易错点”的顺序,不允许上来就刷算法大题。基础题的分数是确定性的,算法题不是,先把确定的分全部拿到手,剩下的时间才敢去搏难题。希望这套顺序帮到你。
本文还有配套的精品资源,点击获取