青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-
友链 搜索文章 >>

青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-

【C语言学习笔记】五、循环


阅读数: 0次    2025-07-15
字数:8.2k字 | 预计阅读时长:32分钟

1. 循环在生活上的作用

循环就像在操场上绕圈跑步,需要有以下几个逻辑:

  • 开始:循环的开始,第一次循环
  • 计数:每循环一次时候要有一次计数
  • 循环的内容:每个循环要干的事情,要执行的语句
  • 结束:循环的结束,跳出这个循环

循环的作用:

  • 避免重复,提高效率
  • 处理大量内容,简化代码
  • 灵活控制程序的行为(有检查机制,根据条件控制循环次数)

2. 初探while循环

while循环的基础语法为:

1
2
3
while (expression) {
statement;
}

与if类似,expression是一个结果为真(true)或假(false)的表达式,当它为真的时候,循环执行括号里的statement,一直到它是假的时候直接跳出循环,继续执行下面的程序。

例如,一个人需要跑步十圈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const uint8_t TOTAL_LAPS = 10;	// 总圈数(常量)
uint8_t current_lap = 0; // 当前圈数

puts("开始跑步!");

// 一直跑步,直到跑完所有圈数
while (current_lap < TOTAL_LAPS)
{
// 迭代点,跑完一圈计次
current_lap++;
printf("跑完第%" PRIu8 "圈!\n", current_lap);
}

puts("跑完了!");

const关键字代表此变量为常量,程序中不可改变,其变量名一般全为大写。

一个while循环,需要有循环要达到的目标与目前的初始化值。没有达到目标时,循环一直进行,当达到想要的目标时,循环结束。

使用continue;语句可以立即结束当前循环,跳过本次循环的剩余部分,并进入下一次循环,可以在需要提前结束此次循环时使用。

使用break;语句可以立即结束循环流程,继续执行循环外的代码,可以在需要跳出循环时使用。

正常来说,每次循环都需要有一个迭代点,使初始化值不断向目标靠近,否则将会进入死循环。

一般的while循环的整个流程可以用下面的流程图来表示:

graph TB
    A[初始化变量] --> B([循环开始])
    B --> C{循环条件判断}
    C -- 条件为真 --> D[执行循环体]
    D --> E[更新循环变量]
    E --> C
    C -- 条件为假 --> F([循环结束])

由此,一个循环需要包括以下五个部分:

  • 初始化
  • 循环条件判断
  • 循环体
  • 迭代点
  • 终止条件

3. 自动贩卖机案例

案例要求:

  • 模拟自动贩卖机买饮料的过程
  • 假设机器只支持硬币,每次投入一枚硬币
  • 只卖一种饮料,每瓶价格是5元
  • 用户需要一直投币,直到金额足够,机器才能提供饮料
  • 附加条件1:机器只支持1元、2元、5元的硬币,其他的金额不支持
  • 附加条件2:用户投入的金额大于饮料金额时,需要为用户找零

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>

// 自动贩卖机案例
int main(void) {
// 初始化参数
const uint32_t PRICE = 5; // 饮料的价格为5元(常量)
uint32_t balance = 0; // 用户余额(当前投入的总数,每次循环迭代)
uint32_t coin = 0; // 用户投币金额

// 提示用户投币
puts("欢迎使用自动贩卖机!");

// 循环直到余额大于等于饮料价格
while (balance < PRICE) {
// 提示用户输入投币金额
printf("当前余额为%" PRIu32 "元,请输入投币金额(只能是1元、2元或5元):", balance);

// 获取用户输入的投币金额
scanf_s("%" SCNu32, &coin);

// 当投入金额符合要求时,更新余额,否则提示重新输入并结束本次循环
if (coin == 1 || coin == 2 || coin == 5) {
balance += coin;
}
else {
puts("当前投币金额无效,请重新输入!");
continue; // 如果投币金额无效,跳过本次循环
}

// 输出当前投入金额
printf("您成功投入了%" PRIu32 "元\n", coin);
}

// 当余额大于等于饮料价格时,提示用户购买成功
puts("购买成功!请取走您的饮料!");

// 找零:如果有多余的余额,提示用户取回
if (balance > PRICE) {
printf("您还有%" PRIu32 "元的余额,请收好!\n", balance - PRICE);
}

// 提示用户结束
puts("感谢使用自动贩卖机,欢迎下次光临!");

return 0;
}

