C语言100例高效刷题路线:指针、链表与内存调试实战
2026/9/19 19:55:21 网站建设 项目流程

简介:这是一份包含一百道C语言经典例题的PDF文档,主要面向C语言初学者、高校计算机专业学生以及备考计算机二级C语言的读者。题目来源经典且覆盖全面,包括基本数据类型、控制结构、函数、数组、指针、结构体、文件操作等核心知识模块,每道题均配有题目描述、程序分析和完整源代码,便于读者对照练习。例如,程序1用三重循环与条件判断解决无重复三位数排列问题,程序2利用数轴分界处理利润提成分段计算,程序3使用sqrt函数判断完全平方数,程序4通过switch和闰年判断计算某天是第几天。这些实例由浅入深,既能帮助初学者理解语法要点,也能训练编程逻辑与调试能力。文档为单个PDF文件,压缩包大小仅2.77MB,轻量便携,已由1201人学习下载。整体而言,这是一套适合系统练习与考前冲刺的C语言经典题库,读者可边看边敲代码,快速提升实际编程水平。

1. 从第30题就放弃的C语言例题册子

你的硬盘里大概率躺着不止一份《C语言经典例题100例.pdf》。大多数人拿到它的路径是相仿的:前20题写得飞快,30到50题开始翻答案,六七十题之后直接看代码,最后得出“这册子太老、题量注水”的结论,然后合上文件,等着下一次从第1题重新开始。真正的问题不在于题目数量,而在于你把例题当成了“任务”而不是“标本”。这篇文章按我带人时习惯的做法,把100例按知识点、难度和代码量重新切成五类,给你一套今天就能动手的刷题路线,顺带把指针、结构体、链表、文件读写这几类高频考点的坑摊开讲清楚。新手可以跟着章节一步步敲,有经验的人可以重点看第2章的考点地图和第5章的测试框架。

2. 100道例题的考点地图:指针、内存与结构体的出现频率

2.1 先按知识点把100例拆成五类

拿到这份PDF的第一晚,不要急着写代码,拿一支笔把题目按知识点归类。常见做法是分成下面五类,这种分法也对应着绝大多数C语言教材的章节顺序:

知识点常见题量占比典型题型上手难度
基础语法与控制流25%素数判断、水仙花数、九九乘法表
数组与字符串25%冒泡排序、字符统计、字符串逆序低中
指针与内存动态分配20%交换函数、二级指针、malloc/free中高
结构体与链表20%学生成绩表、单链表反转、约瑟夫环中高
文件读写与预处理10%文件拷贝、词频统计、带参宏

实际题量分布会因版本略有差异,但大体就是这个比例。文件类题目占比最小,原因是早期教材把文件放在最后一章,篇幅也最长,一本习题册放不下太多。归类做完,你会立刻发现“字符串逆序”和“链表反转”名字不同,考点高度重合,都在考指针移动和边界判断。

2.2 指针题最常见的三种出场方式

指针被誉为C语言的门槛,在100例里几乎无处不在。最简单的出场方式是函数传参:写一个swap函数交换两个整数。很多新手第一版是这样写的:

void swap(int a, int b) { int temp = a; a = b; b = temp; }

这段代码语法完全正确,但调用后实参没有变化。原因是C语言的函数参数默认是值传递,ab只是实参的拷贝。改成指针版本才能操作函数外面的变量:

