简介《浙大翁恺C语言习题集源码》是一款面向C语言初学者的编程练习资料源自浙江大学翁恺教授的课堂习题体系覆盖整数运算、时间计算、数字逆序、BCD解密、水仙花数、九九乘法表、素数统计、猜数字游戏、分数约分、数字拼音转换、单词长度统计、矩阵局部极大值等十余类经典编程任务。资料中每个题目均配有明确的输入输出格式说明和完整的C语言实现可以帮助学习者理解从基础语法到逻辑控制、数组与字符串处理、简单算法设计等核心知识点。压缩包内共4个文件包含txt说明文档、Python脚本、HTML演示页面及inscode代码文件合计仅9KB轻量便携但代码集中便于直接阅读和修改运行。目前已有1784人学习使用适合零基础入门自修、课程同步练习及期末复习能有效提升编程手感与独立解题能力。1. 入手C语言习题集源码包先弄清它是什么很多初学C语言的人拿到一套习题集源码第一反应是“抄就完事了”第二反应是“代码怎么编译不过”。这套C语言编程练习源码包来自某高校公开课的配套习题资料对应的是网上流传很广的一套课程练习题但它并不是标准答案合集而是一套可以反复对照、调试和测试的练习材料。它的价值不在于让你复制粘贴交作业而在于让你看到同一道题在输入边界、内存处理和格式化输出上可以做到什么程度。适合正在学C语言的大学生、准备面试笔试的求职者以及想带着学生做编程练习的老师。下面几章会从源码结构、编译、做题思路、避坑和进阶工具几个方向把它拆开。2. 源码包结构与编译运行先把每个.c文件跑通拿到源码先别急着看代码先把目录结构理清楚。这类习题包一般按章节或题型组织文件文件名里带编号与题型标识。先过一次结构后面的定位会快很多。2.1 目录结构用编号与关键词定位题目常见的结构是这样的目录/文件内容定位关键词ex01_printf.c输入输出基础printf, scanfex02_loop.c循环控制for, while, do-whileex03_array.c一维/二维数组冒泡、逆序、查找ex04_string.c字符串处理strlen, strcmp, 回文ex05_struct.c结构体与排序qsort, 链表ex06_recursion.c递归与分治斐波那契、汉诺塔文件名里的编号直接对应题目顺序通常是课程作业的顺序。有些包会在同目录下放一个readme.txt里面写每道题的输入输出示例。我建议先把那个readme读完因为源码里的注释往往只给结果不给思路直接看代码容易被解法带偏反过来读readme先想一步自己会怎么写再看代码收获完全不同。目录里通常还会混着编译产生的.exe或.out文件这类文件可以忽略也别提交到Git仓库里。判断一道题属于哪个文件有个小技巧文件名里的编号对不上时就搜代码里的关键输入函数scanf里第一个格式串是什么题目基本就定位了。2.2 编译环境Windows与Linux下gcc命令与参数单文件C程序直接用gcc编译。在Linux或macOS终端里执行gcc -Wall -Wextra -stdc99 -o ex03_array ex03_array.c-Wall开启大部分警告-Wextra再开一层边界警告-stdc99指定C99标准因为习题包里很可能用到了for循环内声明变量这类C99语法-o ex03_array指定输出文件名。不加这些参数也能编译但遇到数组越界、类型不匹配时不会给你任何提示等运行出错再去查会很费时间。我一般会从一开始就带着-Wall编译让警告尽早暴露问题。Windows下如果装了MinGW把gcc加到环境变量后在cmd里跑同样的命令。如果报“找不到gcc”说明MinGW没装或没配置环境变量把bin目录加进PATH即可。编译产物在Windows下是ex03_array.exe双击运行对带输入的程序没有意义要在cmd里先切到当前目录再执行cd /d D:\c_exercises ex03_array这里有个Windows下的常见现象程序执行完窗口直接闪退看不到输出。这不是代码错了是控制台程序跑完就结束。临时加一行system(pause)能看到结果但提交给判题系统前必须删掉否则多余输出会被判错。更干净的做法是在cmd里运行程序输出会留在命令行窗口里。2.3 从单文件到多文件把解法改造成可复用工程习题集里的代码大多是单文件全部逻辑放在main里。当你做完十几道题之后会发现很多函数是重复的比如数组逆序、字符串统计、数值交换。这时候把它拆成头文件和源文件会更顺手。一个小例子// utils.h #ifndef UTILS_H #define UTILS_H void swap(int* a, int* b); int reverse_int(int n); #endif// utils.c #include utils.h void swap(int* a, int* b) { int tmp *a; *a *b; *b tmp; } int reverse_int(int n) { int res 0; while (n ! 0) { res res * 10 n % 10; n / 10; } return res; }改造的思路是把通用函数放进utils习题入口文件里只保留scanf流程和printf输出。编译时要同时编译两个文件gcc -Wall -Wextra -stdc99 -o app main.c utils.c这里的坑在于如果只编译main.c链接阶段会报“undefined reference to swap”因为函数的实现不在当前编译单元里。需要把utils.c也一起传给gcc或者先编译成目标文件再链接。拆文件不是必须的但如果你准备把习题集当代码库积累越早习惯这种组织方式越好。还有一类情况不建议拆题目本身只有十几行拆成三个文件反而增加阅读负担。判断标准很简单——这个函数第二道题还会不会用。会复用就拆不会复用在当前文件里写个static函数就行。保持适度组织比一股脑全拆更接近真实工程的做法。3. 从读题到代码拆解习题集的典型解题路径源码包里每道题都有题目描述但很多人做题的顺序是先看题然后直接写代码写完一跑发现不对再回头读题。这个流程浪费时间的点在于题目的输入范围、输出格式、边界条件通常最后才被注意到。正确的顺序应该是先读输入输出约束再决定解法最后动手写。3.1 先读输入输出把边界条件写在注释里做题之前先把题目里的三类信息划出来输入范围、输出格式、边界条件。举例如果题目是“输入n个整数输出它们的逆序”你需要确认n最大是多少、输入一行还是多行、数字之间用什么分隔、逆序输出时末尾有没有空格。这些信息不写清楚代码就是猜。我习惯在写代码前先这样占位/* 输入第一行一个整数 n (1n1000) 第二行 n 个整数空格分隔 输出n 个逆序整数每个数之间一个空格行尾无多余空格 */这段注释就是后面写scanf和printf的依据。输出格式里“行尾无空格”是最容易被忽略的很多判题系统对行尾空格不敏感但人眼检查和对比输出时会显得不一致。先把格式写进注释写代码时照着来能省掉后面一次重写。输入范围常见的四种情况固定个数、文件结束EOF结束、多行输入、字符串中间带空格。固定个数最简单scanf按个数读EOF结束要用while (scanf(...) ! EOF)或while (scanf(...) 1)多行输入要注意行末换行是否被读走字符串带空格就要用fgets而不是scanf(%s)。每一种对应的代码骨架差异很大读题时先判断属于哪种比撸起袖子就写稳得多。3.2 四种高频题型的骨架数组、字符串、结构体、递归习题集里反复出现的题型也就是那几类。数组题的核心是下标控制字符串题的核心是结尾标志结构体题的核心是比较函数递归题的核心是递归出口。给四个最小可用的骨架/* 数组逆序 */ int arr[1000], n, i; scanf(%d, n); for (i 0; i n; i) scanf(%d, arr[i]); for (i n - 1; i 0; i--) { printf(%d, arr[i]); if (i 0) printf( ); }/* 统计字符串中数字字符个数 */ char s[256]; int cnt 0, i 0; fgets(s, sizeof(s), stdin); while (s[i] ! \0 s[i] ! \n) { if (s[i] 0 s[i] 9) cnt; i; } printf(%d\n, cnt);/* 结构体排序按分数从高到低 */ typedef struct { char name[32]; int score; } Student; int cmp(const void* a, const void* b) { return ((Student*)b)-score - ((Student*)a)-score; } /* 排序调用qsort(stu, n, sizeof(Student), cmp); *//* 递归求最大公约数 */ int gcd(int a, int b) { return b 0 ? a : gcd(b, a % b); }这四个骨架分别对应习题集里最常出现的题目类型。数组逆序这段要注意循环里的n-1起点和i0时的空格判断这两处是新手最容易写拧的。字符串统计用fgets而不是gets是因为gets在C11标准里已经移除很多现代编译环境不支持fgets能指定最大读取长度避免缓冲区溢出。结构体排序的cmp函数返回值用差值的写法只适用于分值范围小的场景如果分数可能超出int差值范围建议改成更保守的比较方式。递归骨架的核心是b0这个出口漏了它就直接无限递归栈溢出。3.3 用测试用例验证一张表比十遍目测有用代码写完不要只看样例通过就认定正确。我自己会做一张小表把题目给的样例和两三个自己造的边界用例放一起逐条过。格式这样列输入第一行n第二行数列期望输出实际输出状态3 / 5 1 88 1 58 1 5通过1 / 424242通过1000 / 1000..1递减序列1..1000递增序列与期望一致通过边界用例的意义在于当n等于最小值或最大值、输入里混入负数、字符串中间有空格时代码是否还成立。习题集源码包里每个.c文件头部一般有一组样例输入输出先跑通它们再补两三条自己的用例。用脚本批量做这个验证后面第五章会讲。4. 编译与运行避坑五个高频报错的排查记录这一章写源码包使用过程中最容易翻车的五个问题每条都是“现象→原因→解决”的结构可以当速查表用。4.1 scanf与gets混用导致输入被跳过现象程序先scanf读一个整数再用gets读一行字符串运行时发现gets直接读到了空字符串或上一行残留的换行。原因scanf读取整数时会在输入缓冲区里留下一个换行符gets遇到这个换行符认为当前行已结束于是直接返回空串。这不是编译器问题是输入缓冲机制造成的。解决在scanf和gets之间加一个getchar()吃掉残留换行或者统一用fgets读取所有输入再手动解析。我一般建议改用fgetssscanf的组合因为gets已经废弃fgets能限制长度配合sscanf可以完成格式化解析。char line[256]; int n; fgets(line, sizeof(line), stdin); sscanf(line, %d, n);fgets先读整行sscanf再从字符串里解析整数两个步骤都不会留下残留换行比混用scanf和gets稳定得多。如果后面还要读一行字符串继续用fgets不会出现跳过输入的怪事。4.2 数组越界不报错但结果错现象程序能正常编译运行甚至不崩溃但输出里出现随机数或者某个变量的值莫名被改掉。原因C语言不检查数组越界写入arr[1000]时实际上写到了相邻变量的内存上。这在栈上分配的小数组里很容易发生比如char s[10]却被塞进了一个长度为20的字符串。不崩溃不代表没问题只是还没踩到保护页。解决先把数组容量按题目上限放大——题目说n最大1000就开1000起步不要精打细算。再用编译器和运行时工具兜底编译加-fsanitizeaddress运行时会直接报告越界位置这也是第六章会讲的重要手段。今天还在用“玄学”描述这种问题的人不少其实用AddressSanitizer一跑越界位置立刻现形。常见的越界写法包括for循环里i从1开始读到n把最后一个数写进了越界位置字符串拷贝时没给结尾的\0留位置。这两个场景在习题集里出现的频率最高。4.3 中文字符串乱码与编译器编码问题现象源码里写了中文提示语编译后在终端输出乱码。原因Windows下cmd默认代码页是936GBK而MinGW的gcc默认按UTF-8读取源码文件两边编码不一致输出的字节就乱了。解决源码文件统一保存为UTF-8并在main开头调用setlocale(LC_ALL, )或者在Windows下把文件和编译环境都统一到GBK。最省事的是写英文提示或把中文全部放到注释里程序输出用纯英文。判别编码问题有个小技巧编译时如果警告里出现“unknown escape sequence”之类但代码本身没问题多半就是编码被编译器误解了。4.4 判题式提交多余提示信息被判错现象本地运行输出“请输入n:”结果正确提交到在线练习平台被判“答案错误”。原因在线练习平台把输出和标准答案做逐字符比对任何多余的中文、空格、提示文字都会被当成错误内容。本地人看得舒服的提示信息在判题环境里就是多余输出。解决凡是面向判题系统的习题严格按题目要求的输出格式写不要加任何额外的printf。自己本地调试可以用单独宏或注释区分。这道题在习题集里很容易被误以为“代码没问题”实际上问题就出在多了一个冒号。4.5 void main与返回值隐患现象有些源码包里写的是void main()用现代编译器编译会报“return type of main is not int”或者只给警告运行时直接退出也可能拿不到预期状态码。原因C标准规定main的返回类型必须是intvoid main是部分旧教材和旧编译器时代的写法在很多严格编译选项下不被接受。解决把void main改成int main并在函数末尾加return 0。如果需要把程序的执行结果交给脚本或后续工具用返回值尤其重要脚本里检查$?是否为0就是看main返回的整数。顺手统一成int main是保持题目代码可移植性的底线。5. 把习题集变成自己的代码库Makefile、风格检查与Git管理跑通单个文件只是第一步。当习题数量超过三十道逐个敲gcc命令不现实。用工程化手段把它们管起来。5.1 用Makefile一键批量编译与自测每个.c文件都是一个独立程序写一个简单的Makefile批量编译CC gcc CFLAGS -Wall -Wextra -stdc99 SRCS $(wildcard ex*.c) BINS $(SRCS:.c) all: $(BINS) %: %.c $(CC) $(CFLAGS) -o $ $ clean: rm -f $(BINS) *.exe这段Makefile里的wildcard会把当前目录下所有以ex开头的.c文件收集起来%目标则让make为每个.c文件生成同名可执行文件。运行make后目录里会出现一批可执行文件make clean会清掉它们。如果某个文件编译失败make会停在那个文件上并报错其他文件照常编译。测试阶段可以另写一个test.sh批量跑所有二进制for bin in ex*; do [ -x $bin ] || continue ./$bin in.txt out.txt if diff -q out.txt expected.txt /dev/null; then echo $bin PASS else echo $bin FAIL fi done[ -x $bin ]判断当前文件是否是可执行文件把.c源文件自动跳过。脚本逐一对每个可执行文件跑输入、比对输出第一次全量跑的时候会一次性暴露所有格式错误和边界问题比自己一个个试省太多时间。这里注意diff要求输出逐字节一致所以上一章说的多余空格问题会在测试里立刻暴露。5.2 用clang-format统一代码风格习题集源码手写格式各有偏好有的一行缩进两个空格有的四个有的左大括号另起一行有的跟在行尾。这些不影响运行但影响对照阅读。用clang-format格式化一遍整个目录的风格就统一了clang-format -i ex*.c-i表示直接写回原文件。默认风格基于LLVM可以在目录放一个.clang-format文件调整缩进宽度和括号风格。常用的配置片段BasedOnStyle: LLVM IndentWidth: 4 BreakBeforeBraces: Allman这段配置把缩进改成四个空格大括号改成另起一行。clang-format的效果是格式统一不是逻辑修复所以对已经能运行的代码没有任何风险。就算格式化后编译一遍确认即可。要注意的是clang-format会改变文件里的换行和空格如果之后用git diff对比改动格式化的commit最好和逻辑修改的commit分开不然没法看清真正的改动。5.3 用Git管理进度每道题一个commit把习题集放进Git仓库每完成一道题就提交一次这样能清楚看到每一题的迭代过程。初始化并完成首次提交git init git add ex01_hello.c ex02_loop.c git commit -m 完成输入输出与循环章节提交信息可以带编号比如“完成ex03: 数组逆序”方便之后按编号回溯。写错了想用后悔药就靠Git改坏了就git checkout -- ex03_array.c回滚到上一次提交想对比两个版本的差异用git diff。这套习惯对学生时代的练习项目可能显得重但它的收益是长期积累出一个可以搜索和复用的代码库而不是散落一堆文件名相同的.c。我还习惯在根目录维护一个README.md里面记录每题的状态已通过样例、已过边界测试、未完成。这个清单配合Git状态练习进度一目了然。6. 验证与复盘一套可复制到任何C项目的自检流程做完上面几步代码库能跑起来也归档了但离“确定它是对的”还差一道验证手续。我的自检流程分三档编译期、运行期和回归期。第一档是编译期把编译器警告当成错误gcc -Wall -Wextra -Werror -stdc99 -fsanitizeaddress -o ex03_array ex03_array.c-Werror把所有警告升级为错误有警告就不产出可执行文件逼着把问题在编译阶段解决。第一次用这个参数时会发现很多以前“能编译”的代码其实藏着问题比如未使用的变量、隐式类型转换。加了-fsanitizeaddress后运行期的越界、用后释放会直接报告具体文件和行号。macOS和Linux下这条路很顺Windows下MinGW有的版本不支持AddressSanitizer可以换成Valgrind或用-Werror做编译期兜底。第二档是运行期用自建用例覆盖边界。回到第三章那张测试表把边界用例、非法输入、最大规模输入都过一遍。非法输入指的是比如题目要求正整数时输入负数或零程序应该按预期处理而不是直接崩溃。这一步很多新手不做其实它才是拉开差距的地方。第三档是回归期。改了一道题的实现之后重新跑一遍该题的所有用例再顺手跑一遍相关题目的用例。把第五章的test.sh固定下来每次改完代码直接执行确保修复一个bug没有引入另一个。比如你发现ex03数组越界修完后跑test.sh如果ex04的字符串用例也开始失败说明你的修改波及了公共代码。从那次被一个“不报错但结果错”的数组越界折磨了一下午之后我养成了每次拿到新习题源码都先做三个动作先看readme和目录结构再开-Wall -Wextra编译最后用三组边界用例跑一遍。这套流程用在这份C语言习题集源码包上同样成立换到任何C语言练习项目也成立。希望帮到你。本文还有配套的精品资源点击获取