1.7.1 循环结构与for语句 | Loop Structure and For Statement

发布于 2025-09-16  576 次阅读


循环结构与for语句 | Loop Structure and For Statement

一、回顾与引入

在上一章中,我们学习了分支结构和条件语句:

  • if语句:根据条件选择是否执行
  • if-else语句:根据条件选择执行不同的代码块
  • else-if语句:处理多重条件判断

通过条件语句,我们的程序已经能够根据不同情况做出不同的反应。但是,在实际编程中,我们经常需要重复执行某些操作。

想象以下场景:

  • 打印1到100的所有数字
  • 计算1+2+3+...+100的和
  • 输入100个学生的成绩并计算平均分
  • 重复询问用户输入直到输入正确

如果用我们之前学过的知识,我们可能需要写100行重复的代码!这显然不现实。

今天我们要学习循环结构,让程序能够高效地重复执行代码,就像工厂的流水线一样。

二、什么是循环结构?

循环结构就是让程序重复执行某段代码,直到满足某个条件为止

我们不妨用一个例子来理解循环的基本概念:

我如何连续喊出100次Hello, World!
程序如何连续输出100次Hello, World!\n

这两个问题本质上是一样的。我们先来看第一个问题:

要喊出100次Hello, World!,我们自然需要在内心默数。这个过程有这样几个关键要素:

  • 每次执行的动作(喊出"Hello, World!")
  • 内心的计数器(从1数到100,每执行一次动作就 +1)
  • 何时停止(数到100就停止)

编程中的循环概念很类似,只是由计算机来完成这个"默数"和"执行"的过程。

于是我们抽象出这样几个概念:

  • 循环体:每次循环,所需要执行的、具有重复性特征的语句。例如每次都输出"Hello, World!"
  • 循环变量:在这个例子里,我们在心里“默数”记录“反复喊出了多少次Hello, World!”,如果用一个变量来存储这个次数,那么它便代表“进行了多少次循环”,像这样与循环有关的变量我们称作循环变量。
  • 循环条件:在循环中,总是涉及到“何时停止”的问题。控制循环是否继续进行的条件是循环条件,在这个例子里,“循环次数<=100”就是循环条件。

三、for循环详解

C语言中有三种主要的循环语句:forwhiledo-while。我们先学习最常用的for循环。

1. for循环的基本语法

for (初始化; 条件判断; 更新操作) {
    // 循环体:需要重复执行的代码
}

语法说明:

  • 初始化:多数时候,在这里声明循环变量,并设置循环变量的初始值,只执行一次
  • 条件判断:每次循环前都会检查,为真继续循环,为假结束循环
  • 循环体:需要重复执行的代码块
  • 更新操作:每次循环结束后执行,通常用来更新循环变量

2. for循环的执行流程

我们仍然通过一个例子来理解for循环:

for (int i = 1; i <= 5; i++) {
    printf("Hello, World!");
}

让我们一步步看这个循环是如何执行的:

  1. 第一步:初始化 - int i = 1

    • 计算机创建一个变量i,赋值为1
    • 就像你准备开始数数,先在心里想"我从1开始数"
  2. 第二步:判断条件 - i <= 5

    • 计算机检查:i(现在是1) <= 5 吗?答案是"是"
    • 就像你问自己"我数到5了吗?"答案是"还没有"
  3. 第三步:执行循环体 - printf("Hello, World!");

    • 既然条件满足,就执行大括号里的代码
    • 就像你喊出"Hello, World!"
  4. 第四步:更新变量 - i++

    • 执行完循环体后,i增加1,现在i变成2
    • 就像你在心里数"2"
  5. 重复步骤2-4

    • 继续检查条件(2 <= 5),执行循环体,i变成3...
    • 直到i变成6时,条件(6 <= 5)为假,循环结束

整个过程就是:初始化→判断→执行→更新→判断→执行→更新...直到条件不满足。

3. 简单示例:打印数字

//Print_Numbers.c
#include <stdio.h>

