内容提要
- 循环结构
- 循环定义
- 当型循环的实现
- 直到型循环的实现
- 循环的嵌套
- 循环的应用场景
- 基础算法模型
- break与continue
循环结构
什么是循环结构
代码在满足某种条件的前提下,重复执行,就叫做循环结构
循环的分类
- **无限循环:**其实就是死循环,程序设计中要谨慎使用。
- **有限循环:**循环限定循环次数(for)或者终止循环条件(while,do…while)。
循环的构成
- **循环条件:**如循环次数,或者终止循环的出口
- **循环体:**需要重复执行的代码。
当型循环的实现
特点:先判断,后执行,如果条件不满足,一次都不执行
**代表:**while、for
while
语法:
while (循环条件)
{
循环语句;
}
//如果循环语句是单语句,可以省略花括号{}
while (循环条件) 循环单语句;
while (循环条件)
循环单语句;
//注意:有些特殊场景下,循环体语句糅合到了循环条件中,所以省略掉了循环体
while (循环条件);
说明:
- 循环条件的返回值必须是逻辑值(使用
非0
表示真,使用0
表示假,C语言底层使用1表示真)C99版本引入了一个stdbool.h
,可以使用true或者false来表示真假,这里的true和false实际上就是定义的两个符号常量,true的值是1,flase的值0。 {}
包起来的内容整体称之为循环体- 我们要在循环体中控制循环条件的变化,也可以在循环条件中控制循环条件的变化,否则产生死循环。
执行过程:
**特点:**先判断,后执行,循环体语句有可能一次都不执行。
案例
-
需求:求1~100的累加和
-
分析:
- 创建一个变量sum = 0,用来接收累加和
- 创建一个变量i,用来表示计算数,我们会给i一个初始值i = 1,每次循环的时候i++
- 在循环中,使用sum += i,完成累加和运算
- 同时我们要限定循环的条件,也就是i的范围:i <= 100
- 第1次:sum += i = 0 + 1 = 1
- 第2次:sum += i = 1 + 2 = 3
- 第3次:sum += i = 3 + 3= 6
- …
-
代码:
#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个变量sum,用来接收累加和 int sum = 0; // 这里一定要初始化, 因为sum要参与计算 // 创建一个循环变量i,i循环初始值,因为i要参与计算 int i = 1; // 循环变量 // 循环求累加和 while (i <= 100)// 循环条件 { // 实现加法运算 sum += i; // 等价于 sum = sum + i; // 改变循环条件,让其逼近循环的出口 i++; } // 输出sum printf("1~100的累加和是%d\n", sum); return 0; }
-
运行结果
案例:
-
需求:求1~100以内的偶数和
-
分析:
- 创建两个变量,sum = 0用来存储累加的偶数和, i = 2用做循环变量,存储自然数
- 创建一个循环,设置循环条件i <= 100
- 在循环体内,需要一个if来校验i是否是偶数(if(i % 2 == 0)),若果满足,就在if中求偶数和:sum += i
- 在循环体的最后一行,需要改变循环变量i的值,用来逼近循环出口
- 循环结束后,才能输出sum
-
代码
#include <stdio.h> int main(int argc,char *argv[]) { // 创建两个变量,sum存储累加和,i存储自然数(i是循环条件) int sum = 0, i = 2; // 创建一个循环,实现偶数和运算 while (i <= 100) { // 计算偶数和 if (i % 2 == 0) sum += i; i++; // 注意:此时i++是在if语句外部 } // 输出偶数和 printf("1~100以内的偶数和是:%d\n", sum); return 0; }
-
运行结果:
while的特殊写法:
while(1) // 死循环 while(i=5)
for
原则上:for循环能实现的,while循环也能实现,for循环可以看做是while循环的一种特殊写法,for循环胜在结构清晰。
语法:
for(① 表达式1; ② 表达式2; ③表示式3)
{
④ 循环体语句;
}
//如果循环体语句是单语句,也是可以沈略{}的
for(① 表达式1; ② 表达式2; ③表示式3) ④ 循环体语句;
for(① 表达式1; ② 表达式2; ③表示式3)
④ 循环体语句;
说明:
①()
中可以仅保留两个;;
,举例:for(;;)
,此时就是for循环中的一种死循环的体现。
②表达式1
是循环变量
,我们需要赋初值,循环变量也可以单个,也可以是列表,多个循环变量之间使用逗号分隔:
//循环变量是单个
for (int i = 0;...;...){..}
//循环变量是列表
for (int i = 0, j = len -1; ... ; ...){...}
③表达式2
是循环条件
,用来限制循环的次数,循环条件支持关系表达式,逻辑表达式等,举例:
for (...; i < 10 && j >= 0;...){...}
④表达式3
是修改循环变量
,支持列表,列表使用逗号分隔,这里可以使用赋值表达式,举例:
for (...;...;i++,j--){...}
⑤执行顺序:①②④③ → ②④③ → ②④③ → … → ②
执行过程:
特点:先判断,后执行,循环体语句有可能一次都不执行。
案例
-
需求:计算1~100以内偶数和
-
代码:
#include <stdio.h> /** * 使用while实现 */ int while_test() { // 创建两个变量,sum存储累加和,i存储自然数(i是循环条件) int sum = 0, i = 2; // 创建一个循环,实现偶数和运算 while (i <= 100) { // 计算偶数和 if (i % 2 == 0) sum += i; i++; // 注意:此时i++是在if语句外部 } // 输出偶数和 printf("1~100以内的偶数和是:%d\n", sum); return 0; } /** * 使用for实现 */ int for_test1() { // 创建两个变量,sum存储累加和,i存储自然数(i是循环条件) int sum = 0, i = 2; // 创建一个循环,实现偶数和运算 for (;i <= 100;) { // 计算偶数和 if (i % 2 == 0) sum += i; i++; // 注意:此时i++是在if语句外部 } // 输出偶数和 printf("1~100以内的偶数和是:%d\n", sum); return 0; } /** * 使用for实现 */ int for_test2() { // 创建两个变量,sum存储累加和,i存储自然数(i是循环条件) int sum = 0, i; // 创建一个循环,实现偶数和运算 for (i = 2; i <= 100; i++) { // 计算偶数和 if (i % 2 == 0) sum += i; } // 输出偶数和 printf("1~100以内的偶数和是:%d\n", sum); return 0; } int main(int argc,char *argv[]) { while_test(); for_test1(); for_test2(); return 0; }
案例:
-
需求:用户通过键盘输入一个整数n,求n的阶乘。例如: n = 4,n的阶乘1 * 2 * 3 * 4
-
代码:
#include <stdio.h> #include <math.h> int main(int argc,char *argv[]) { // 创建一个变量,用来存储计算数 unsigned long n; // 创建一个变量,用来存储乘积 unsigned long r = 1; // 通过控制台,给n赋值 printf("请输入一个整数:\n") scanf("%lu", &n); // 通过for循环计算 for (int i = 1; i <= fabs(n); i++) r *= i; printf("1~%lu之间阶乘的结果是%lu\n", (unsigned long)fabs(n), r); return 0; }
-
注意:阶乘计算,如果乘积超过变量存储的范围,会溢出,结果计算可能不准确。
for实现死循环:
for(表达式1;;表达式3) {..}
for(表达式1;;) {..}
for(;;表达式3) {..}
for(;;) {}
for(表达式1;表达式2;) {}
...
循环实现的三要素
- 循环变量的初始化,举例:int i = 2;
- 循环条件,举例:i < 100
- 循环变量的更新,举例:i++
举例:
-
需求:求斐波拉切数列前20个
-
分析:
-
斐波拉切数列指的是符合一定规则的数列,举例:1,1,2,3,5,8…
-
斐波拉切数列的特点:第3个数等于前两个数之和,最开始的第1,2个数是固定的
-
代码:
#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个变量,存储第1,2个数,默认都是1 int f1 = 1, f2 = 1; // 循环变量 int i = 1; // 每一次循环产生2个数,也就循环10次 // 通过循环生成数列 for (; i <= 10; i++) { // 输出当前的两个数列 printf("%-12d%-12d\n",f1,f2); // 计算后续的第1,2个数 f1 += f2; // f1 = f1 + f2 = 1 + 1 ... f2 += f1; // f2 = f2 + f1 = 2 + 1 ... } return 0; }
-
运行结果:
-
课堂练习
**需求:**通过键盘录入一个整数,判断这个整数是否是水仙花数。
示例:
#include <stdio.h>
#include <math.h>
int main() {
int num, originalNum, n = 0, sum = 0;
printf("请输入一个整数: ");
scanf("%d", &num);
originalNum = num;
// 计算数字的位数
while (num != 0) {
num /= 10;
n++;
}
num = originalNum; // 恢复原始数值
// 计算各位数字的n次方之和
while (num != 0) {
int digit = num % 10;
sum += pow(digit, n);
num /= 10;
}
// 判断并输出结果
if (sum == originalNum) {
printf("%d 是水仙花数。\n", originalNum);
} else {
printf("%d 不是水仙花数。\n", originalNum);
}
return 0;
}
直到型循环的实现
特点:先执行,后判断,不管条件是否满足,至少执行一次。
**代表:**do…while,goto(已淘汰,不推荐使用)
do…while
语法:
循环变量;
do
{
循环体;
}while(循环条件);
说明:
①循环条件
的返回值必须是逻辑值
(0和非0(计算机返回1))
②{}
包起来的内容称之为循环体
③我们要在循环体
中控制循环条件
的变化,否则会产生死循环。
执行过程:
特点:先执行,后判断,不管条件判断是否满足,循环体语句至少执行一次。
案例:
-
需求:求1~100以内偶数和
-
分析:
- 创建一个变量,用来存储sum, sum = 0
- 创建一个循环变量, i = 2
- 创建一个do…while循环,再循环体中,校验1 % 2 ==0,若果满足,就是实现sum +=i
- 计算完成,在循环体的末行,对循环变量进行更新i++
- 限制循环的出口:i <= 100
- 循环结束,打印输出sum的值
-
代码
#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个变量,存储偶数和 int sum = 0; // 创建一个循环变量 int i = 2; // 创建一个循环,实现偶数和的计算 do { // 校验偶数 if (i % 2 == 0) sum += i; // 更新循环变量,逼近出口 i++; } while(i <= 100); // 打印输出sum printf("1~100以内的偶数和是%d\n", sum); return 0; }
案例:
-
需求:用C语言编写简单猜数字游戏代码。游戏规则是程序随机生成一个1到100之间的数字,玩家通过输入猜测数字,程序会提示猜测是太大还是太小,直到玩家猜中为止。
-
代码
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc,char *argv[]) { // 创建变量number(随机数)、guess(用户猜测的数字)、count(计数) int number, guess, count = 0; // 设置随即种子 srand(time(NULL)); // 生成随机数 1 ~ 10之间的随机数 number = rand() % 10 + 1; // 此时生成 0 ~ 9之间的随机数,包含头不含尾 printf("-----------------------------\n"); printf("| 猜数字小游戏v1.0 |\n"); printf("-----------------------------\n"); printf("我已经想了一个1~10之间的数,你猜猜是多少?\n"); // 猜数字,猜不对一直猜,猜到对为止 do { printf("请输入你的猜测:\n"); // 接收scanf的返回值 int result = scanf("%d",&guess); // 对非法输入进行校验(比如,我们要求输入数值,但是不小写输入了非数值,此时就是非法输入) if (result != 1) { // 如果输入的不是数值,就是非法的 // 清空输入缓冲区,重新从外部设备读取数据到缓冲区 while(getchar() != '\n'); // getchar 类似 scanf("%c",c); printf("请输入数字!\n"); continue; // 这次循环,跳过不算 } // 计数 count ++; // 校验 if (guess > number) printf("太大了,再猜一次。\n"); else if (guess < number) printf("太小了,再猜一次。\n"); else { printf("恭喜你,猜对了!\n"); printf("你一共猜了%d次。\n", count); } } while (guess != number); // 等价于 guess > number || guess < number return 0; }
-
运行结果
goto(了解)
语法:
goto 标签
标签:标明目标代码的位置,是一个不加""
的字符串。
案例:
-
需求:求1~100以内的偶数和
-
代码:
#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个变量,用来存放偶数和 int sum = 0; // 创建一个循环变量 int i = 2; // 定义goto标签,名字自己取,符合标识符命名即可 loop: // 过滤偶数 if (i % 2 == 0) sum += i; // 偶数和计算 i++; // 更新循环变量 // 循环条件 if (i <= 100) goto loop; // 标签loop由goto触发 printf("1~100以内的偶数和是%d\n",sum); return 0; }
-
注意事项
- 可读性:
goto
语句会破坏代码的结构和可读性,使得代码难以理解和维护。因此,应尽量避免使用。 - **替代方案:**大多数情况下,可以使用循环、条件语句、函数等结构来替代
goto
语句,使代码更加清晰和易于管理。 - **嵌套限制:**虽然
goto
语句可以跨函数跳转(即跳转到另一个函数中的标签),但这种用法是不合法的,并且会导致编译错误。goto
语句只能在同一函数内部跳转。 - 错误处理:在某些情况下,
goto
语句可以用于错误处理,例如从嵌套的多层循环中跳出。但即使在这种情况下,也应谨慎使用,并考虑是否有更好的替代方案。
- 可读性:
总结
虽然goto
语句在C语言中是合法的,并且有时可能看起来很方便,但过度使用或不当使用会导致代码质量下降。因此,建议尽量避免使用goto
语句,而是采用更结构化和可维护的编程方法。
循环的嵌套
3种循环(while、for、do…while)可以相互嵌套的。在前一个循环结构中又嵌套一个循环结构。例如:
)
案例:
-
需求:九九乘法表
-
分析:
-
我们发现九九乘法表整体其实就是一个9行的直角三角形
-
同时发现:每一行显示的列数最多不超过行,第1行1列,第2行2列…第9行9列
假定:行用i表示,列用j表示,i和j的关系:j <=i
-
在实现的时候,我们发现需要同时控制行和列的变化,在编程中,行列式需要通过for双层嵌套实现(****)
-
-
代码:
#include <stdio.h> int main(int argc,char *argv[]) { printf("--- 九九乘法表 ---\n"); // 外层循环:生成行,有9行 for (int i = 1; i <= 9; i++) { // 内层循环:生成列,列受到行的影响, 列 <= 行 for (int j = 1; j <= i; j++) { // 生成算式 printf("%d×%d=%d\t",j,i,j*i); } // 每一行所有列输出完毕,一定要换行 printf("\n"); } printf("\n"); return 0; }
-
运行结果:
案例
-
需求:求100~200之间所有的素数(素数又被称作质数)
-
分析
- 什么是素数:只能被1和自身整除的数叫做素数或者质数。
-
代码:
#include <stdio.h> int main(int argc,char *argv[]) { // 创建一个变量,存放100~200之间的自然数 int num = 100; // 定义一个循环变量 int i; // 定义一个标记,用来标记这个num是否是素数 int flag; // 外层循环:生成100~200之间的自然数 for (; num <= 200; num++) { // 每一个num生成的时候,我们默认其为素数 flag = 1; // 内层循环,我们让每一个生成的num 除 2 ~ num / 2之间的数,一旦出现能整除的情况,就说明这个num不是素数 for (i = 2; i < num / 2; i++) { // 测试整除的情况 if(num % i == 0) { flag = 0; break;// 循环结束,只要出现一次能整除的情况,就说明这个数不是素数 } } // 校验完毕,验证这个num是否是素数 if(flag) // 等价于 flag == 1 printf("%-4d",num); } printf("\n"); return 0; }
-
运行结果:
循环结构的典型应用场景
- 求累和:举例:``1+2+3…+100的和,sum = 0`
- 求累积:举例:
1*2*3..*100的积,result = 1
- 求均值:举例:
(1+2+3...+100)/100的值
- 求极值:举例:
12,34,56,67中的最大值、最小值
- 元素遍历:常用于数组元素的遍历。
- …
基础算法模型
- 累和
- 定义一个变量(sum),并赋初值为0;
- 该变量累加(+=)每一个数据项(i);
- 当访问完每一个数据项,此时该变量的取值就是累加和的结果。
- 累乘
- 定义一个变量,并赋初值为1;
- 用该变量累乘(*=)每一个数据项;
- 当访问完每一个数据项,此时该变量的取值就是累乘的结果。
- 极值(多应用于数组)
- 定义一个变量,并赋初值为第一个数据项;
- 从第二个数据项开始,依次于该变量进行比较,如果大于/小于该变量,则将当前数据项的 数据赋值给该变量。
- 当访问完每一个数据项,此时该变量的取值就是求极值的结果。
break和continue
break
功能:
①用在switch中,用来跳出switch的case语句;如果case没有break,可能会产生case穿透。
②用在循环中(while、do…while、for),提前结束循环,也就是跳出整个循环。
说明:
①break不用用于循环语句和swich语句之外的任何其他语句中。
②break只能终止并跳出最近一层的循环结构,简而言之,就是只能跳出一层循环。
举例:
**帅同学过年的时候,家里安排了5场相亲,帅哥相到第3个时候相中了,此时剩余2场相亲取消。
图示:
案例:
-
需求:参与分支结构的考试,如果考试及格
-
代码:
#include <stdio.h> int main(int argc,char *argv[]) { printf("分支结构考试!\n"); // 创建一个变量,用来存储成绩 int score; // 创建一个循环,实现重复考试 do { printf("磊哥开始考试...\n"); scanf("%d", &score); // 考试及格,就终止循环 if (score >= 60) break; printf("很遗憾,没有及格,继续考!\n"); // break执行后,这个代码不执行 }while(1); // 死循环 printf("考试结束!\n"); return 0; }
-
运行结果
continue
**功能:**continue语句不会结束整个循环,而是跳过本次循环尚未执行的语句,进入下一次循环。
举例说明:
**帅同学过年的时候,家里安排了5场相亲,帅哥相到第3个时候相中了,姑娘已经结婚了,帅哥跳过,继续剩余2场相亲。
说明:
①仅用于循环语句中
②在嵌套循环的情况下,continue语句只对包含它的最近一层循环其作用,只能有用单层循环。
图示:
案例
-
需求:求1~100以内的偶数和
-
代码:
#include<stdio.h> /** * 不使用continue */ int for_test1() { // 创建两个变量,sum存储累加和,i存储自然数(i是循环条件) int sum = 0, i; // 创建一个循环,实现偶数和运算 for (i = 2; i <= 100; i++) { // 计算偶数和 if (i % 2 == 0) sum += i; } // 输出偶数和 printf("1~100以内的偶数和是:%d\n", sum); return 0; } /** * 使用continue */ int for_test2() { // 创建两个变量,sum存储累加和,i存储自然数(i是循环条件) int sum = 0, i; // 创建一个循环,实现偶数和运算 for (i = 2; i <= 100; i++) { // 跳过奇数 if(i % 2 != 0) continue; // 计算偶数和 sum += i; // 一旦执行上面的continue,这句代码就不会执行 } // 输出偶数和 printf("1~100以内的偶数和是:%d\n", sum); return 0; int main(int argc,char *argv[]) { for_test1(); return 0; }
跳出多层循环(扩展)
跳出多层循环是不能直接使用break和continue实现的,因为它们只能跳出单层循环,跳出多层循 环,需要我们自定一定标志位进行跳出(标志位也可以配合break使用)。
案例:
#include <stdio.h>
int fun0()
{
// 定义一个标志位
int is_flag = 1;// 默认循环成立
// 定义一个变量,用来接收控制台输入
char fu;
// 循环
while(is_flag)
{
printf("外层循环执行的内容..\n");
while(is_flag)
{
printf("内存循环执行的内容..\n");
scanf("%c",&fu);
if(fu == 'Y' || fu == 'y')
{
is_flag = 0;
}
}
}
}
int fun1()
{
// 定义一个标志位
int is_flag = 1;// 默认循环成立
// 定义一个变量,用来接收控制台输入
char fu;
// 循环
while(is_flag)
{
printf("外层循环执行的内容..\n");
while(1)
{
printf("内存循环执行的内容..\n")
scanf("%c",&fu);
if(fu == 'Y' || fu == 'y')
{
is_flag = 0;
break;
}
}
}
}
int main()
{
fun0();
fun1();
}
注意:如果是多层循环(嵌套循环),进的时候是从外到内,跳出的时候是从内到外。