简介:《C语言基础(全)》是一份面向零基础学习者与高校课程复习者的PDF讲义,围绕C语言入门所需的核心语法与程序设计思维展开,适合操作系统、嵌入式、驱动开发等方向的学习者打基础。资源包内含1个PDF文件,大小约1.57MB,篇幅集中、便于按周次检索。内容从计算机与编程语言、编译与解释执行的区别讲起,串起C语言发展脉络与K&R、ANSI C、C95、C99等标准,并覆盖变量初始化、scanf输入及返回值判断、表达式与运算符优先级、赋值与复合赋值、递增递减、关系运算和判断等知识点;同时配有最大公约数、时间差计算、英制计量单位转换等示例代码,可帮助读者理解语法落地方式与常见易错点。已有13516人学习,适合课堂配套、考前梳理和动手练习时对照查阅。
1. 一份按周推进的 C 语言基础文档:从最大公约数讲到补码,它到底适合谁
拿到一份 c语言基础 PDF资料,很多人的第一反应是直接翻到指针和数组那几章,结果前面讲变量初始化和scanf返回值的几页被一笔带过,真写起代码来输入一错就卡半小时。这份基础文档的编排有点反直觉:它不拿hello world开局,而是先用一个求最大公约数的三步循环讲算法,再讲程序到底是被解释还是被编译,最后才把 C 语言的历史和标准铺开。全书按 First Week 到 Sixth Week 分成六段,覆盖表达式、判断、循环、数据类型,每一节都配了能直接敲的示例代码,从英制身高换算、时间差计算,到猜数字、整数正逆序分解、辗转相除,一路推到补码和浮点精度。
它适合两类人:一类是零基础、需要一条按周推进、每步都有代码可敲的路线;另一类是学过一阵子,但scanf返回值、补码表示、浮点比较这几块一直是黑匣子,想找一份能随时回查的基础文档。下面按“文档讲了什么、怎么跑起来、怎么改造”的顺序,把它拆开看。
2. 文档骨架与编译链路:六周目录怎么映射成一条能跑通的练习路径
2.1 六周目录的知识点分布与产出物
这份资料的目录不是按语法条目排的,而是按“周”推进,每一周都落在一组具体的示例程序上。把目录摊开看,六周实际是三条线并行:语言基础线、算法演练线、底层表示线。
| 周次 | 主题 | 文档里的示例程序 | 能带走的产出 |
|---|---|---|---|
| First Week | 程序设计与 C 语言、编译与解释、C 标准 | 辗转相除求最大公约数 | 会写main、会用printf、知道编译和执行的区别 |
| Second Week | 变量、表达式、运算符优先级 | 英制身高换算、时间差计算 | 会做整数和浮点的混合运算 |
| Third Week | 关系运算、注释、if 与 switch-case | 分段函数、单一出口写法 | 会写分支、知道break的穿透行为 |
| Fourth Week | 循环、数位数、猜大小、求平均数 | do-while数位数、猜数字 | 会写三种循环、知道哪种更省判断 |
| Fifth Week | for 循环、goto、接力 break | 正序分解整数、枚举求 gcd | 会写循环控制、知道怎么跳多层 |
| Sixth Week | 数据类型、补码、浮点、字符 | sizeof实验、浮点相等比较 | 知道类型边界、能解释溢出 |
这张表最大的用处是给自己定位:如果你已经会写循环,可以直接从 Fifth Week 的“正序分解整数”往下读,那一节把mask的推演过程写得很细,是整份资料里少数几个把“为什么这么写”讲透了的地方。
提示:表格里每一行都对应一批可以独立编译的小程序,读完一周就单独建一个目录,别全塞进一个
main.c,否则后面会为了改一个示例把整份文件注释掉。
2.2 编译与解释:文档开篇那条主线的正确打开方式
文档开头花了不少篇幅讲“解释”和“编译”的区别:解释是借助一个程序去理解你的程序再执行,编译是先把源码翻译成机器语言再直接跑。它还特意提了一句,语言本身没有解释和编译之分,任何语言都可以解释执行或编译执行,只是执行方式不同;编译型有确定的运算性能,解释型能在运行过程中改源码。这段话看起来像科普,实际决定了后面所有示例该怎么跑。
C 是典型的编译型语言,所以文档里每一个示例都不是“复制进去就能出结果”,中间隔着预处理、编译、汇编、链接四步。很多人第一次翻车就翻在这里:代码明明是从 PDF 里原样抄的,gcc一跑报一堆错,其实是#include <stdio.h>被漏掉了,或者文件后缀被存成了.txt。我一般会先确认三件事:文件名以.c结尾、第一行有#include <stdio.h>、main的返回类型写的是int。
2.3 把第一个程序跑起来:gcc 四阶段与常用编译参数
文档里的第一个完整示例就是辗转相除,把它存成gcd.c,用下面这组命令能把四个阶段拆开看,出问题时能快速定位是哪一步挂了。
# 1. 预处理:展开头文件、替换宏、去掉注释,得到纯 C 代码 gcc -E gcd.c -o gcd.i # 2. 编译:把 C 代码翻译成汇编 gcc -S gcd.i -o gcd.s # 3. 汇编:把汇编写成目标文件(机器码,但还没链接) gcc -c gcd.s -o gcd.o # 4. 链接:把目标文件和标准库拼在一起,生成可执行文件 gcc gcd.o -o gcd # 日常一步到位,同时打开警告和调试信息,按 C99 标准编译 gcc -std=c99 -Wall -Wextra -g gcd.c -o gcd-E、-S、-c分别对应预处理、编译、汇编三步,不加参数直接gcc 源文件 -o 目标则是四步连做。-std=c99很关键,文档里好几处特性依赖它:for (int i = 1; ...)把循环变量定义在for里面、//行注释、const常量、<stdbool.h>里的bool,这些在 ANSI C 下都要么报错要么行为不同。-Wall -Wextra会把“变量未使用”“格式符和参数类型不匹配”这类问题提前打出来,-g是为了后面用调试器单步看循环变量。
编译完直接./gcd就能看到输出。如果gcc提示undefined reference to 'sqrt'之类,说明用到了数学库,命令末尾补一个-lm;如果提示printf格式串警告,多半是%d打了double,这类警告不要关掉,它救过很多次命。
3. 表达式、输入输出与分支循环:文档里那十几个示例代码的落地拆解
3.1 scanf 的三态返回值与输入校验
文档在 Second Week 里专门讲了scanf的返回值,这是整份资料里最容易被略过、又最实用的一段:返回正整数表示成功读入的变量个数,返回 0 表示输入不匹配、一个都没读进去,返回EOF表示输入流结束(stdio.h里定义的常量,通常是 -1)。把这个返回值用起来,程序就不会在错误输入上继续往下算。
#include <stdio.h> int main(void) { int a = 0, b = 0; int ret; /* scanf 返回成功赋值的变量个数,用它区分三种输入结局 */ ret = scanf("%d %d", &a, &b); if (ret == 2) { printf("a=%d, b=%d\n", a, b); } else if (ret == 0) { /* 第一个字符就不匹配,例如输入 ",3 4" */ printf("输入不匹配,一个变量都没读进去\n"); } else if (ret == EOF) { /* Windows 下 Ctrl+Z 回车,Linux 下 Ctrl+D */ printf("输入流结束\n"); } else { /* 返回 1,说明只读成功了第一个 */ printf("只读进去 %d 个变量\n", ret); } return 0; }这段代码的关键在ret的三种取值:2是正常路径,0说明用户输入的第一个字符就不是数字,EOF说明输入被主动结束。返回1这种情况文档里举的例子是输入3,4——%d读到3后卡在逗号上,第二个%d失败,于是只成功了一个。参数上要注意scanf后面必须传地址(&a),这是它和printf最大的区别;%d对应int*,%lf对应double*,%f对应float*,类型对不上不会报错,但会读到垃圾值。
3.2 运算符优先级与整数/浮点混合运算
文档给了一张优先级表,从高到低大致是:括号、单目运算符(!、正负号、++、--)、乘除取余、加减、关系运算、相等与不等、逻辑与、逻辑或、赋值。其中赋值是自右向左结合的,所以a = b = 6等价于a = (b = 6)。这张表真正发挥作用的地方是混合运算,文档里英制身高换算那个例子就是典型。
#include <stdio.h> int main(void) { int foot = 0, inch = 0; printf("输入英尺和英寸,例如 5 7:"); if (scanf("%d %d", &foot, &inch) != 2) { printf("输入格式不对\n"); return 1; } /* inch / 12.0 里有一个 double,整个除法提升为浮点除法; 如果写成 inch / 12,两个 int 相除会先截断成 0 */ double meter = (foot + inch / 12.0) * 0.3048; printf("身高是 %.3f 米\n", meter); return 0; }12.0这个小数点看着不起眼,却是这段代码唯一的难点。C 的规则是:整数和浮点一起运算时,整数先自动转成浮点;但两个整数相除,结果仍是整数并直接截断。所以5 + 7/12得到的是5,5 + 7/12.0才是5.5833...。文档在 First Week 末尾专门写了“整数在计算机里运算比浮点数快”,这句话的潜台词是:别为了图省事把所有变量都定义成double,该用整数的地方用整数,只在需要小数的位置显式写一个.0。
时间差那个例子也有个容易被忽略的细节:
int t1 = hour1 * 60 + minute1; int t2 = hour2 * 60 + minute2; int t = t2 - t1; /* C 的整数除法向零取整,负数时结果和直觉不同 */ printf("时间差是 %d 小时 %d 分\n", t / 60, t % 60);当t是负数(后一个时间早于前一个),-30 / 60得0,-30 % 60得-30,输出就是“0 小时 -30 分”。文档里的版本没处理这种情况,自己改的时候要么先判大小再交换,要么统一转成分钟数输出。
3.3 交换变量、复合赋值与自增自减
文档在 Second Week 给了交换两个变量的小套路,用第三个临时变量中转,这是最稳的写法:
int a = 5, b = 6, t; t = a; /* 先把 a 存起来,否则下一步 a 被覆盖就找不回来了 */ a = b; b = t;不用临时变量的异或交换在文档里没提,实际也不建议在初学阶段用:可读性差,两个数是同一个变量时会出错。复合赋值+=、-=、*=、/=、%=文档特意提醒两个运算符中间不要加空格,total /= 12 + 6等价于total = total / (12 + 6),而不是total = total / 12 + 6——右边整体先算完再除。
自增自减那段讲得比较细:count++的值是加之前的值,++count的值是加之后的值。文档还顺带提了历史来源,说当年 PDP-11 上有 INC 和 DEC 两条指令,编译器能把++、--直接映到这两条指令上,如今编译器即便你写a = a + 1也会生成同样的指令,所以这个运算符现在的意义主要是习惯和方便。
int i = 5; int a = i++; /* a = 5, i = 6:先取值,后自增 */ int b = ++i; /* i = 7, b = 7:先自增,后取值 */实际写代码时我一般把自增单独写成一行,不塞进表达式里,a[i++] = b[j++]这种写法在边界上翻车过一次之后就不太敢用了。
3.4 if 单一出口与 switch-case 的取舍
文档在 Third Week 明确表态“更支持单一出口方式”,并给了对照代码:非单一出口版本在三个分支里各自printf,单一出口版本先把结果算进f,最后统一输出一次。
int f; if (x < 0) { f = -1; } else if (x == 0) { f = 0; } else { f = 2 * x; } printf("%d\n", f); /* 只有一个出口,打印逻辑不重复 */单一出口的好处是输出格式只写一遍,后面要改成写文件或者加前缀,只动一行。代价是多一个变量、多一次赋值,性能上可以忽略。
switch-case那段文档重点讲了穿透:case只是跳转标号,执行完最后一条语句后如果没有break,会继续往下执行到下一个case。
switch (type) { case 1: case 2: printf("你好\n"); break; case 3: printf("晚上好\n"); /* 这里故意漏掉 break,会继续执行 case 4 的语句 */ case 4: printf("再见\n"); break; default: printf("啊,什么啊?\n"); break; }case 1和case 2共用一个分支,靠的是上面那个case没有语句、直接落到下一个标号,这是合法且常用的写法。而case 3漏break则会“掉”进case 4,如果这是无意的,就是典型 bug。文档还提了两条限制:控制表达式只能是整数型结果,case后面必须是常量(C99 起可以是常量表达式);另外明确指出分段函数虽然能用switch实现,但这种情况尽量用if,因为switch的可读性在这里反而更差。
3.5 三种循环的选型:for、while、do-while
文档给了一句很实用的选型口诀:有固定次数用for,必须执行一次用do-while,其他情况用while。数位数用do-while是因为 0 也是一位数,循环体至少要进一次:
int x = 0, n = 0; do { x /= 10; /* 去掉最低位 */ n++; /* 位数加一 */ } while (x > 0); /* x = 0 时输出 1,符合“0 是一位数”的直觉 */求平均数则相反,文档里明确说while比do-while好,因为少一次if判断:
int number = 0, sum = 0, count = 0; /* scanf 返回 1 表示成功读入一个整数,-1 是约定的结束标记 */ while (scanf("%d", &number) == 1 && number != -1) { sum += number; count++; } if (count > 0) { printf("%f\n", 1.0 * sum / count); /* 乘 1.0 强制浮点除法 */ }do-while版本必须在循环体里先读一次、再判断是不是-1,多一层嵌套;while版本把读入和判断合到条件里,逻辑更短。for那段文档讲了两点:循环变量可以定义在for里面(仅 C99 支持),以及for的三个表达式都可以省略,for (; 条件;)完全等价于while (条件)。
3.6 整数逆序与正序分解的 mask 法
逆序分解文档给的是“%10取个位、/10去掉个位”的经典套路:
int x = 700, digit = 0, ret = 0; while (x > 0) { digit = x % 10; /* 取最低位 */ ret = ret * 10 + digit; /* 已有结果整体左移一位,加上新位 */ x /= 10; /* 去掉最低位 */ } /* ret = 7,尾部的 0 在逆序后自然消失 */正序分解要难一档,因为得先知道最高位是多少。文档的解法是先算出一个mask:
int x = 12345; int mask = 1; int t = x; /* 把 mask 推到和 x 同量级,例如 12345 对应 10000 */ while (t > 9) { t /= 10; mask *= 10; } do { printf("%d", x / mask); /* 取当前最高位 */ if (mask > 9) printf(" "); /* 最后一位后面不打空格 */ x %= mask; /* 去掉最高位 */ mask /= 10; } while (mask > 0);mask的作用是“位权”。第一段循环把mask从 1 推到 10000,第二段每次用x / mask拿到当前最高位,用x %= mask把最高位剥掉,mask /= 10降一档。文档把每一步的中间值都打印出来了(x=700,digit=0,ret=0那几行),照着跑一遍比看十遍解释都管用。
4. 数据类型底层:补码、整数范围、浮点精度与字符编码的文档写法
4.1 整数类型、sizeof 与补码
Sixth Week 是整份资料里密度最高的一段。文档先把 C 的类型列了一遍:整数有char、short、int、long、long long,浮点有float、double、long double,C99 还加了_Bool。然后强调“类型决定四件事”:格式化符、数的范围、占内存大小、内存里的表达形式。sizeof是个运算符不是函数,用来量字节数:
#include <stdio.h> int main(void) { /* %zu 是 size_t 的格式化符,sizeof 的结果类型就是 size_t */ printf("char = %zu\n", sizeof(char)); printf("short = %zu\n", sizeof(short)); printf("int = %zu\n", sizeof(int)); printf("long = %zu\n", sizeof(long)); printf("long long = %zu\n", sizeof(long long)); printf("float = %zu\n", sizeof(float)); printf("double = %zu\n", sizeof(double)); return 0; }在常见的 64 位平台上,输出大致是 1、2、4、8、8、4、8,但long在不同平台上可能是 4 也可能是 8,文档特意写了“取决于编译器(CPU)”,所以不要把它当成固定值。补码那段文档讲得很巧:它从“0 减 1 应该得到 -1”推起,8 位下00000000 - 00000001借位后得到11111111,这个数当纯二进制看是 255,当补码看就是 -1。于是负数不用单独存符号,直接参与加法就能得到正确结果,这也是文档说的“能把负数当正常的数运算”。
4.2 有符号与无符号、整数溢出
unsigned的初衷文档写得很清楚:不是为了扩大表示范围,而是为了做纯二进制运算,主要是为了移位。字面量后面加u或U表示无符号,加l或L表示long。整数范围那张表要记住几个关键值:char是 -128 到 127,short是 -32768 到 32767,int取决于平台但通常是 32 位。
unsigned char c = 255; c = c + 1; /* 回绕成 0,无符号溢出是定义好的行为 */ int a = 2147483647; a = a + 1; /* 有符号溢出是未定义行为,不要依赖结果 */这两行的区别值得记一辈子:无符号溢出按模回绕,结果是确定的;有符号溢出在标准里是未定义行为,编译器有权做任何优化,实际表现可能是变成负数,也可能被优化掉。要处理大数就换long long,或者在加之前先判断a > INT_MAX - 1。
4.3 浮点精度与相等判断
浮点那节文档给了两个特殊值:INF是超出表示范围,NaN是无效浮点数。最实用的还是精度那段——带小数点的字面量默认是double,float要加f或F后缀;两个float用==比较很可能失败,正确做法是用绝对值差和一个小量比。
#include <stdio.h> #include <math.h> int main(void) { float a = 1.345f; /* f 后缀表明这是 float,不是 double */ float b = 1.123f; float c = a + b; if (c == 2.468) { /* 大概率不成立,浮点累加有误差 */ printf("相等\n"); } else { printf("不相等,c=%.10f\n", c); } /* 正确姿势:差值绝对值小于容差就算相等,1e-6 是科学计数法 */ if (fabs(c - 2.468f) < 1e-6) { printf("在误差范围内相等\n"); } return 0; }编译这段要加-lm链接数学库。容差取多大取决于业务:一般比较用1e-6,金额计算文档直接建议“用整数,整数是精确的”,把元换成分来存。文档还有一句很关键的提醒:现代 CPU 对double的硬件运算性能和float差不多,64 位机器上存储速度也不慢,所以没有特殊需要就只用double,别为了省 4 个字节给自己找精度麻烦。
4.4 字符类型、ASCII 与大小写转换
char既是整数也是字符,'a'是字符字面量,'1'也是字符(ASCII 49),和整数 1 完全不是一回事。文档那句“一个 49 的各自表述”说的就是这个:char c = 49和char c = '1'在内存里是同一个字节,只是打印时%d出 49、%c出1。
#include <stdio.h> int main(void) { char c = 'A'; /* ASCII 里大写和小写是两段连续区间,差值是固定的 */ char lower = c + 'a' - 'A'; printf("%c -> %c, ASCII: %d -> %d\n", c, lower, c, lower); return 0; }'a' - 'A'得到的是两段之间的距离,加到任意大写字母上就变成对应的小写,反向减就变大写。这个技巧只对英文字母成立,遇到非字母字符要先判断范围。逃逸字符那张表也值得抄下来:\n换行、\t到下一个制表位、\\反斜杠本身、\"双引号、\'单引号、\r回车、\b回退一格。\r和\b在终端里能做出“原地刷新进度”的效果,写命令行小工具时很有用。
4.5 强制类型转换与 C99 逻辑类型
强制类型转换文档强调了两点:它只是“算出一个新值”,不改变原变量的类型和值;转换运算符的优先级比四则运算高。
int a = 5, b = 6; double d = (double)a / b; /* 0.8333...,先转 a 再做浮点除法 */ double wrong = (double)(a / b); /* 0.0,括号里先做了整数除法 */这两行的差别就是括号位置,结果差了整整一个数量级。文档还提醒(short)32768这类向下转换不安全,小的类型不总能装下大的量。C99 的逻辑类型要包含<stdbool.h>才能用bool、true、false,底层仍然是整数,sizeof(bool)通常是 1。逻辑运算有短路特性:&&左边为假就不算右边,||左边为真就不算右边。文档专门提醒“不要把赋值组合进表达式”,因为短路会让赋值时有时无,这类 bug 极难查。
5. 避坑与排查:照 PDF 敲代码时最容易翻车的五个点
5.1 输入输出与格式化相关的坑
现象:scanf("%d", &n)之后紧跟scanf("%c", &c),程序不等输入就直接跳过,c拿到一个空行或空格。原因:%d读完数字后,输入缓冲区里还留着回车符,%c不做空白跳过,直接把回车读走了。解决:在%c前面加一个空格写成scanf(" %c", &c),空格表示“跳过所有前导空白”;或者在两次读之间手动getchar()吃掉一个字符。这个问题在混合读数字和字符时必现,跟编译器无关。
5.2 表达式与分支相关的坑
现象:if (a = b)这个条件永远成立,不管a和b原来是多少。原因:=是赋值不是比较,整个表达式的值就是被赋的值,只要b非零条件就为真。文档在 Third Week 专门用一段代码标了“错误使用==和=”。解决:写==;把常量放左边写成if (6 == a),漏写一个等号会直接编译报错;编译时开-Wall,多数编译器会对“条件里出现赋值”给出警告。
现象:1/2算出来是 0,(double)(a/b)也是 0。原因:两个整数相除结果仍是整数,直接截断;强制转换加在括号外面,除法已经先做完了。解决:至少让一个操作数是浮点,写1.0/2或(double)a / b,转换加在分子上而不是整个表达式外面。
5.3 类型与精度相关的坑
现象:if (c == 2.468)明明打印出来c就是2.468000,条件却不成立。原因:float只有大约 6 到 7 位有效数字,1.345f + 1.123f的实际结果可能是2.4679999,二进制浮点无法精确表示大多数十进制小数。解决:用fabs(c - 2.468) < 1e-6做容差比较;涉及金额的场景改用整数以分为单位;无特殊需求统一用double。
现象:printf("%d", d)打印出一个莫名其妙的负数,其中d是double。原因:%d期待int,double按整数格式读会读到完全不相干的内存位,标准里是未定义行为。解决:double用%f或%lf输出,float用%f,long long用%lld。把-Wformat打开(-Wall已包含),这类错误会直接变成编译警告。
5.4 编译标准与语法相关的坑
现象:for (int i = 1; i <= n; i++)在某个环境里报'for' loop initial declarations are only allowed in C99 mode。原因:默认按 ANSI C(C89)编译,变量必须在代码块开头定义,for里面不允许声明。解决:编译加-std=c99或-std=c11;或者把int i;提到循环外面。文档里那句“ANSI C 标准只能在代码开头的地方定义变量,C99 则是只要在使用前定义就可以”说的就是这件事。
6. 二次开发:把这份基础文档改造成能自测的练习题库
照着 PDF 敲一遍只能证明“能跑”,证明不了“写对了”。我习惯把每个示例从main里抽成纯函数,再配一组assert,这样改代码时能立刻知道有没有破坏原有行为。以辗转相除和整数逆序为例:
#include <assert.h> #include <stdio.h> /* 把 main 里的循环逻辑抽成函数,去掉输入输出,便于断言 */ int gcd(int a, int b) { while (b != 0) { int t = a % b; a = b; b = t; } return a; } int reverse_int(int x) { int ret = 0; while (x > 0) { ret = ret * 10 + x % 10; x /= 10; } return ret; } int main(void) { /* 文档原例 + 手工补的边界用例 */ assert(gcd(32, 26) == 2); assert(gcd(12, 18) == 6); assert(gcd(7, 0) == 7); /* b 为 0 时应直接返回 a */ assert(reverse_int(700) == 7); /* 尾部 0 逆序后消失 */ assert(reverse_int(0) == 0); /* 循环一次都不进 */ assert(reverse_int(12345) == 54321); printf("all pass\n"); return 0; }抽函数的原则是:不碰标准输入输出,参数和返回值都是基本类型,这样assert才能直接比较。gcd(7, 0)和reverse_int(0)这两个用例是文档里没有的,属于“边界补全”,正是这类用例能暴露实现里的想当然。断言失败时程序会打印文件名和行号,比printf一行行对快得多,缺点是发布版本要定义NDEBUG关掉它。
工程化一步是用 Makefile 把编译参数固定下来,避免换台机器就忘了加-std=c99:
CC = gcc CFLAGS = -std=c99 -Wall -Wextra -Werror -g TARGET = practice SRCS = main.c gcd.c reverse.c $(TARGET): $(SRCS) $(CC) $(CFLAGS) -o $@ $^ test: $(TARGET) ./$(TARGET) clean: rm -f $(TARGET)$@是目标名,$^是所有依赖,命令行前面必须是 Tab 不是空格。-Werror把警告当错误,配合-Wall -Wextra,格式符不匹配、变量未使用、隐式函数声明这类问题会在编译期被拦住。
最后列一张边界用例表,每读完一章就往下补几行,这份表比任何笔记都耐用:
| 函数 | 输入 | 期望输出 | 覆盖的知识点 |
|---|---|---|---|
gcd | 32, 26 | 2 | 文档首个示例 |
gcd | 7, 0 | 7 | b为 0 的终止条件 |
reverse_int | 700 | 7 | 尾部 0 被丢弃 |
reverse_int | 0 | 0 | 循环不进入 |
digit_count | 0 | 1 | do-while至少执行一次 |
time_diff | 0:30 到 1:00 | 0 小时 30 分 | 分钟换算与取模 |
刚开始拆分的时候,我把gcd的while条件写成b > 0,结果gcd(7, 0)直接返回 7 看起来没问题,但gcd(0, 7)返回了 0,测了十分钟才发现终止条件写偏了。从那以后我每拆一份基础文档,都会先把里面的main抽成函数、补两条边界断言再跑一遍,确认示例真的能过再往下读。希望帮到你。
本文还有配套的精品资源,点击获取