int main() {
    printf("=== 打印1到10的数字 ===\n");

    for (int i = 1; i <= 10; i++) {
        printf("数字: %d\n", i);
    }

    printf("循环结束!\n");

    return 0;
}

运行结果:

=== 打印1到10的数字 ===
数字: 1
数字: 2
数字: 3
数字: 4
数字: 5
数字: 6
数字: 7
数字: 8
数字: 9
数字: 10
循环结束!

这个例子与上一个例子相较,有所不同的是:我们在循环体中使用到了循环变量i。这是很常见的做法,因为循环变量本身也是变量,和普通变量一样可以赋值、写入表达式等。例如这个例子的循环体中,printf("数字:%d\n", i);就在每次输出时打印了当前次循环的循环次数。

四、for循环的常见用法

1. 计算累加和

//Sum_Calculation.c
#include <stdio.h>

int main() {
    int sum = 0;

    printf("计算1+2+3+...+100的和\n");

    for (int i = 1; i <= 100; i++) {
        sum = sum + i;  // 也可以写成:sum += i;
    }

    printf("结果:1到100的和 = %d\n", sum);

    return 0;
}

2. 计算阶乘

//Factorial.c
#include <stdio.h>

int main() {
    int n;
    long long factorial = 1; 
    // long long是C中的另一种数据类型,用于存储整数。
    // 与int类型不同的是,long long可以存储的范围是
    // -2^63 ~ 2^63 - 1
    // 因此使用long long类型存储阶乘结果,防止“存不下”

    printf("请输入一个正整数: ");
    scanf("%d", &n);

    if (n < 0) {
        printf("错误:负数没有阶乘\n");
        return 1;
        // 还记得return么?
        // 记得return 0;认为规定了什么样的含义么?
        // 为什么这里return的返回值不是0?
    }

    for (int i = 1; i <= n; i++) {
        factorial = factorial * i;  // 也可以写成:factorial *= i;
        // n的阶乘 = n * (n-1) * ... * 2 * 1
        // 循环变量i从1开始,每次循环通过i++加1
        // 并让阶乘结果乘上i,最后算得n的阶乘
    }

    printf("%d! = %lld\n", n, factorial);

    return 0;
}

3. 灵活使用for循环

事实上,for语句对循环变量的控制是很灵活的:

  • 循环变量不一定初始化为1
  • 循环条件可以多样
  • 更新操作也不一定是每次循环一定是自增1,可以是自减1,甚至自加2等等。

例如这个例子中的倒序循环和下个例子中的步长不为1的循环等等。

倒序循环

//Countdown.c
#include <stdio.h>

int main() {
    printf("=== 倒计时程序 ===\n");

    for (int i = 10; i >= 1; i--) {
        printf("倒计时: %d\n", i);
    }

    printf("时间到!\n");

    return 0;
}

步长不为1的循环

//Step_Loop.c
#include <stdio.h>

int main() {
    printf("=== 打印所有偶数(2到20) ===\n");

    for (int i = 2; i <= 20; i += 2) {  // 每次增加2
        printf("偶数: %d\n", i);
    }

    printf("\n=== 打印所有奇数(1到19) ===\n");

    for (int i = 1; i <= 19; i += 2) {  // 每次增加2
        printf("奇数: %d\n", i);
    }

    return 0;
}

五、for循环的变体

1. 省略某些部分

还记得for循环的基本语法是:

for (初始化; 条件判断; 更新操作) {
    // ...
}

事实上,初始化条件判断更新操作这三条语句是可以省略不写的。表示分别在执行初始化、判断循环条件或执行更新操作时,不执行任何语句

值得说明的是,即使省略初始化条件判断更新操作用以分隔的分号;是不能省略的。分号提示了初始化条件判断更新操作三条语句的位置。

例如:

//Loop_Variants.c
#include <stdio.h>