当使用scanf_s()获取输入时,当数据类型为例如uint32_t的标准类型时,虽然也可以使用PRIu32标准类型,但是对于输入,有专门的SCNu32类型,也包含在头文件inttypes.h中。

4. 遇到循环问题的解决方案与经验

在循环部分:

  • 确保所有用到的变量都被正确初始化;
  • 确保循环开始条件、结束条件正确,不会导致无法进入循环或进入意料之外的死循环;
  • 确保迭代点逻辑正确,经过迭代,可以靠近最终要实现的目标。

在企业中,循环体内尽量不要做太多的计算,减少对函数的调用,也应该避免使用一些容易破坏循环的数据结构。

程序中,变量命名要尽量直观合理,常量一定要全部大写,且一定要添加变量的注释,这样会提高代码的可读性,Visual Studio中鼠标悬停时候也可以看到注释内容,这在排查问题时候会有很大的帮助。

排查时在关键的地方也可以多用printf()来打印关键变量的值,来帮助排查问题。

5. break与利用死循环-求和案例

案例要求:

  • 写一个计算求整数和的案例
  • 用户从终端输入一系列数字,数字间用回车隔开,直到用户输入一个单一的“0”作为结束
  • 程序把所有输入的数字相加,输出最终结果

第一种程序实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始化变量
uint32_t sum = 0; // 用于存储求和结果
uint32_t number = 1; // 用于存储用户输入的整数

puts("请输入一系列整数,用回车隔开,输入0结束:");

// 循环读取用户输入的整数,直到输入0为止
while (number != 0) {
puts("请输入一个整数(输入0结束):");

scanf_s("%" SCNu32, &number);
sum += number;
}

printf("求和的结果是: %" PRIu32 "\n", sum);

第一种写法就是按照一般的思路,while循环中的条件与循环结束时需要满足的条件相反,即number != 0,但此时变量number的值如果被初始化为0,进入while循环之前的第一次判断就无法通过,导致程序无法进入循环。

因此,这种写法应当将number的初始值初始化为0以外的其他值,这样就可以正常进入循环,程序正常运行。

值得一提的是,number的值不管初始化为多少,只要不是0,就都不会影响程序运行,因为它的值会在scanf_s()中被重新赋值(从键盘输入重新给number赋值)。

如果不对number做初始化,程序会直接报错。

这种写法看似可行,但逻辑比较绕,不是一个好的写法,一个比较好的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 初始化变量
uint32_t sum = 0; // 用于存储求和结果
uint32_t number; // 用于存储用户输入的整数

puts("请输入一系列整数,用回车隔开,输入0结束:");

// 循环读取用户输入的整数,直到输入0为止
while (true) {
puts("请输入一个整数(输入0结束):");

scanf_s("%" SCNu32, &number);
if (number == 0) {
break; // 输入0时结束循环
}
sum += number;
}

printf("求和的结果是: %" PRIu32 "\n", sum);

while的条件中写true,可以不做任何条件判断,直接进入死循环,当输入的number不是0时,将其加和给sum,并进行下一次循环,一直到输入的number为0的时候,使用break;语句打断并跳出循环。

此时number只需要声明,不做初始化时程序也不会报错。

6. 处理字符和字符串的退出检测问题(选修)

上一节的程序中,循环的结束是用0来判定的,但是0也是一个数字,这样判定并不好,最好用一个字母,例如输入一个单独的“q”时,程序结束。

如果对上一节的程序不做修改,直接输入字符“q”,程序就会陷入死循环,这是因为scanf_s()不会对非预期的类型输入做处理,也就是目前程序中的预期输入类型为%u,如果输入为其他类型,输入的数据就一直卡在内存中无法被处理,每次循环时,scanf_s()都会对内存中的这个数据做处理且处理失败,从而不会请求新的输入,使得程序陷入死循环。

