简介:这套《chap循环结构实用PPT学习教案》是一份面向C语言初学者的循环结构教学课件,围绕while、do-while、for三种循环语句及break、continue流程控制展开,适合课堂教学、自学复习或备课参考。资源主体为1个PPTX文件,压缩包共1个文件、大小仅148KB,内容精炼,便于直接打开演示或打印学习。课件以例题驱动方式讲清循环的语法格式、执行流程与典型应用,如用while和do-while求1到100的和、用for计算序列和、break跳出循环、continue跳过本次迭代,并演示辗转相除法求最大公约数、求100~200间素数、1000以内完数等综合编程场景,既有基础概念,也有循环嵌套与算法实例。预览显示全篇共15页,重点突出、步骤清晰,能帮助读者快速理清循环结构的使用区别与易错点。目前已有71人学习浏览,适合正在学习C语言分支与循环章节的学生、备考二级的考生以及需要制作相关教案的教师参考。
1. 别把循环只当“重复执行”来学
循环结构是C语言里最先学、也是最容易写出隐患的控制流。很多人以为while、do-while、for只是语法不同,实际上它们的表达式求值时机、循环体执行次数、以及break/continue的作用范围完全不同。比如do-while至少执行一次,而while可能一次都不执行;for的三个表达式可以任意省略,省略不当就变成死循环。这份循环结构教学资源用典型的累加求和、素数、完数、最大公约数四个例子,把三种循环和两种跳转语句串在一起,正好覆盖了初学者最容易踩的坑。适合正在学C语言的学生,也适合需要给新人讲清楚循环边界的工程师。下面按“语义差异 → 表达式机制 → 跳转与嵌套 → 调试技巧”的顺序拆开讲。
2. while与do-while:先判断还是先执行的本质差异
2.1 while的“当型”语义与经典累加
while语句在C语言里称为“当型”循环,语义是“当条件成立时,反复执行循环体”。标准形式:
while (表达式) 语句;如果循环体是多条语句,需要用花括号括起来。教材里的例5.2用while求1到100的和,改写得更规范一点:
#include <stdio.h> int main(void) { int i = 1; int sum = 0; while (i <= 100) { sum = sum + i; i++; } printf("%d\n", sum); return 0; }执行逻辑是:先计算表达式i <= 100,如果值为真(非0)就进入循环体,执行完sum + i和i++后再次回到while处判断;一旦i变成101,表达式为假(0),循环结束。这里要特别注意i++的位置——如果忘了写,i永远等于1,条件永远为真,程序会陷入死循环。
参数说明:i是循环计数器,初值1;sum是累加器,初值0。累加器必须在循环前清零,否则结果会带上内存里的随机值。i++也可以写成i = i + 1或i += 1,效果相同。实际项目中,i++作为独立语句更常见,因为它的副左右明确,不参与表达式运算时也不会引入未定义行为。
2.2 do-while的“直到型”语义与至少执行一次
do-while被称为“直到型”循环,标准形式:
do { 语句; } while (表达式);注意分号在while后面,不能省略。同样的累加例子用do-while实现:
#include <stdio.h> int main(void) { int i = 1; int sum = 0; do { sum = sum + i; i++; } while (i <= 100); printf("%d\n", sum); return 0; }执行过程是先进循环体,再判断i <= 100。因此即使条件一开始就为假,循环体也会执行一次。例如把i初值改成101,while版本输出0,do-while版本会先执行sum = 0 + 101、i++,再判断失败,最后输出101。这个差异在菜单输入、用户交互等“至少响应一次”的场景里非常有价值。比如你写一个确认退出程序,总得先弹出一次提示让用户选择,用do-while就比在while前额外贴一遍提示代码干净得多。
2.3 从入口条件选择循环类型
判断用哪种循环,核心看业务逻辑是否允许“零次执行”。如果数据源可能为空、条件一开始就不成立,用while;如果流程要求先做一次再看是否继续,用do-while。两者的对比如下:
| 对比项 | while | do-while |
|---|---|---|
| 判断时机 | 先判断后执行 | 先执行后判断 |
| 最小执行次数 | 0次 | 1次 |
| 适用场景 | 读取文件直到EOF、遍历未知长度数据 | 菜单循环、输入校验、协议解析首帧 |
| 常见错误 | 忘记更新循环变量导致死循环 | while后漏写分号,或误加分号导致循环体为空 |
在实际教学里有一种典型误用:用do-while保证“至少执行一次”,但业务上明明允许零次输入。反过来,有人用while做用户输入校验,第一次输入前要先弹提示,不得不在循环前复制一遍提示代码,改用do-while更简洁。
两种循环可以互相转换。假设while循环的条件是C,循环体是B,那么do-while版本需要先把C的判断结果存下来,或者用if包一层:
if (C) { do { B; } while (C); }这样改虽然保持了“可能零次”的语义,但条件C会被计算两次,如果C里有函数调用,会产生额外副作用。所以工程上不提倡硬转换,而是根据入口条件是否天然存在来选择。这个细节在面试中经常被问到,也是区分“背过语法”和“真正理解控制流”的一条分界线。
3. for循环的表达式机制与死循环陷阱
3.1 三个表达式的执行时机
for循环是C语言中最灵活、也最容易写错的循环。一般形式:
for (表达式1; 表达式2; 表达式3) 语句;表达式1只在循环开始前执行一次,通常用来初始化循环变量;表达式2在每次迭代前判断,为真才进入循环体;表达式3在每次循环体执行完后执行,通常用来更新循环变量。教材例5.3的累加用for写:
#include <stdio.h> int main(void) { int i; int sum = 0; for (i = 1; i <= 100; i++) sum = sum + i; printf("%d\n", sum); return 0; }执行顺序是:i = 1→ 判断i <= 100→ 执行sum = sum + i→ 执行i++→ 再判断。注意表达式3是在循环体结束后执行,而不是在判断前,因此即使循环体内有continue,表达式3仍然会执行,这一点和下面要说的while不同。很多初学者误以为for的求值顺序是“初始化→判断→更新→循环体”,实际却是“初始化→判断→循环体→更新”,顺序错了,边界条件就会跟着错。
3.2 省略表达式与死循环的边界
for的三个表达式都可以省略,但两个分号必须保留。省略时对行为的影响如下:
| 省略位置 | 写法示例 | 后果与注意事项 |
|---|---|---|
| 表达式1 | for (; i <= 100; i++) | 需要自行初始化i,分号不能丢 |
| 表达式2 | for (i = 1; ; i++) | 条件恒为真,死循环,必须配合break |
| 表达式3 | for (; i <= 100; ) | 需要在循环体内更新i,否则死循环 |
| 三个全省 | for (;;) | 等价于while(1),通常用于事件循环 |
省略表达式2导致死循环的典型场景是服务器守护进程,但如果是业务代码里无意省略,会非常难排查。演示一个用break退出的省略写法:
#include <stdio.h> int main(void) { int i; for (i = 1; ; i++) { if (i > 100) break; printf("%d\n", i); } return 0; }这里如果删掉if和break,程序会一直打印到int溢出并继续循环,直到手动终止。参数说明:i是int类型,有符号整型的最大值约21亿,但循环不会因为溢出自动退出,因为整数溢出在有符号类型下是未定义行为,不能依赖它来自动终止。所以for (;;)里的退出条件必须显式写在循环体中,并且要保证在极端输入下仍然可达。
3.3 逗号表达式与循环变量同步更新
for的表达式1和表达式3可以是逗号表达式,用来同时处理多个变量。例如:
for (i = 0, j = 100; i <= j; i++, j--) sum += i + j;这段代码每轮从两端向中间逼近,i从0开始、j从100开始,当i超过j时停止。逗号表达式的求值顺序从左到右,整体值是最后一个表达式的值,但在这个场景里我们只关心副作用。需要特别注意,表达式2不能使用逗号表达式表示“同时满足”,它只能写一个条件,多个条件要用&&或||连接。
这种写法适合处理数组首尾交换、双指针问题。比如字符串反转:
char s[] = "hello"; int left = 0, right = 4; for (; left < right; left++, right--) { char tmp = s[left]; s[left] = s[right]; s[right] = tmp; }这里表达式1省略是因为两个变量的初值在循环前已经设置好。left < right是条件,left++, right--同步更新两端索引。注意字符数组要保证右边界索引不越界,如果数组长度是偶数,left < right在指针交错时正好停止,不会重复交换。
对于循环次数已知、需要多个变量协同更新的场景,for的逗号表达式比在循环体里手写更新语句更紧凑。但代价是“更新逻辑”从循环体里被挪到了for的头部,代码阅读者必须熟悉这个语法才不会误判。在团队编码规范里,如果约定不允许在for表达式中写复杂表达式,那就拆成while加显式更新,可读性优先。
4. break与continue、嵌套循环的实战控制
4.1 break跳出一层循环
break用在循环体内,作用是立刻终止当前这一层循环,跳到循环之后的语句。注意它只能跳出一层,不是跳出所有外层循环。教材里的例子:
for (i = 1; i <= 10; i++) { s = i * i; if (s > 30) break; printf("%f\n", s); }当s > 30时break触发,整个for循环结束。这里的printf实际输出1、4、9、16、25,当i=6时s=36,break后不再执行printf。要注意,break在嵌套循环中只作用于最内层包含它的循环,如果需要跳出多层,要么用返回值、要么用状态标志位,不能依赖break穿透多层。
4.2 continue跳过本次迭代的剩余部分
continue不结束循环,而是跳过本次迭代中continue后面的语句,进入下一次迭代。例5.4输出100到200之间不能被3整除的数:
#include <stdio.h> int main(void) { int n; for (n = 100; n <= 200; n++) { if (n % 3 == 0) continue; printf("%d ", n); } printf("\n"); return 0; }这里n % 3 == 0时continue,跳过打印语句,直接执行n++。注意for循环中的表达式3不受continue影响,但while和do-while不同——如果continue用在while里,跳过的语句中恰好包含循环变量更新,就会造成死循环。这是很多人踩过的坑。
比如:
i = 1; while (i <= 10) { if (i == 5) continue; printf("%d\n", i); i++; }当i等于5时,continue把i++跳过了,i永远停在5,造成死循环。解决办法是把i++移到continue之前,或者改用for循环。在实际项目中,while循环里出现continue时,一定要检查循环变量更新是否被跳过,这是代码审查时的一个高频检查点。
break和continue对循环流程的影响差异如下:
| 控制语句 | 对当前循环的影响 | 对for表达式3的影响 |
|---|---|---|
| break | 立即终止整个循环,跳到循环后第一条语句 | 不执行表达式3 |
| continue | 跳过本次迭代剩余语句,进入下一次判断 | 仍然执行表达式3 |
4.3 辗转相除法:循环控制的完整应用
最大公约数的辗转相除法是理解while循环退出的好例子:
int gcd(int m, int n) { int r; if (m < n) { r = m; m = n; n = r; } r = m % n; while (r != 0) { m = n; n = r; r = m % n; } return n; }流程是:先保证m不小于n,然后反复用高位数模低位数,把余数赋值给低位数,直到余数为0。最后一次被整除的除数n就是最大公约数。注意循环条件r != 0,进入循环前先算一次余数,否则循环体里的m = n会覆盖掉原始数据。这个实现里没有使用break,循环的退出完全依赖余数收敛到0,属于最典型的while用法。
4.4 素数检测与完数:嵌套循环的break优化
例5.6检测100到200之间的素数,使用双重循环:
#include <stdio.h> #include <math.h> int main(void) { int m, i, k, f; for (m = 101; m <= 200; m = m + 2) { k = (int)sqrt(m); f = 1; for (i = 2; i <= k; i++) { if (m % i == 0) { f = 0; break; } } if (f == 1) printf("%d ", m); } printf("\n"); return 0; }这里m = m + 2跳过偶数,因为大于2的偶数不可能是素数。内层循环只需要从2遍历到sqrt(m),复杂度从O(n)降到O(sqrt(n))。f是标志位,一旦发现因子就置0,再用break跳出内层循环——注意break只结束内层循环,外层循环继续判断下一个m。k保存的是m的平方根取整,用于限定i的上界,减少不必要的取模运算。
完数检测类似,但内层循环不能提前break,因为要统计所有因子之和:
for (m = 2; m < 1000; m++) { sum = 1; for (n = 2; n < m; n++) { if (m % n == 0) sum += n; } if (m == sum) { printf("%d=1", m); for (n = 2; n < m; n++) if (m % n == 0) printf("+%d", n); printf("\n"); } }说明:sum初值为1是因为1是所有大于1整数的因子,内层从2开始到m-1,找到因子就累加,最后判断因子和是否等于m本身。这个算法可以进一步优化成n <= m / 2,因为一个数的真因子不可能超过它的一半,但在这份教学资源里保留的是便于理解的原始版本。注意这段代码用两层循环遍历m和n,第三次循环只用于输出因子,不是嵌套三层,逻辑上要分清“计算”和“展示”两个阶段。
5. 循环调试与等价转换:验证循环边界的一个通用技巧
5.1 在循环入口打印关键变量
当循环行为不符合预期时,不要只盯着代码读,在入口处加一行printf:
for (i = 1; i <= 100; i++) { printf("i=%d, sum=%d\n", i, sum); sum = sum + i; }这能看到每次迭代前i和sum的真实值,观察最后一次迭代是不是i=100、sum=5050。调试完记得删掉,或者用条件编译包起来。如果循环次数很大,可以只打印前几次和最后几次,避免刷屏。更专业的做法是用宏控制调试输出:
#ifdef DEBUG_LOOP #define LOOP_DEBUG(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define LOOP_DEBUG(fmt, ...) #endif这样在编译时用-DDEBUG_LOOP开启,正式构建里自动消失。
5.2 三种循环的等价改写模板
while、do-while、for三者可以互换。给出最基本的for转while模板:
表达式1; while (表达式2) { 语句; 表达式3; }注意continue在这种改写中的语义会变:for循环的表达式3在continue后仍会执行,但转成while后,表达式3位于循环体末尾,遇到continue会被跳过,需要改成在continue前执行更新语句。do-while转while时,要先用if判断条件,满足才进入do-while,否则跳过;但条件表达式有副作用时可能被求值两次,需要警惕。
验证边界时,用这几个模板改写后对比输出,能快速定位是判断条件写错还是更新时机错了。也可以设计一组边界用例:空循环(条件初始为假)、单次循环、达到上限值、超过上限值。比如验证累加和函数时,分别传入n=0、n=1、n=2、n=100,对照公式n(n+1)/2手工计算结果。把这些边界用例写成断言,每次改动循环逻辑后跑一遍,比肉眼检查可靠——这个方法在排查环形缓冲区读写索引时同样有效。
本文还有配套的精品资源,点击获取