int main() {
    int i;

    // 省略初始化部分
    // 这里我们在for循环外声明变量i,并初始化为1。因此,for循环的初始化环节留空了。
    i = 1;
    for (; i <= 5; i++) {
        printf("i = %d\n", i);
    }

    printf("\n");

    // 省略更新部分
    // for循环在执行完循环体后,会执行“更新操作”部分的语句。
    // 因此,我们亦可以把“更新操作”的语句放在循环体内的最后,
    // 并在for循环的“更新操作”处留空。
    // 例如:
    for (i = 1; i <= 5;) {
        printf("i = %d\n", i);
        i++;  // 在循环体内更新
    }
    // 尽管这样的写法是等价的,但代码的可读性变差,并不推荐。

    return 0;
}

2. 多个变量控制

//Multiple_Variables.c
#include <stdio.h>

int main() {
    printf("=== 同时控制多个变量 ===\n");

    for (int i = 1, j = 10; i <= 5; i++, j--) {
        printf("i = %d, j = %d, 和 = %d\n", i, j, i + j);
    }

    return 0;
}

运行结果:

=== 同时控制多个变量 ===
i = 1, j = 10, 和 = 11
i = 2, j = 9, 和 = 11
i = 3, j = 8, 和 = 11
i = 4, j = 7, 和 = 11
i = 5, j = 6, 和 = 11

六、嵌套循环

当我们需要处理二维的问题时,经常需要使用嵌套循环(循环中包含循环)。

1. 打印乘法表

//Multiplication_Table.c
#include <stdio.h>

int main() {
    printf("=== 九九乘法表 ===\n");

    for (int i = 1; i <= 9; i++) {          // 外层循环:行
        for (int j = 1; j <= i; j++) {      // 内层循环:列
            printf("%d×%d=%d\t", j, i, i*j);
        }
        printf("\n");  // 每行结束后换行
    }

    return 0;
}

运行结果:

=== 九九乘法表 ===
1×1=1
1×2=2   2×2=4
1×3=3   2×3=6   3×3=9
1×4=4   2×4=8   3×4=12  4×4=16
1×5=5   2×5=10  3×5=15  4×5=20  5×5=25
...

2. 打印图案

//Pattern_Printing.c
#include <stdio.h>

int main() {
    int rows = 5;

    printf("=== 打印星号三角形 ===\n");

    for (int i = 1; i <= rows; i++) {       // 控制行数
        // 打印空格
        for (int j = 1; j <= rows - i; j++) {
            printf(" ");
        }
        // 打印星号
        for (int k = 1; k <= 2*i - 1; k++) {
            printf("*");
        }
        printf("\n");
    }

    return 0;
}

运行结果:

=== 打印星号三角形 ===
    *
   ***
  *****
 *******
*********

七、常见错误与注意事项

1. 死循环

// ❌ 错误:忘记更新循环变量
for (int i = 1; i <= 10;) {    // 缺少 i++
    printf("%d\n", i);
    // i永远是1,条件永远为真,形成死循环
}

// ❌ 错误:条件永远为真
for (int i = 1; i >= 1; i++) {  // 条件错误
    printf("%d\n", i);
    // i会一直增长,条件永远满足
}

// ✅ 正确:
for (int i = 1; i <= 10; i++) {
    printf("%d\n", i);
}

2. 循环变量的作用域(极其重要!!)

//Variable_Scope.c
#include <stdio.h>

int main() {
    // 循环变量(例如这里的i)
    // 如果在for循环语句的“初始化”部分中声明,只能在for循环体内使用。
    // 你可以认为,像下面写法里的循环变量i,只“属于”for语句。
    for (int i = 1; i <= 5; i++) {
        printf("循环内: i = %d\n", i);
    }
    // ❌ 错误:i在此处不可访问
    printf("循环外: i = %d\n", i);  // 编译错误

    // 如果想在循环内外都可以使用,需要在循环外声明循环变量。
    // 你可以认为这样声明的循环变量j,属于主函数main()下的所有语句。
    // 因此循环内外都可以使用。
    // ✅ 正确:在循环外声明变量
    int j;
    for (j = 1; j <= 5; j++) {
        printf("循环内: j = %d\n", j);
    }
    printf("循环外: j = %d\n", j);  // j = 6

    return 0;
}