下面来做这个拓展,对于一个简单的扩展来说,使用一个char类型的字符代替数字输入,但是此时此求和器就只能计算10以内的加减法,输入的数字超过10,程序就会出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 初始化变量
uint32_t sum = 0; // 用于存储求和结果
char input; // 用于存储用户输入的字符,会被转换为整数

puts("请输入一系列整数(0-9),用回车隔开,输入q结束:");

// 循环读取用户输入的字符,直到输入q为止
while (true) {
puts("请输入一个0-9的整数(输入q结束):");

scanf_s(" %c", &input, 1);
if (input == 'q') {
break; // 输入q时结束循环
}

// 检查输入是否为数字字符,如果是,则转换为整数并累加到sum中
if (input >= '0' && input <= '9') {
sum += (uint32_t)(input - '0'); // 将字符转换为整数并累加
} else {
puts("输入无效,请输入0-9的整数或q结束。");
continue; // 如果输入无效,继续下一次循环
}
}

printf("求和的结果是: %" PRIu32 "\n", sum);

其中的scanf_s()需要注意,因为输入数字并输入回车时,实际的输入为数字+\n,因此如果要让程序正常运行,需要在%c前加空格,且输入字符时,需要加上scanf_s()的第三个参数。

这个程序虽然实现了输入q退出,但是有个很明显的问题,就是只能计算10以内的求和,接下来将使用比较复杂的知识来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h> // 包含标准库头文件
#include <limits.h> // 包含整数限制头文件
#include <errno.h> // 包含错误号头文件

int main() {
// 初始化变量
uint32_t sum = 0; // 用于存储求和结果
char input[50]; // 用于存储用户输入的字符串
char* end; // 用于指向转换后字符串的结束位置

puts("请输入一系列整数,用回车隔开,输入q结束:");

// 循环读取用户输入的字符,直到输入q为止
while (true) {
errno = 0; // 重置错误码

puts("请输入一个的整数(输入q结束):");

scanf_s("%49s", &input, 50); // 使用scanf_s安全读取字符串,限制输入长度为49字符

if (input[0] == 'q' && input[1] == '\0') {
break; // 输入q时结束循环
}

// 检查输入是否为数字字符
uint64_t number = strtol(input, &end, 10); // 将输入字符串转换为整数,基数为10

// 检查转换是否成功
if (end == input || *end != '\0') {
// 如果转换失败,end指向输入字符串的开头或未能完全转换
puts("输入无效,请输入一个整数或q结束。");
continue; // 继续下一次循环
}
else if (errno == ERANGE || number < 0 || number > UINT32_MAX) {
// 如果转换结果超出范围,或者小于0或大于UINT32_MAX
printf("输入的数字超出范围,请输入一个小于或等于 %" PRIu32 " 的正整数。\n", UINT32_MAX);
continue; // 继续下一次循环
}
else {
// 如果转换成功,将数字加到sum中
sum += (uint32_t)number; // 将number转换为uint32_t并加到sum中
}
}

printf("求和的结果是: %" PRIu32 "\n", sum);

return 0;
}

这个程序比较复杂,但也比较完善,用到了数组、指针、库函数调用、错误码的使用等知识,几乎对输入数据所有可能出现的情况做了处理,可用于参考学习。

7. do-while与while的区别

do-while循环的基础语法为:

1
2
3
do {
statement;
} while (expression);

与while类似,expression是一个结果为真(true)或假(false)的表达式,当它为真的时候,循环执行括号里的statement,一直到它是假的时候直接跳出循环,继续执行下面的程序。

与while不同的是,while是在执行statement;前先判断一次expression,当它是真的时候进入循环,为假的时候不进入循环,但是do-while是先执行一次statement;,然后判断expression来决定是否要继续循环。

由此可见,do-while循环的流程图如下:

graph TB
    A[初始化变量] --> B([循环开始])
    B --> C[执行循环体]
    C --> D[更新循环变量]
    D --> E{循环条件判断}
    E -- 条件为真 --> C
    E -- 条件为假 --> F([循环结束])

do-while用于需要在进入循环前至少执行一次循环体的情况,比如第5节中的求和案例,除利用死循环和break;配合之外,可以使用do-while实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始化变量
uint32_t sum = 0; // 用于存储求和结果
uint32_t number; // 用于存储用户输入的整数

