C语言基础避坑:从最大公约数到补码、scanf与浮点比较

发布时间:2026/11/3 17:21:38

C语言基础避坑:从最大公约数到补码、scanf与浮点比较 简介面向C语言初学者与程序设计入门者的系统学习资料以单份PDF文档形式梳理从计算机与编程语言基础到判断结构的完整知识脉络。压缩包内共1个PDF文件体积约1.57MB轻量易获取适合在电脑或移动端直接阅读与标注。内容围绕C语言的核心语法与工程定位展开先说明编译与解释两种执行方式的差异以及C语言从B语言到C99标准的演进再依次讲解变量初始化、scanf返回值的三种情形、表达式与运算符优先级、赋值与递增递减运算符并配有最大公约数、英制身高换算、时间差计算等示例代码。资料已有13516人学习可作为课堂笔记的补充或复习速查清单帮助读者把零散语法点串成体系边读边敲代码巩固理解。1. 一份 C 语言基础 PDF 文档从最大公约数讲到补码的完整链路很多人拿到一份 C 语言基础 PDF 资料习惯从第一页顺序翻翻到switch-case那节就困了合上文档第二天再从第一页开始。这份基础文档的组织路子不太一样它用辗转相除法开篇先把「变量怎么换、循环怎么停」这两件事立住再回头讲编译与解释、scanf 的返回值、补码、浮点比较这些真正让人翻车的地方中间穿插英尺英寸换算、时间差、猜数字、正序分解整数几个能直接跑的小程序。它适合两类人——正在补语法但写不出完整程序的初学者以及带课程设计、需要一份能照着讲一遍的从业者。下面按文档的实际章节顺序拆每个环节落到能复现的命令、参数和排查手段。2. 编译还是解释从辗转相除法那段 while 说到 -stdc89 的报错行2.1 解释和编译不是语言的属性是执行方式文档里有一句话值得反复念语言本身没有解释和编译的区分任何语言都可以解释执行或编译执行只是具体执行方式不同。这句话解决了一个很常见的误解——不少人以为 C 天生就是编译型语言Python 天生就是解释型语言实际上 C 也有解释器实现Python 也能打包成二进制。区别在于取舍解释型方式在程序运行过程中还能改源代码调试周期短编译型方式有一个确定的运算性能跑起来之后行为稳定。C 语言最终落在编译这条路上跟它的历史有关。文档里交代得很清楚C 从 B 语言发展而来B 从 BCPL 发展而来BCPL 又受了 FORTRAN 的影响BCPL 和 B 都支持指针间接方式所以 C 也支持1973 年 3 月第三版 Unix 上出现了 C 编译器同年 11 月第四版 Unix 完全用 C 重写。一门语言能用自己的编译器重写自己的操作系统这件事本身就说明了编译执行对性能确定性的要求。落到操作层面C 代码从源文件到能跑的程序是四步预处理、编译、汇编、链接。日常用一条命令把四步串起来就够了但每一步出问题时的报错信息长得完全不一样认得出是哪一步排查能省一半时间。# 一步到位源文件直接生成可执行文件 gcc -stdc99 -Wall -Wextra -O2 -o gcd gcd.c # 分步执行报错时能定位到具体阶段 gcc -stdc99 -E gcd.c -o gcd.i # 预处理展开头文件和宏 gcc -stdc99 -S gcd.i -o gcd.s # 编译生成汇编 gcc -stdc99 -c gcd.s -o gcd.o # 汇编生成目标文件 gcc gcd.o -o gcd # 链接拼上库函数先说逻辑第一条命令是日常用法-o指定输出文件名不写的话在 Linux 上默认叫a.out过两天你自己都认不出哪个文件是哪个。后面四条是拆开看每一步的中间产物.i是预处理后的纯 C 代码.s是汇编.o是机器码但还没链接链接阶段才会去libc里找printf的实现。再说参数-stdc99指定语言标准这一项后面会专门讲它能救多少命-Wall -Wextra打开常用警告和额外警告很多「运行结果不对但编译通过」的问题加上这两个参数就能在编译期被抓出来-O2是优化等级学习阶段其实可以不加加了之后gdb单步调试会跳行反而不方便对照源码。提示初学阶段建议先用gcc -stdc99 -Wall -Wextra -g编译-g带上调试符号出问题能挂gdb看变量。等程序逻辑稳定了再去谈-O2的性能。2.2 辗转相除法三轮变量追踪比单步调试更快文档开篇那个最大公约数的算法描述只有三步如果 v 等于 0计算结束u 就是最大公约数如果 v 不等于 0计算 u 除以 v 的余数让 u 等于 v而 v 等于那个余数回到第一步。这三步翻译成 C 就是一个while循环加一个临时变量关键在于那个临时变量不能省。#include stdio.h int main(void) { int u 32; int v 26; while (v ! 0) { int temp u % v; /* 必须先存余数下一步 u 就被 v 覆盖了 */ u v; v temp; } printf(%d\n, u); /* 循环结束时 v 为 0u 即最大公约数 */ return 0; }逻辑说明循环体里三行的顺序是有讲究的。如果先写u v;再写v u % v;那么u % v里的u已经是新的值算出来的余数完全不对。用一个temp暂存余数等于给赋值操作留了一个「后悔药」。文档在后面讲交换两个变量的值时用的也是同一招t a; a b; b t;这是同一类思维赋值会覆盖覆盖之前先备份。参数说明与手工验算初始u32, v26。第一轮temp 32 % 26 6然后u26, v6第二轮temp 26 % 6 2u6, v2第三轮temp 6 % 2 0u2, v0循环条件v ! 0不成立退出输出 2。32 和 26 的最大公约数确实是 2。我一般会让学生在纸上把这三轮画成一张表而不是直接开调试器。原因很实际调试器一次只给你看一个瞬间的变量值纸上的表能同时看到「上一轮」和「这一轮」的对照哪一步开始偏离预期一目了然。等表画顺了再上gdb就是验证而不是摸索。gcc -stdc99 -Wall -Wextra -g -o gcd gcd.c gdb ./gcd # 在 gdb 里break main / run / next / print u / print v / continuebreak main在入口下断点next逐行执行不进入函数print u打印当前值。这套组合拳对任何包含循环的小程序都通用不只是辗转相除。2.3 C89 和 C99 的差异会精确落到某一行文档里提到一个现在很多人已经感知不到的限制ANSI C 标准只能在代码开头的地方定义变量C99 则是只要在使用前定义就可以。这条差异在今天的环境里非常容易撞上因为很多教学环境默认还是老的编译器配置而学生从网上抄来的代码全是 C99 写法。#include stdio.h int main(void) { int n 0; if (scanf(%d, n) ! 1) { return 1; } int fact 1; /* C89 要求这行必须在所有语句之前 */ for (int i 1; i n; i) { /* C89 报错for 里不能定义 i */ fact * i; } printf(%d! %d\n, n, fact); return 0; }用两套标准各编译一次报错位置能直接指出来gcc -stdc89 -Wall -o fact fact.c # error: for loop initial declarations are only allowed in C99 or C11 mode # error: ISO C90 forbids mixed declarations and code gcc -stdc99 -Wall -o fact fact.c # 通过逻辑说明第一类报错是for循环里声明int iC89 不认这种写法第二类报错是int fact 1;出现在一条if语句之后C89 要求所有变量声明集中在块的开头。这两条是 C89 与 C99 之间最高频的差异看懂报错原文比背结论有用得多。参数说明-std后面除了c89、c99还有c11、c17等取值学校里常见的默认值是gnu17或gnu11所以同样的代码在不同机器上表现不一样。当你在自己电脑上跑得好好的拷到实验室机器上就编译不过第一件事就是问对面用的-std是什么。文档里另外提到//行注释仅在 C99 有用也是同一类问题用-stdc89编译一份带//注释的代码报错位置会落在注释那一行看上去莫名其妙。注意如果你的代码要交给别人编译把-std写进 Makefile 而不是靠口头约定这个习惯能省掉大量「我这边没问题」的扯皮。3. scanf 与表达式三种返回值、一张优先级表和 12.0 这个小数3.1 scanf 的返回值分三种情况每一种都对应一类 bug文档在这部分讲得很实在scanf是有返回值的返回值可以分成三种情况。正整数表示正确输入参数的个数返回 0 表示用户的输入不匹配无法正确输入任何值EOF是在stdio.h里面定义的常量通常值为 -1表示输入流已经结束。#include stdio.h int main(void) { int a 0; int b 0; int ret scanf(%d %d, a, b); if (ret 2) { printf(两个都读到了: a%d b%d\n, a, b); } else if (ret 1) { printf(只读到第一个: a%db 仍是旧值 %d\n, a, b); } else if (ret 0) { printf(格式不匹配a、b 都没被写入\n); } else if (ret EOF) { printf(输入流结束\n); } return 0; }逻辑说明四种分支对应四种真实输入。输入3 4两个%d都匹配成功返回 2。输入3,4第一个%d读到 3 后遇到逗号停下第二个%d因为逗号卡住只成功一个返回 1此时a是 3b保持初始化时的 0。输入,3 4第一个字符就是逗号两个都没读到返回 0a和b都是初始值。按 CtrlZ 再回车Windows或 CtrlDLinux/Unix输入流结束返回EOF。参数说明这里最容易翻车的是返回 1 和返回 0 这两种情况——程序不会崩也不会报错只是默默拿着旧值往下算算出来的结果看起来毫无规律很多人就此开始怀疑编译器。把返回值接住并显式分支这个黑匣子就打开了。另外b在声明时初始化为 0 是有意为之如果写成int b;返回 1 的时候b是栈上的随机值打印出来更难解释。提示判断「输入是不是整数」这件事靠scanf的返回值比靠猜靠谱得多。养成写if (scanf(...) ! 期望个数) { 处理错误 }的习惯比事后调试便宜。3.2 优先级表不是用来背的是用来对照排查的文档给了一张完整的优先级表单目 -和 --最高自右向左然后是* / %再是 -再是关系运算 再是 !再是再是||最后是赋值 - * / %自右向左。背这张表没什么意义写代码时该加括号就加括号读代码时拿它当对照表才有价值。真正需要查表的场景往往出现在强制类型转换和算术运算混在一起的地方。#include stdio.h int main(void) { int a 5; int b 6; /* 强转的优先级高于除法先 (double)a 得到 5.0再除以 6 */ double d1 (double)a / b; /* 括号把除法包起来先算 5/6 得整数 0再转 double 得 0.0 */ double d2 (double)(a / b); printf(d1 %.4f\n, d1); /* 0.8333 */ printf(d2 %.4f\n, d2); /* 0.0000 */ return 0; }逻辑说明两行的差别只在一对括号的位置结果差了一个数量级。d2那种写法是文档里点名的典型误用——很多人以为只要写了(double)就能得到小数实际上整数除法在括号内就已经把小数部分丢干净了。参数说明a和b都是inta / b走整数除法结果是 0。要得到浮点结果要么像d1那样先把其中一个操作数转成double要么写成1.0 * a / b这种用浮点字面量把整个表达式「拉」到浮点域的做法文档在求平均数那段代码里用的就是1.0*sum/count。赋值运算也有同样的陷阱。文档里讲「赋值也是运算也有结果」a 6的结果就是 6所以a b 6等价于a (b 6)。这个特性在链式赋值时很方便但在条件判断里就是灾难if (a b)不是比较是把b赋给a然后判断a非零编译器一般会给警告但警告不是错误程序照样跑。文档里专门把if(ab)拎出来当反面例子原因就在这里。3.3 英尺英寸那段代码12 和 12.0 差 18 厘米文档里那段英制单位转换的代码值得单独拆一遍它是「整数除法」这个坑最直观的现场。#include stdio.h int main(void) { int foot 0; int inch 0; printf(请分别输入身高的英尺和英寸如输入\5 7\表示5英尺7英寸); if (scanf(%d %d, foot, inch) ! 2) { return 1; } /* 12.0 而不是 12整数除法会把 inch/12 截断成 0 */ printf(身高是 %.4f 米。\n, (foot inch / 12.0) * 0.3048); return 0; }逻辑说明inch / 12.0里12.0是浮点字面量C 会把inch自动提升为double再做除法结果保留小数。如果把12.0写成12inch / 12就是整数除法输入 7 英寸时结果是 0整个表达式变成(5 0) * 0.3048 1.5240米。正确答案是(5 7/12.0) * 0.3048 ≈ 1.7018米两者差 17.8 厘米。参数说明0.3048是英尺到米的换算系数%.4f保留四位小数。这段代码的输入校验用! 2卡住避免用户输入非数字时拿着初始值算出 0 米这种奇怪结果。同一章的时间差程序是另一个角度t1 hour1*60 minute1把时间统一折算成分钟t t2 - t1得到分钟差再t/60和t%60拆回小时和分钟。这里%用在正数上没问题但如果第二个人输入的时间比第一个人早t是负数t/60和t%60的行为跟数学直觉不一致——整数除法向零取整-95/60得 -1-95%60得 -35拼起来是「-1 小时 -35 分」。这个边界文档没展开但实际写考勤、计时类程序必然会遇到稳妥做法是先判断大小交换两个时间点最后再把符号补回去。3.4 sizeof、补码与浮点比较三个容易想当然的地方文档后半段讲类型有几处细节在写实际代码时会反复用到。sizeof是个运算符给出某个类型或变量在内存中占据的字节数写法是sizeof(int)或sizeof(i)注意它不是函数不用包含头文件。文档给的类型大小是char1 字节short2 字节int取决于编译器通常是一个字long取决于编译器long long8 字节。这意味着同一份打印sizeof(int)的代码在 32 位和 64 位环境里可能输出不同的数字写序列化、网络协议、文件格式这几类程序时必须以实际sizeof为准不能按记忆写死。补码那段的推导值得跟着走一遍。文档的思路是因为0 - 1 -1所以用00000000 - 00000001借位得到1111111111111111当纯二进制看是 255当补码看是 -1。同样的道理8 位有符号数的范围是 -128 到 12700000000到01111111对应 0 到 12710000000到11111111对应 -128 到 -1。补码的精妙之处在于能把负数当普通数参与运算-1 1直接相加就是1 00000000溢出位丢掉剩00000000即 0不需要额外的符号位判断逻辑。文档还提了一句unsigned的初衷不是为了扩展能表达的范围而是为了做纯二进制运算主要是移位。这句话在做位操作时非常关键有符号数右移是算术移位还是逻辑移位取决于实现无符号数右移一定是补 0这就是为什么哈希、编解码、位图这类代码里到处是unsigned。浮点比较那段文档给的代码是拿1.345f 1.123f跟2.468直接做结果是「不相等」。原因在于float只有 7 位左右有效数字二进制无法精确表示这些十进制小数累加后的值跟字面量2.468差了最后几位。文档给的判断方法是fabs(f1-f2) 1e-12用误差窗口代替相等判断。#include stdio.h #include math.h int main(void) { float a 1.345f; float b 1.123f; float c a b; /* 直接 比较几乎必然失败改用误差窗口 */ if (fabsf(c - 2.468f) 1e-6f) { printf(在误差范围内相等\n); } else { printf(不相等c %.10f\n, c); } return 0; }逻辑说明fabsf是float版本的绝对值函数math.h里还有double版的fabs。误差窗口取1e-6是因为float的有效位数大约 7 位十进制窗口比精度小一两个数量级比较合适如果是double比较窗口可以收到1e-12甚至更小。参数说明编译这个文件要加-lm因为数学库在部分系统上不默认链接。另外文档提到一个更实用的建议——算钱的时候用整数单位取分整数是精确的。这条比任何精度技巧都实在涉及金额、库存、计数的场景一律绕开浮点。4. 分支与循环四个模板的取舍和 mask 的构造过程4.1 if 的单一出口与多出口文档偏向单一出口文档在讲分支时给了一个分段函数的两种写法一种在每个分支里直接printf另一种先用变量f存结果最后统一printf。文档的态度是「更支持单一出口方式」。#include stdio.h int main(void) { int x 0; if (scanf(%d, x) ! 1) { return 1; } int f 0; if (x 0) { f -1; } else if (x 0) { f 0; } else { f 2 * x; } printf(%d\n, f); return 0; }逻辑说明所有分支只负责给f赋值输出集中在一处。好处是后续如果要改成写文件、写日志、返回给调用方只需要改最后一处不用在三个分支里各改一遍。坏处是多了一个变量分支少的时候显得啰嗦。参数说明f初始化为 0 是为了覆盖「三个分支都没进去」这种理论上的情况——虽然这里的if-else链已经穷尽了所有整数但养成初始化的习惯等分支条件变复杂时能救命。文档同时点了一个容易忽略的语法细节if那一行结束的时候没有分号后面的语句写在大括号里。如果手滑写成if (x 0);后面再加语句那个分号就成了一个空语句if只管这个空语句后面的代码无条件执行——编译通过逻辑全错。这种 bug 靠肉眼扫代码很难发现-Wall有时会给empty body in an if-statement的警告值得留意。4.2 switch-case 的 break 穿透既是坑也是特性文档对switch的描述很到位switch可以看作是一种基于计算的跳转计算控制表达式的值后程序跳转到相匹配的case处分支标号只是说明switch内部位置的路标执行完分支最后一条语句后如果没有break就会顺序执行到下面的case里去直到遇到break或switch结束。#include stdio.h int main(void) { int type 0; if (scanf(%d, type) ! 1) { return 1; } switch (type) { case 1: case 2: printf(你好\n); break; /* 1 和 2 共用一段逻辑靠故意不写 break 实现 */ case 3: printf(晚上好\n); /* 这里没有 break会继续执行 case 4 */ case 4: printf(再见\n); break; default: printf(啊什么啊\n); break; } return 0; }逻辑说明输入 1 或 2 都打印「你好」然后跳出这是break穿透的正面用法——多个case共享一段处理逻辑。输入 3 会先打印「晚上好」因为没有break继续往下执行case 4的语句再打印「再见」然后才遇到break跳出。这个行为在有些场景下是有意的比如状态机里一个状态要级联触发后续动作但更多时候是漏写break导致的 bug。参数说明switch的控制表达式只能是整数型结果case后面只能是常量可以是常数计算的表达式C99。文档另外提了一点虽然switch-case能实现分段函数但那种情况尽量用if判断因为switch的语义是「按值跳转」硬套区间判断会把条件写得很别扭。这个判断标准很实用——switch适合枚举、命令码、状态值这类离散取值区间和不等式交给if。4.3 for、while、do-while 的判据文档给了三条文档在循环部分给了一套很干脆的判据如果有固定次数用for如果必须执行一次用do-while其他情况用while。这三条不难记难的是判断「固定次数」到底怎么算。#include stdio.h int main(void) { int n 0; if (scanf(%d, n) ! 1 || n 0) { return 1; } int fact 1; for (int i 1; i n; i) { /* 次数明确从 1 到 n共 n 次 */ fact * i; } printf(%d! %d\n, n, fact); return 0; }逻辑说明阶乘的循环次数由n直接决定属于典型的「固定次数」用for最合适初始化、条件、步进三件事写在一行里读的人一眼能看出循环边界。文档还给了另一种写法把n本身当循环变量往下减for ( ; n 1; n--)最后用一个额外的i保存原始值用于输出——这种写法省一个变量但把输入参数改掉了后面如果还要用n就得提前备份反而不如第一种清爽。参数说明i定义在for里面作用域只到循环结束这是 C99 的特性。文档提到一个内存角度的解释说局部变量在栈内存中存在for循环结束后变量会被及时释放——这个说法在 C 里不太准确C 没有垃圾回收器栈变量的生命周期由作用域决定出了作用域只是编译器不再允许你访问栈空间要等函数返回才回收。真正的好处是可读性i只在循环里有意义写在循环里能明确表达这个意图也避免了跟外面的同名变量冲突。do-while的适用场景是「至少执行一次」文档里数位数那个例子最典型#include stdio.h int main(void) { int x 0; if (scanf(%d, x) ! 1) { return 1; } int n 0; do { x / 10; /* 先除以 10再去掉一位 */ n; /* 位数加一 */ } while (x 0); /* 直到 x 变成 0 */ printf(%d\n, n); return 0; }逻辑说明输入 0 的时候如果写成while (x 0)循环一次都不进位数输出 0但 0 本身是一位数答案应该是 1。do-while保证循环体至少执行一次输入 0 时先x / 10得 0n变 1条件不成立退出输出 1正确。参数说明这个程序只处理非负整数输入负数会因为x 0永远不成立而在第一次循环后退出输出 1。要处理负数得先取绝对值或者单独判断符号。文档在猜数字那个程序里用了do-while加srand(time(0))和rand() % 100 1这里有两个细节srand只需要在程序开始时调用一次放在循环里每轮都调会导致同一秒内生成的随机数完全一样time(0)需要time.h返回的是秒级时间戳所以同一秒内启动两次程序种子相同随机序列也相同。这是rand的固有局限做小游戏够用做需要真随机的场景就得换方案。4.4 正序分解整数mask 是怎么一步步搭起来的逆序输出一个整数很容易不断%10取个位再/10去掉个位就行。正序输出要难一层因为得先知道最高位是哪一位。文档给的方案是先构造一个mask再逐位除。#include stdio.h int main(void) { int x 700; int mask 1; int t x; /* 第一步找到与 x 最高位对齐的 mask */ while (t 9) { t / 10; mask * 10; } /* 第二步逐位取出并输出 */ do { int d x / mask; /* 取出当前最高位 */ printf(%d, d); if (mask 9) { printf( ); /* 最后一位后面不加空格 */ } x % mask; /* 去掉已输出的那一位 */ mask / 10; /* mask 降一位 */ } while (mask 0); printf(\n); return 0; }逻辑说明第一段while里t从 700 开始700 9成立t变 70mask变 1070 9成立t变 7mask变 1007 9不成立退出。此时mask 100正好跟三位数的最高位对齐。第二段do-while里第一轮d 700 / 100 7输出 7x变成700 % 100 0mask变 10第二轮d 0 / 10 0输出 0x还是 0mask变 1第三轮d 0 / 1 0输出 0mask变 0循环结束。最终输出7 0 0。参数说明mask的初始值必须是 1不能是 0否则mask * 10永远是 0。第二段用do-while而不是while是为了处理输入为 0 的情况——mask为 1 时至少要输出一个 0。文档在goto那节还给了一个三重循环找零钱的例子用exit标志一层层break对比用goto直接跳到末尾的out:标号。两种写法都正确goto版本少了两层判断代码更短exit版本不依赖跳转读起来更线性。我的经验是跳出两层以内用标志位三层以上用goto或者干脆把内层循环抽成函数用return后者最干净。文档提到「out:写到前面或许只是格式习惯方便阅读并不影响后面的程序」这句话对——标号的位置编译器不管但读者管写在循环前面确实更容易看到出口在哪。5. 避坑排查读完这份 PDF 之后最容易翻车的五个点5.1 现象代码在自己机器上编译通过换台机器报一堆错原因两份环境的默认-std不同一边是gnu17一边是c89或gnu89for (int i ...)、行注释//、混合声明与语句这几类写法在老标准下全部非法。解决把-stdc99或更高的c11写进编译命令和 Makefile不要依赖系统默认值。收到别人的代码先看有没有 Makefile没有就问一句用的什么标准比事后逐条改代码快得多。5.2 现象程序不崩不报错算出来的结果毫无规律原因scanf返回值没接输入格式不匹配时变量保持旧值或未初始化值后续计算全建立在脏数据上。返回 0 和返回 1 这两种情况最隐蔽因为程序照常往下跑。解决每次scanf都把返回值接住并显式分支变量声明时给初值让「没读到」这种情况有一个可预测的结果。调试时第一件事是把所有输入变量的值打印出来而不是盯着计算结果找规律。5.3 现象明明写了强制类型转换除法结果还是整数原因(double)(a / b)是先把a / b按整数除法的规则算完再转括号内的精度已经丢了。强转的优先级高于四则运算所以(double)a / b和(double)(a / b)是两个完全不同的表达式。解决要么把强转加在其中一个操作数上(double)a / b要么用浮点字面量把表达式拉过去1.0 * a / b。改完拿一组会产生小数的输入验算一遍比如a5, b6应该得到0.8333而不是0.0000。5.4 现象switch 里某个分支执行完接着又执行了下一个分支的代码原因漏写breakswitch的默认行为是顺序穿透。如果漏写的是最后一个分支之前的那个症状是「多打印了一行」如果漏写的是中间的可能连累后面所有分支都跑一遍。解决先给每个分支补上break让它行为正确再逐个审视哪些穿透是有意的多个case共用逻辑有意的那些加一行注释说明避免下一个人当成 bug 改掉。编译器一般不给穿透警告靠人工 review。5.5 现象浮点数比较永远不相等循环条件永远差一点点原因十进制小数在二进制里多数无法精确表示0.1 0.2 ! 0.3这类经典问题会渗透到所有用比较浮点的地方包括循环的终止条件。解决比较改成误差窗口fabs(a - b) 1e-6循环条件尽量用整数计数器别用浮点累加。涉及金额一律换成整数分。文档里那句「算钱的时候用整数整数是精确的」值得抄在便签上。6. 把 PDF 当自检清单一份代码 review 表和三个进阶练法这份文档的价值不在于读完而在于写完之后拿出来对一遍。我把它压成一张八项自检表每次提交代码前扫一眼检查项对应文档位置不合格的表现编译命令带-std和-Wall -Wextra标准与变量定义只写gcc xxx.c警告全忽略所有变量声明时初始化变量与输入调试时打印出随机值scanf返回值全部接住输入函数输入格式错时结果无规律除法两边类型一致或显式提升表达式与类型转换该出小数的位置出 0switch每个分支有break或注释说明分支多执行了不该执行的分支循环边界用整数浮点不做计数器循环与浮点循环多跑或少跑一次浮点比较用误差窗口浮点类型永远为假跨层跳出不超过两层goto 与 break三层break加标志位满天飞三个练法按难度排。第一个是把文档里的每个小程序都手敲一遍然后故意改坏把12.0改成12把do-while改成while把break删掉看输出怎么变。改坏再改回来这个过程比读十遍正确代码印象深。第二个是给每个程序补上输入校验和边界处理比如时间差程序处理第二个人时间更早的情况正序分解处理负数和 0猜数字处理输入非数字。文档给的示例代码大多假设输入合法把这块补齐代码的健壮性会上一个台阶。第三个是挑一个文档里用循环实现的功能改成递归实现再改回来对比两种写法在边界情况下的表现——辗转相除法和阶乘都适合这么练。有一个习惯我保持了挺久每写完一个带输入的程序先不跑正常数据先跑四组异常输入——空输入、格式错误、边界值、负数。这四组能过正常数据基本不会出问题。这个习惯是从一次课程设计的翻车经历来的当时一个统计程序在演示现场因为助教输入了一个逗号而全线崩溃我在台上盯着屏幕找了十分钟才反应过来是scanf返回值没接。从那以后我每次写输入都强制走一遍这四组宁可多写几行if也不想再经历一次那种场面。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/11/3 17:16:38