3. 循环次数的计算

// 常见的循环次数计算错误

// 这个循环执行几次?
for (int i = 0; i < 10; i++) {      // 执行10次:i从0到9
    // ...
}

for (int i = 1; i <= 10; i++) {     // 执行10次:i从1到10
    // ...
}

for (int i = 0; i <= 10; i++) {     // 执行11次:i从0到10
    // ...
}

for (int i = 1; i < 10; i++) {      // 执行9次:i从1到9
    // ...
}

八、实际应用:成绩统计程序

和之前的内容一样,我们仍然用一个小例子来复习一下本节内容。你能看懂每一条语句在执行什么样的功能么?

//Grade_Statistics.c
#include <stdio.h>

int main() {
    int n;
    float score, sum = 0, average;
    float max_score = 0, min_score = 100;
    int pass_count = 0;  // 及格人数

    printf("=== 学生成绩统计程序 ===\n");
    printf("请输入学生人数: ");
    scanf("%d", &n);

    if (n <= 0) {
        printf("学生人数必须大于0!\n");
        return 1;
    }

    printf("请依次输入%d个学生的成绩:\n", n);

    for (int i = 1; i <= n; i++) {
        printf("第%d个学生成绩: ", i);
        scanf("%f", &score);

        // 输入验证
        if (score < 0 || score > 100) {
            printf("成绩无效,请输入0-100之间的数字!\n");
            i--;  // 重新输入这个学生的成绩
            continue;
        }

        // 累加总分
        sum += score;

        // 更新最高分和最低分
        if (score > max_score) {
            max_score = score;
        }
        if (score < min_score) {
            min_score = score;
        }

        // 统计及格人数
        if (score >= 60) {
            pass_count++;
        }
    }

    // 计算平均分
    average = sum / n;

    // 输出统计结果
    printf("\n=== 统计结果 ===\n");
    printf("学生总数: %d人\n", n);
    printf("总分: %.2f\n", sum);
    printf("平均分: %.2f\n", average);
    printf("最高分: %.2f\n", max_score);
    printf("最低分: %.2f\n", min_score);
    printf("及格人数: %d人\n", pass_count);
    printf("及格率: %.2f%%\n", (float)pass_count / n * 100);

    return 0;
}

九、动手练习

练习:水仙花数寻找器

编写程序找出所有的三位水仙花数。水仙花数是指一个三位数,其各位数字立方和等于该数本身。
例如:153 = 1³ + 5³ + 3³ = 1 + 125 + 27 = 153

//参考答案
//Narcissistic_Numbers.c
#include <stdio.h>

int main() {
    printf("=== 三位水仙花数寻找器 ===\n");
    printf("水仙花数是指各位数字立方和等于该数本身的三位数\n\n");

    int count = 0;

    for (int num = 100; num <= 999; num++) {
        // 分解数字的各个位
        int hundreds = num / 100;        // 百位
        int tens = (num / 10) % 10;      // 十位
        int units = num % 10;            // 个位

        // 计算各位数字的立方和
        int sum = hundreds*hundreds*hundreds + 
                  tens*tens*tens + 
                  units*units*units;

        // 判断是否为水仙花数
        if (sum == num) {
            count++;
            printf("第%d个水仙花数: %d = %d³ + %d³ + %d³ = %d + %d + %d = %d\n", 
                   count, num, hundreds, tens, units,
                   hundreds*hundreds*hundreds,
                   tens*tens*tens,
                   units*units*units,
                   sum);
        }
    }

    printf("\n总共找到了%d个三位水仙花数。\n", count);

    return 0;
}

下一章预告: 学会了for循环后,我们将继续学习while循环和do-while循环,了解不同循环结构的特点和应用场景,让你的编程技能更加全面!


这里是 /* Huajidawang */ 的个人主页