puts("请输入一系列整数,用回车隔开,输入0结束:");

// 先获取一次输入,然后循环读取用户输入的整数,直到输入0为止
do {
puts("请输入一个整数(输入0结束):");

scanf_s("%" SCNu32, &number);
sum += number;
} while (number != 0);

printf("求和的结果是: %" PRIu32 "\n", sum);

此时即使不对number做初始化,程序也不会报错。

8. 随机数猜数游戏案例-do-while练习

猜数游戏案例要求:

  • 程序自己生成一个1-100的随机数
  • 循环读取用户输入的数字,直到用户输入的数字与目标数字相同
  • 当大于或小于目标数时,给出对应的提示

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <time.h> // 时间函数头文件
#include <stdlib.h> // 标准库头文件

// C语言案例:猜数游戏
int main(void) {
uint32_t secretNumber; // 用于存储生成的随机数
uint32_t guess; // 用于存储用户的猜测

srand(time(NULL)); // 初始化随机数种子

// 生成1到100之间的随机数
secretNumber = rand() % 100 + 1;

puts("欢迎来到猜数游戏!");
puts("我已经选择了一个1到100之间的数字。");

do {
puts("请输入你的猜测:");
scanf_s("%" SCNu32, &guess);
if (guess < secretNumber) {
puts("太小了!再试一次。");
} else if (guess > secretNumber) {
puts("太大了!再试一次。");
}
} while (guess != secretNumber);

puts("恭喜你,猜对了!");
printf("我选的数字就是:%" PRIu32 "\n", guess);

return 0;
}

这个程序与上上一节出现的问题一样,当输入非法数据,例如输入一个字符“q”的时候,程序会进入死循环,因此需要检查判断输入是否符合要求,下面两种处理方式用于参考学习。

第一种处理方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
uint32_t secretNumber;	// 用于存储生成的随机数
uint32_t guess; // 用于存储用户的猜测

uint32_t status; // 用于存储函数调用的状态

srand(time(NULL)); // 初始化随机数种子

// 生成1到100之间的随机数
secretNumber = rand() % 100 + 1;

puts("欢迎来到猜数游戏!");
puts("我已经选择了一个1到100之间的数字。");

do {
puts("请输入你的猜测:");

status = scanf_s("%" SCNu32, &guess); // 读取用户输入的猜测,并将函数返回值存储在status中

// 检查输入是否有效
if (status != 1) {
puts("输入无效,请输入一个数字。");
while (getchar() != '\n'); // 清除输入缓冲区
continue; // 重新开始循环
}

if (guess < secretNumber) {
puts("太小了!再试一次。");
} else if (guess > secretNumber) {
puts("太大了!再试一次。");
}
} while (guess != secretNumber);

puts("恭喜你,猜对了!");
printf("我选的数字就是:%" PRIu32 "\n", guess);

使用status储存scanf_s()的返回值,当返回值不为1时,输入无效,此时清除输入缓冲区并进入下一次循环。

第二种处理方法比较复杂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
uint32_t secretNumber;	// 用于存储生成的随机数
uint32_t guess; // 用于存储用户的猜测

uint32_t status; // 用于存储函数调用的状态
char buffer[100]; // 输入缓冲区

srand(time(NULL)); // 初始化随机数种子

// 生成1到100之间的随机数
secretNumber = rand() % 100 + 1;

puts("欢迎来到猜数游戏!");
puts("我已经选择了一个1到100之间的数字。");

do {
puts("请输入你的猜测:");

fgets(buffer, sizeof(buffer), stdin); // 从标准输入读取一行
status = sscanf_s(buffer, "%" SCNu32, &guess); // 解析输入为无符号整数

// 检查输入是否有效
if (status != 1) {
puts("输入无效,请输入一个数字。");
continue; // 如果输入无效,重新开始循环
}

if (guess < secretNumber) {
puts("太小了!再试一次。");
} else if (guess > secretNumber) {
puts("太大了!再试一次。");
}
} while (guess != secretNumber);

puts("恭喜你,猜对了!");
printf("我选的数字就是:%" PRIu32 "\n", guess);

9. continue和break联用条件判断和实际用途