STM32F103ZET6用FSMC驱动TFTLCD实战指南

简介:本资源是一套面向嵌入式初学者与STM32进阶开发者的FSMC驱动TFTLCD显示完整实验例程,基于主流型号STM32F103ZET6芯片,聚焦硬件接口配置、LCD底层驱动移植与图形界面基础实现。压缩包共204个文件,涵盖58个头文件(.h…

2026/11/3 17:16:38

ZCode开源:AI编程智能体如何自动拆解任务并操作代码

ZCode 开源那天,我第一反应是去 GitHub 仓库看了一眼,标题是“ZCode”,项目描述写得很简单:一个 AI 编程智能体,上一个命令就把任务拆解成一步步计划,调 Shell 执行命令、读代码库、改文件、跑测试&#xf…

2026/11/3 18:27:03

【Go语言基础【17】】切片:一种动态数组

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 18:27:03

Qwen3-235B-A22B-Instruct-2507 的性能评测

Qwen3-235B-A22B-Instruct-2507 的性能评测 【免费下载链接】Qwen3-235B-A22B-Instruct-2507 Qwen3-235B-A22B-Instruct-2507是一款强大的开源大语言模型,拥有2350亿参数,其中220亿参数处于激活状态。它在指令遵循、逻辑推理、文本理解、数学、科学、编程…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 1:04:48

STM32实战:基于MAX14431数字隔离器的SPI隔离通信方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 1:04:48

工业SPI隔离通信设计:MAX14431BAEE+与STM32L4A6RG实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 1:04:48

STM32+NRF24L01无线抢答器:硬件设计、通信协议与低功耗实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