void swap(int *a, int *b) { int temp = *a; // 取出a指向地址里的值 *a = *b; // 把b的值写入a指向的地址 *b = temp; }

调用时要写swap(&x, &y)。参数int *a表示接收一个int变量的地址,函数内部通过解引用操作拿到或修改原变量。这个点能卡住很多人,它也是理解数组名和字符串函数的基础。比如strcpy(dst, src)为什么能修改dst的内容,就是因为它的内部拿着指针,操作的是地址上的内存。

2.3 链表题为什么总是“会看不会写”

链表是另一个分水岭。结构体本身不难,难的是节点之间的指针指向。常见例题是单链表反转:

struct Node { int data; struct Node *next; }; struct Node *reverseList(struct Node *head) { struct Node *prev = NULL; while (head != NULL) { struct Node *next = head->next; // 先记下后一个节点 head->next = prev; // 当前节点指向前驱 prev = head; // prev整体后移 head = next; // head整体后移 } return prev; }

关键在局部指针next:它必须在修改head->next之前保存下个节点的地址。很多人在白板上写链表题会卡在这一步,因为先改了head->next,导致原来的下一个节点再也找不回来。链表这类题动笔之前先想三件事:谁指向谁、改变指向后会不会丢地址、循环退出时指针停在哪。配合画内存图可以少踩一半的坑。

2.4 刷题顺序建议

我建议按表格从上往下刷,基础语法类和数组类先各做20道,这些题让你熟练forwhile、数组下标和基本输入输出。指针类和结构体类不要跳着做,前面字符串函数没吃透就碰链表会非常吃力。文件类题放到最后,因为它通常要把前面的知识点串起来。这样按层推进,100例就不是一百个孤立题目,而是一张由简到繁的知识网。

3. 从字符串逆序拆开100例里的经典套路:审题到验证

3.1 一道题目里的边界条件

“输入一个字符串,将其逆序输出”在100例里出现频率高,同时能考数组、指针、字符串函数和边界处理。很多人的第一版是这样写的:

#include <stdio.h> #include <string.h> int main() { char s[100]; gets(s); // 不推荐,老题里却很常见 int len = strlen(s); for (int i = len - 1; i >= 0; i--) { printf("%c", s[i]); } return 0; }

问题不在输出顺序,而在gets函数:它从标准输入读取字符串时不检查缓冲区长度,如果输入超过99个字符,就会越界写入s之后的相邻内存。现代C标准已经移除了gets,更安全的是fgets(s, sizeof(s), stdin)fgets的第二个参数是缓冲区大小,第三个参数指定读取哪个流;它会把末尾的换行符也读进来,所以输出时要处理掉,否则逆序结果会变成以换行符开头。

3.2 三种典型实现方式对比

第一种是原地交换,也是面试笔试里比较喜欢的写法:

void reverseStr(char *s) { int left = 0; int right = strlen(s) - 1; while (left < right) { char t = s[left]; // 临时变量保存左侧字符 s[left] = s[right]; // 右侧字符覆盖到左侧 s[right] = t; // 保存的左侧字符写入右侧 left++; right--; } }

这种写法本质上是第2章swap指针思路的复用,left和right是两个下标,循环条件left < right在奇偶长度下都成立。如果不写left++right--,指针就永远停在原地死循环。

第二种是用递归实现反转,代码量小但调用栈深,不适合长字符串,适合用来理解函数调用过程:

void reverseRecursive(char *s, int low, int high) { if (low >= high) return; char t = s[low]; s[low] = s[high]; s[high] = t; reverseRecursive(s, low + 1, high - 1); }

第三种是只打印不修改原字符串,从len - 1往前输出。做题时选择哪一种,取决于后续是否需要继续使用原字符串。下面这张表可以做快速选型:

实现方式时间复杂度额外空间是否修改原字符串
原地交换O(n)O(1)
递归O(n)O(n)调用栈
逆序输出O(n)O(1)

3.3 用fscanf和fprintf把结果写进文件

例题里写到文件时,最常用的是fscanffprintf。要求是:从文本文件逐行读入字符串,每行逆序后写入另一个文件。

#include <stdio.h> #include <string.h> int main() { char line[256]; FILE *fin = fopen("input.txt", "r"); FILE *fout = fopen("output.txt", "w"); if (fin == NULL || fout == NULL) { perror("fopen failed"); return 1; } while (fgets(line, sizeof(line), fin) != NULL) { size_t len = strlen(line); if (len > 0 && line[len - 1] == '\n') { line[len - 1] = '\0'; len--; } for (size_t i = len; i > 0; i--) { fputc(line[i - 1], fout); } fputc('\n', fout); } fclose(fin); fclose(fout); return 0; }

finfout是两个FILE *类型的文件指针。fopen("input.txt", "r")里“r”表示只读,“w”表示只写:output.txt不存在时创建,存在时清空后重写。fgets(line, sizeof(line), fin)每次最多读256字节的一行;代码里检查line[len - 1] == '\n'是为了处理源文件末尾换行符的问题。这里很容易踩的坑是漏掉对换行的处理,导致output.txt里每一行逆序后都多出一个空行。

4. 100例里高频易错点与调试:为什么你的代码“感觉对了但输出不对”

4.1 浮点数相等判断

C语言例题里有一类历久弥新的题:判断浮点运算结果是否等于某个数。比如判断0.1 + 0.2是否等于0.3。直接写成if (a == b)绝大多数情况下不成立,因为浮点数在二进制里无法精确表示所有十进制小数。常见做法是使用误差范围:

#include <math.h> double a = 0.1 + 0.2; double b = 0.3; if (fabs(a - b) < 1e-9) { printf("equal\n"); } else { printf("not equal\n"); }

fabs计算绝对差值,1e-9是误差阈值。阈值要按题目精度要求设置:要求小数点后6位就取1e-7到1e-8。在Linux上用gcc编译时记得加-lm链接数学库,完整命令是gcc test.c -lm -o test,否则会报出对fabs的未定义引用。

4.2 字符串函数与数组越界

例题里让用strcpy复制字符串的位置很多,很多人会忽略目标数组的长度。strcpy循环拷贝src字符到dst,直到遇到\0,它完全不关心dst实际有多大。如果src是长度11的“hello world”字符串,目标数组却只有char dst[10],就会把数据写到dst边界之外。更稳妥的写法是用strncpy再加手动补结束符:

char src[] = "hello"; char dst[4]; strncpy(dst, src, sizeof(dst) - 1); dst[sizeof(dst) - 1] = '\0';

这里sizeof(dst) - 1保证最多复制3个字符,留一个位置放\0strncpy自己的坑在于:如果src长度恰好等于n,它不会自动补\0。所以多写一行dst[sizeof(dst) - 1] = '\0';看起来冗余,却可以避免后续调用strlen时读越界。这类题考的核心从来不是函数原型背诵,而是你有没有意识到“字符串本质是字符数组,数组大小必须由调用者负责”。

4.3 用valgrind和gdb精准定位问题

例题代码写多了,最气人的是程序不崩溃、输出却乱七八糟。这时不要盯着代码空想,先把工具用起来。Linux下先检查内存问题:

gcc -g -o demo demo.c valgrind --leak-check=full --show-leak-kinds=all ./demo

-g让编译产物带上调试符号表,valgrind才能报告具体行号。--leak-check=full输出每块泄漏内存的分配位置。输出里最值得关注的两类提示是“Invalid read/write”和“definitely lost”。前者表示数组下标越界或指针访问了非法地址,后者表示malloc的内存没有free。C语言内存管理的绝大多数运行期问题,用这一条命令就能暴露出来。

如果只是逻辑不对,用gdb在可疑行打断点:

gdb ./demo b 23 run print left

b 23给第23行下断点,run运行程序,print left查看当前变量值。下一步用next逐行执行,观察left和right的变化。例题里80%的“反直觉输出”都来自变量在某个作用域被意外修改,gdb把变量打印一遍,通常比阅读十分钟代码更有效。

对应这几类高频问题,可以列一个自查表:

现象常见原因第一步排查手段
输出乱码数组越界或字符串缺\0valgrind检查非法写
结果差很小浮点数相等误判改用fabs差值比较
程序崩溃指针未初始化或越界gdb查看调用栈
文件内容缺失fclose前忘记刷新检查返回值

5. 把100例变成你的第一份可维护代码

5.1 给例题加测试用例,不要再用眼睛验证

刷到后期,手动输入数据验证会越来越低效。我建议用assert做自动化断言,把每个例题的边界场景固定下来:

#include <assert.h> #include <string.h> void reverseStr(char *s); // 你要测试的函数 void test_reverseStr(void) { char s[] = "abc"; reverseStr(s); assert(strcmp(s, "cba") == 0); char s2[] = ""; reverseStr(s2); assert(strcmp(s2, "") == 0); } int main(void) { test_reverseStr(); return 0; }

assert中的表达式为假时,程序会终止并打印出错行号。每当你修改了reverseStr实现,重跑一次测试就能马上知道哪个边界场景被破坏了。链表反转、文件读写这类题目也能用同样的方式构造固定输入,断言输出结果。这样练下去,你不但完成了一道题,还顺手攒下了一套可以回归测试的小型测试集。

5.2 一个自查技巧:先画内存图,再写代码

我在写任何涉及指针或数组的例题前,都会先在草稿纸上画出“变量名、地址、地址里的值”三列。以单链表反转为例:画三个节点,然后一步步推演prev、head、next三个指针在每个循环周期的位置,等推演出的循环能从头顺到尾,再回到编辑器里写代码。这套习惯在C语言项目里比在Python或Java里更有价值,因为它能逼你确认每个赋值操作发生在哪块内存上,而不是停留在“感觉应该这样写”的层面。

当你能独立写完绝大部分例题,并且给它们配上断言测试之后,不妨再往前一步:把每道题的主逻辑抽成参数明确的函数,不要把所有代码堆在main里。一百道题整理完,你手里就有了一小撮命名清晰、边界严谨的C语言工具函数。以后在项目里遇到数组处理、字符串操作或链表遍历,可以直接把你刷过的这套代码拿过来改改参数复用。例题的价值从来不是做一遍就够,而是经历一次从题目到可复用函数的完整转化。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询