案例要求:

  • 用户输入一系列数字,需要忽略所有负数和大于100的数,0-100之间的数才是有效数字
  • 对于每一个有效数字,如果它是偶数,告知用户这个数是偶数
  • 如果它是奇数,告知用户这个数是奇数
  • 当用户输入-1时,退出程序

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int32_t number;

while (true) {
puts("请输入一个数字(输入 -1 退出):");
scanf_s("%" SCNd32, &number);
if (number == -1) {
puts("程序结束。");
break; // 退出循环
}
if (number < 0 || number > 100) {
puts("无效数字,请输入0到100之间的数字。");
continue; // 跳过当前循环,继续下一次输入
}
if (number % 2 == 0) {
printf("%" PRId32 " 是偶数。\n", number);
} else {
printf("%" PRId32 " 是奇数。\n", number);
}
}

continue和break与前面提到的卫语句类似,一般与if联用,用于对一些特殊情况做一些特殊处理,因此一般需要在循环体的靠前部分调用。

10. 初探for循环

for循环的基础语法为:

1
2
3
for (init-expression; cond-expression; loop-expression) {
statement;
}

for语句的括号中可以包含三个表达式:

  • init-expression:为循环指定初始化,在循环中只在开始时执行一次,如int i = 0,如果在这里声明并初始化,这个变量就是局部变量,只能在这个for循环中使用;
  • cond-expression:是循环条件,和while循环类似,是一个结果为真(true)或假(false)的表达式,当它为真的时候,循环执行括号里的statement;,一直到它是假的时候直接跳出循环,继续执行下面的程序。一般与init-expression中定义的初始化循环变量有关;
  • loop-expression:是迭代点,一般是对init-expression中定义的初始化循环变量做操作,使其每次循环都接近cond-expression。

可以用流程图表示为:

graph TB
    A([循环开始]) --> B[初始化表达式
(init-expression)] B --> C{条件表达式
(cond-expression)} C -- 条件为真 --> D[执行循环体
(statement;)] D --> E[更新表达式
(loop-expression)] E --> C C -- 条件为假 --> F([循环结束])

对于while循环中的跑步案例,用for循环可以这样实现:

1
2
3
4
5
6
7
8
9
10
const uint8_t TOTAL_LAPS = 10;	// 总圈数(常量)

puts("开始跑步!");

// 一直跑步,直到跑完所有圈数
for (uint8_t current_lap = 0; current_lap < TOTAL_LAPS; current_lap++) {
printf("跑完第%" PRIu8 "圈!\n", current_lap + 1);
}

puts("跑完了!");

11. 训练:求和平方

要求:

  • 用户输入一个正整数N,程序计算从1到N的所有整数的平方和

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
uint32_t number;				// 用户输入的正整数
uint32_t sum_of_squares = 0; // 初始化平方和为0

puts("请输入一个正整数N:");
scanf_s("%" SCNu32, &number);

// 计算从1到N的所有整数的平方和
for (uint32_t index = 1; index <= number; index++) {
sum_of_squares += index * index; // 累加平方
}

printf("从1到%" PRIu32 "的所有整数的平方和为:%" PRIu32 "\n", number, sum_of_squares);

12. 训练:倒数五个数

要求:

  • 用户输入一个正整数N,程序从N开始,一直倒数到1

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
uint32_t start_nmber;	// 倒数的起始数字

puts("请输入一个正整数N:");
scanf_s("%" SCNu32, &start_nmber);

puts("倒数开始!");

// 循环倒数
for (uint32_t index = start_nmber; index > 0; index--) {
printf("%" PRIu32 "\n", index);
}

puts("倒数结束!");

扩展:目前程序会直接倒数到结束,现扩展程序功能,实现按实际的时间来倒数。

不同平台下有不同的库函数来实现延时倒数功能,例如Windows平台下可以引用头文件windows.h,使用函数Sleep()来实现延时,此函数传入参数的单位是毫秒。

Linux平台下可以引用头文件unistd.h,使用sleep()来实现延时,此函数传入参数的单位是秒。

具体代码实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <Windows.h> // Windows头文件

// C语言案例:倒数五个数
int main(void) {
uint32_t start_nmber; // 倒数的起始数字

puts("请输入一个正整数N:");
scanf_s("%" SCNu32, &start_nmber);

puts("倒数开始!");

// 循环倒数
for (uint32_t index = start_nmber; index > 0; index--) {
printf("%" PRIu32 "\n", index);
Sleep(1000); // 暂停1秒
}

puts("倒数结束!");
return 0;
}

13. 训练:阶乘

要求:

  • 用户输入一个正整数N,计算N的阶乘(N!)

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uint32_t number;		// 用户输入的正整数
uint32_t factorial = 1; // 阶乘结果,初始化为1

puts("请输入一个正整数:");
scanf_s("%" SCNu32, &number);

puts("计算阶乘中...");

// 计算阶乘
for (uint32_t index = 1; index <= number; index++) {
factorial *= index; // 累乘
}

printf("结果:%" PRIu32 "! = %" PRIu32 "\n", number, factorial);

14. 训练:判断素数

开平方根:引入头文件math.h后,使用函数sqrt(number)即可对传入这个函数的number计算开平方根。

素数:又被成为质数,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数(规定1既不是质数也不是合数)。

要求:

  • 用户输入一个除0和1正整数,程序将判断这个数是否为素数,并输出结果

分析:

当一个数字N不是素数时,会有两个数字的乘积等于N,而又得知,N的开方N\sqrt{N}N​与它自身相乘,结果也是N,则前面得到的两个数,不可能都大于N\sqrt{N}N​,也不可能都小于N\sqrt{N}N​,因此他们只能分布在N\sqrt{N}N​两侧,也就是这两个数中的较小值,必定小于N\sqrt{N}N​。

又因为一个数的所有因数必定是对应的,因此只要一一检查N能否被从1到N\sqrt{N}N​之间的所有整数整除即可。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
uint32_t number;		// 用户输入的正整数
bool is_prime = true; // 假设number是素数

puts("请输入一个正整数(除0和1):");
scanf_s("%" SCNu32, &number);

// 检查输入的数是否大于1
if (number < 2) {
is_prime = false; // 小于2的数不是素数
}
else {
// 检查number是否为素数
for (uint32_t i = 2; i <= sqrt(number); i++) {
if (number % i == 0) {
is_prime = false; // 如果能被i整除,则不是素数
break; // 找到一个因子就可以退出循环
}
}
}

// 输出结果
if (is_prime) {
printf("%" PRIu32 " 是素数。\n", number);
}
else {
printf("%" PRIu32 " 不是素数。\n", number);
}

程序也可以有优化的方法,for循环的循环条件中的i <= sqrt(number)包含开根操作,在程序中比较耗时,可以用更简单的i * i <= number来替代。

15. 训练:简单的乘法表

要求:

  • 用户输入一个正整数N,程序生成一个简单的当前数的乘法表并输出

程序实现:

1
2
3
4
5
6
7
8
9
10
11
uint32_t number;		// 用户输入的正整数

puts("请输入一个正整数:");
scanf_s("%" SCNu32, &number);

printf("乘法表(1到%" PRIu32 "):\n", number);

// 生成并输出乘法表
for (uint32_t index = 1; index <= 10; index++) {
printf("%" PRIu32 " x %" PRIu32 " = %" PRIu32 "\n", index, number, index * number);
}

16. 训练:简单绘画正方形

要求:

  • 用户输入一个正整数N,程序绘制出一个用“*”组成的正方形
  • 正方形的长和宽都是N

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
uint32_t size;	// 正方形的边长

puts("请输入正方形的边长(正整数):");
scanf_s("%" SCNu32, &size);

puts("绘制的正方形如下:");
// 绘制正方形(使用for循环嵌套)
for (uint32_t i = 0; i < size; i++) {
for (uint32_t j = 0; j < size; j++) {
printf("* "); // 输出星号
}
printf("\n"); // 换行
}

17. 训练:简单绘画三角形

要求:

  • 用户输入一个正整数N,程序绘制一个用“*”组成的三角形
  • 三角形的行数等于N,每一行的”*“的个数从1开始,逐行增加

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
uint32_t size;	// 正方形的边长

puts("请输入正方形的边长(正整数):");
scanf_s("%" SCNu32, &size);

puts("绘制的正方形如下:");
// 绘制正方形(使用for循环嵌套)
for (uint32_t i = 0; i < size; i++) {
for (uint32_t j = 0; j <= i; j++) {
printf("* "); // 输出星号
}
printf("\n"); // 换行
}

18. 训练:金字塔数字

要求:

  • 用户输入一个正整数N,程序绘制一个行数为N的正金字塔
  • 每一行的数字个数逐行增加,最中间的数字是从1到N,其他数字由最中间的数字向两侧递减到1
  • 每一行数字居中显示

例如N为5时,输出应该为:

1
2
3
4
5
        1
1 2 1
1 2 3 2 1
1 2 3 4 3 2 1
1 2 3 4 5 4 3 2 1

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
uint32_t levels;	// 定义金字塔的层数变量

puts("请输入金字塔的层数:");
scanf_s("%" SCNu32, &levels);

printf("您输入的层数是:%" PRIu32 "\n", levels);

// 打印金字塔
for (uint32_t i = 1; i <= levels; i++)
{
// 打印空格
for (uint32_t j = 0; j < levels - i; j++)
{
printf(" ");
}

// 打印正向数字
for (uint32_t j = 1; j <= i; j++)
{
printf("%" PRIu32 " ", j);
}

// 打印反向数字
for (uint32_t j = i - 1; j > 0; j--)
{
printf("%" PRIu32 " ", j);
}

// 换行
printf("\n");
}

此程序还能做一些优化,打印空格时,每一次循环都要计算一次levels - i,然而进入每一行时,这个值都是固定的,可以提前计算好,使得每一次循环只需要做判断即可。

打印空格部分变为:

1
2
3
4
5
6
7
// 打印空格
uint32_t spaces = levels - i; // 提前计算需要打印的空格数量

for (uint32_t j = 0; j < spaces; j++)
{
printf(" ");
}

19. 案例:进度条

要求:使用循环模拟一个进度条

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint32_t total_steps = 100; // 总步骤数

puts("处理中...");

// 模拟处理过程
for (uint32_t i = 0; i <= total_steps; i++) {
printf("\r["); // 回车符,回到行首
for (uint32_t j = 0; j < i; j++) {
printf("#");
}
for (uint32_t j = i; j < total_steps; j++) {
printf(" ");
}
printf("] %" PRIu32 "%%", (i * 100) / total_steps);

fflush(stdout); // 刷新输出缓冲区
Sleep(100); // 模拟处理延时(100毫秒)
}

puts("\n处理完成!");

这个代码实现中总步骤数字只能是100,导致进度条过长,超出一行的范围时,进度条刷新就不太正确,可以增加进度条长度和当前步骤,在每一次绘制进度条之前计算当前位置在进度条中的占比,以此来实现在进度条较短时均匀刷新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
uint32_t total_steps = 100; // 总步骤数
uint32_t bar_length = 50; // 进度条长度
uint32_t pos_step = 0; // 当前步骤位置

puts("处理中...");

// 模拟处理过程
for (uint32_t i = 0; i <= total_steps; i++) {
pos_step = (i * bar_length) / total_steps; // 计算当前进度

printf("\r["); // 回车符,回到行首

// 输出进度条
for (uint32_t j = 0; j < bar_length; j++) {
if (j < pos_step) {
printf("#");
} else {
printf(" ");
}
}

// 输出百分比
printf("] %" PRIu32 "%%", (i * 100) / total_steps);

fflush(stdout); // 刷新输出缓冲区,确保进度条实时更新
Sleep(1000); // 模拟处理延时(100毫秒)
}

puts("\n处理完成!");

20. 案例:检查组件故障

要求:

  • 模拟若干组件的健康检查流程
  • 每个组件会有20%的概率随机出现故障
  • 此案例用于参考学习

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <time.h>

// C语言案例:检查组件故障
// 模拟检查组件健康状态的函数
bool check_component_health(uint32_t component_id);

int main(void) {
uint32_t total_components = 10; // 总共的组件数量

srand(time(NULL)); // 初始化随机数种子

puts("开始检查组件健康状态...");

// 遍历每个组件进行健康检查
for (uint32_t i = 1; i <= total_components; ++i) {
printf("正在检查组件 %" PRIu32 "...", i);

// 模拟20%的概率出现故障
if (check_component_health(i)) {
printf("组件 %" PRIu32 " 状态良好!\n", i);
} else {
printf("组件 %" PRIu32 " 出现故障!\n", i);
}
}

return 0;
}

// 检查组件健康状态的函数实现
bool check_component_health(uint32_t component_id) {
// 在实际应用中,这里可能会发送网络请求,有复杂的逻辑来检查组件状态

// 这里使用rand()函数生成一个随机数来模拟组件健康状态
// 原理解释:使用rand()函数生成一个随机数,范围是0到RAND_MAX(通常是32767)
// 因为rand() % 5的结果是0到4之间的整数,所以每5次调用一次rand(),会有一次结果为0,这样就能模拟20%的故障概率
return (rand() % 5 != 0); // 返回true表示健康,false表示故障
}

21. 使用VS进行debug调试

在循环中遇到问题时,可以使用printf()在合适的地方打印输出,可以了解程序执行到当前这一步时候各个循环变量的值,方便调试。

同时,Visual Studio还有一个非常强大的功能,就是debug调试,可以在想要监测的某一行代码的行号左边点击断点设置栏,为这一行代码打上断点,出现一个红色的圆点即为设置成功。

此时使用调试模式运行程序,程序执行到这一行代码时候就会停下来,同时在界面上显示目前所有相关变量的值,也可以点击Continue(继续)来使程序继续运行,直到运行到下一个断点,或是点击单步调试按钮,让程序从当前代码的位置按每一小步继续向下运行,相关变量的值也会实时显示在界面上并随着程序的运行而变化。

断点再次点击即可消失。

这种调试手段非常强大,不需要写很多的printf()输出,简单又高效,应该熟练掌握。

可以自行搜索其他相关资料或自己摸索,来学习这种debug调试方式。

22. 第五章结束语

循环这一章非常注重训练,一定要多思考,多训练!

每个人都有自己的优缺点,不要自负,也不必自卑。

保持谦虚,坚持学习!

本文来源: 青江的个人站
本文链接: https://hanqingjiang.com/2025/07/15/20250715_C_loop/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
知识共享许可协议
赏

谢谢你请我喝可乐~

支付宝
微信
  • Notes
  • C

扫一扫,分享到微信

微信分享二维码
为Hexo博客引入Mermaid流程图渲染
  1. 1. 1. 循环在生活上的作用
  2. 2. 2. 初探while循环
  3. 3. 3. 自动贩卖机案例
  4. 4. 4. 遇到循环问题的解决方案与经验
  5. 5. 5. break与利用死循环-求和案例
  6. 6. 6. 处理字符和字符串的退出检测问题(选修)
  7. 7. 7. do-while与while的区别
  8. 8. 8. 随机数猜数游戏案例-do-while练习
  9. 9. 9. continue和break联用条件判断和实际用途
  10. 10. 10. 初探for循环
  11. 11. 11. 训练:求和平方
  12. 12. 12. 训练:倒数五个数
  13. 13. 13. 训练:阶乘
  14. 14. 14. 训练:判断素数
  15. 15. 15. 训练:简单的乘法表
  16. 16. 16. 训练:简单绘画正方形
  17. 17. 17. 训练:简单绘画三角形
  18. 18. 18. 训练:金字塔数字
  19. 19. 19. 案例:进度条
  20. 20. 20. 案例:检查组件故障
  21. 21. 21. 使用VS进行debug调试
  22. 22. 22. 第五章结束语
© 2021-2025 青江的个人站
晋ICP备2024051277号-1
powered by Hexo & Yilia
  • 友链
  • 搜索文章 >>

tag:

  • 生日快乐🎂
  • 新年快乐!
  • 小技巧
  • Linux
  • 命令
  • 语录
  • 复刻
  • Blog
  • Notes
  • Android
  • C
  • Homework
  • MATLAB
  • FPGA
  • Server
  • Vivado

  • 引路人-稚晖
  • Bilibili-稚晖君
  • 超有趣讲师-Frank
  • Bilibili-Frank