循环结构

对应教学日历:讲次 8-10(4月9日-4月16日)


一、while 循环

while (表达式)     // 表达式为真时循环继续
{
    循环体;
}
  • 先判断,后执行:如果一开始条件就为假,循环体一次也不执行
  • 循环体内必须有使条件趋向于假的语句,否则是死循环

典型模式:未知循环次数

while (scanf("%d", &n) != EOF) { ... }     // 读到文件末尾
while ((ch = getchar()) != '\n') { ... }   // 读入一行
while (n != 0) { n = n / 10; count++; }    // 统计位数

二、do-while 循环

do
{
    循环体;
} while (表达式);    // 注意:这里有分号!
  • 先执行,后判断:循环体至少执行一次
  • while 后面有分号,不要漏掉

典型应用:至少执行一次的场景

do {
    printf("请输入正整数:");
    scanf("%d", &n);
} while (n <= 0);   // 用户输入非法时重新输入

三、for 循环

for (表达式1; 表达式2; 表达式3)
{
    循环体;
}
// 表达式1:初始化(只执行一次)
// 表达式2:条件判断(每次循环前判断)
// 表达式3:循环变量更新(每次循环后执行)

执行流程

① 初始化 → ② 判断条件(假则退出)→ ③ 执行循环体 → ④ 更新变量 → 回到②

for 与 while 的等价关系

for (i=0; i<n; i++)     ≈     i=0;
{                            while (i<n)
    循环体;                   {
}                                循环体;
                                 i++;
                             }

for 的特殊用法

for (;;) { ... }          // 无限循环(省略三个表达式)
for (i=0, j=0; i<10; i++, j+=2) { ... }  // 逗号表达式
for (; i<100; ) { ... i++; }   // 省略初始化和更新(等价于 while)

四、循环的嵌套

for (i = 0; i < n; i++)        // 外循环
    for (j = 0; j < m; j++)    // 内循环
    {
        // 内层每执行完一轮,外层才执行一次
    }

循环嵌套可以用于处理二维问题:九九乘法表、矩阵遍历、图形打印。

五、辅助控制语句

break

  • 跳出当前最近的一层循环或 switch
  • 常用于提前终止循环
for (i=0; i<100; i++)
{
    if (a[i] == target)
        break;    // 找到了,跳出循环
}

continue

  • 跳过本次循环的剩余语句,进入下一次循环迭代
  • 不跳出循环,只结束当前迭代
for (i=0; i<100; i++)
{
    if (a[i] < 0) continue;  // 跳过负数
    sum += a[i];
}

break vs continue

breakcontinue
作用范围循环 + switch仅循环
效果跳出循环跳过本次剩余,继续下一次

六、循环经典应用

1. 穷举法(枚举法)

在有限范围内逐一检查所有可能。

// 百钱买百鸡:公鸡5元,母鸡3元,小鸡1元3只,100元买100只
for (g=0; g<=20; g++)
    for (m=0; m<=33; m++)
    {
        x = 100 - g - m;
        if (5*g + 3*m + x/3.0 == 100)
            printf("公鸡%d 母鸡%d 小鸡%d\n", g, m, x);
    }

2. 迭代法(递推法)

用前一步结果推导下一步。

// 辗转相除法求最大公约数
while (n != 0)
{
    t = m % n;
    m = n;
    n = t;
}
// m 即为最大公约数

3. 累加累乘

sum = 0;                   // 累加初始化为 0
for (i=1; i<=100; i++)
    sum += i;
 
product = 1;               // 累乘初始化为 1
for (i=1; i<=n; i++)
    product *= i;          // n!

4. 有规律图形打印

// 打印金字塔
for (i=1; i<=n; i++)
{
    for (j=1; j<=n-i; j++) printf(" ");   // 空格
    for (j=1; j<=2*i-1; j++) printf("*"); // 星号
    printf("\n");
}

作业示例

ex7-2:while 循环——骑车还是走路

#include <stdio.h>
int main()
{
    int n;
    double dist;
    scanf("%d", &n);
    while (n--) {                     // n 次循环
        scanf("%lf", &dist);
        double bikeTime = 50.0 + dist / 3.0;
        double walkTime = dist / 1.2;
        if (bikeTime < walkTime) {
            printf("Bike\n");
        } else if (walkTime < bikeTime) {
            printf("Walk\n");
        } else {
            printf("All\n");
        }
    }
    return 0;
}

要点

  • while (n--) 循环 n 次(n 递减到 0 停止)
  • %lf 用于 double 的 scanf

ex7-4:while 循环——大小写转换

#include<stdio.h>
int main ()
{
    char ch;
    printf("请输入字符:\n");
    while((ch=getchar())!='#')
    {
        if(ch>='A'&&ch<='Z')
            ch=ch+32;
        else if(ch>='a'&&ch<='z')
            ch=ch-32;
        putchar(ch);
    }
}

要点

  • (ch=getchar())!='#' → 赋值表达式 ch=getchar() 的值是读到的字符
  • 循环直到输入 # 结束
  • 大小写转换:±32

ex7-6:while——统计正负数与平均值

#include<stdio.h>
int main ()
{
    int input,p,n;
    float average,sum=0;
    p=n=0;
    scanf("%d",&input);
    while(input!=0)
    {
        if(input>0) p++;
        if(input<0) n++;
        sum=sum+input;
        scanf("%d",&input);    // 循环体内继续读入
    }
    if(p+n==0)
        printf("No point.");
    else
    {
        average=sum/(p+n);
        printf("%d\n%d\n%.f\n%.2f",p,n,sum,average);
    }
    return 0;
}

要点

  • while(input!=0) 0 作为输入结束标志
  • 循环体内 scanf 读取下一个数,否则死循环
  • 注意除零判断:p+n==0

ex8-1:do-while 猜数游戏(含次数限制)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() 
{
    int n, rand_num, guess;
    int count = 0;      
    scanf("%d", &n);
    srand((unsigned int)time(NULL)); 
    rand_num = rand() % 100 + 1;     
 
    while (1) {
        scanf("%d", &guess);         
        if (guess < 0) {             // 输入负数退出
            printf("Game Over\n");
            break;
        }
 
        count++; 
        if (count > n) {             // 超次数
            printf("Game Over\n");
            break;
        }
        if (guess > rand_num) {
            printf("Too big\n");
        } else if (guess < rand_num) {
            printf("Too small\n");
        } else {
            if (count == 1) printf("Bingo!\n");
            else if (count <= 3) printf("Lucky You!\n");
            else printf("Good Guess!\n");
            break; 
        }
    }
    return 0;
}

要点

  • while(1) + break 实现灵活退出
  • 多层判断:负数退出、超次数退出、猜对退出
  • break 跳出当前最近 while

ex8-2:辗转相除法求最大公约数

#include <stdio.h>
int main() {
    int m, n, t, a, b;
    scanf("%d %d", &m, &n);
    a = m; b = n;
    if (m < n) {           // 确保 m >= n
        t = m; m = n; n = t;
    }
    while (n != 0) {       // 辗转相除核心
        t = m % n;
        m = n;
        n = t;
    }
    printf("最大公约数为:%d\n", m);
    printf("最小公倍数为:%d\n", a * b / m);
    return 0;
}

要点

  • 辗转相除法:重复用 m % n 直到余数为 0,最后的除数即最大公约数
  • 最小公倍数 = 两数之积 / 最大公约数

ex8-3:嵌套 for——三角九九乘法表

#include <stdio.h>
int main() 
{
    for (int i = 1; i <= 9; i++) {        
        for (int j = 1; j <= 9; j++) {
            if (j < i) {
                printf("    ");     // 空白填充
            } else { 
                printf("%4d",i*j);
            }
        }
        printf("\n");
    }
    return 0;
}

要点

  • 双重循环:外层控制行,内层控制列
  • %4d 使每个输出占 4 格宽度,对齐整齐

ex8-4:完数查找(嵌套 for)

#include <stdio.h>
int main() 
{
    int i, j, sum; 
    for (i = 1; i <= 1000; i++) {
        sum = 0; 
        for (j = 1; j < i; j++) {
            if (i % j == 0) {
                sum = sum + j;    // 累加真因子
            }
        }
        if (sum == i) {
            printf("%d\n", i);    // 完数:因子和等于自身
        }
    }
    return 0;
}

要点

  • 完数定义:所有真因子之和等于本身的数(如 6 = 1+2+3,28 = 1+2+4+7+14)
  • sum 每次外循环必须重置为 0
  • 内循环查找 i 的所有因子

ex8-5:素数判断与打印(嵌套 for + break)

#include <stdio.h>
 
int main() {
    int count = 0;
    for (int num = 3; num <= 300; num++) {
        int isPrime = 1;
        for (int i = 2; i * i <= num; i++) {  // 只需检查到 sqrt(num)
            if (num % i == 0) {
                isPrime = 0;
                break;
            }
        }
        
        if (isPrime) {
            printf("%4d ", num);
            count++;
            if (count % 10 == 0) printf("\n");  // 每行 10 个
        }
    }
    printf("\n");
    return 0;
}

要点

  • 素数判断优化:只需检查到 i * i <= num(等价于 i <= sqrt(num)
  • break 一旦发现因子立即跳出
  • count % 10 == 0 控制每 10 个换行

作业重点

  • while:先判断后执行,可能一次都不执行
  • do-while:先执行后判断,至少执行一次(注意末尾分号)
  • for:最常用的已知次数循环,for(初始化; 条件; 更新)
  • 循环嵌套:外层每执行一次,内层执行所有轮次;总执行次数 = 外层次数 × 内层次数
  • break 跳出当前循环;continue 跳过本次剩余语句
  • 经典算法必须掌握:辗转相除法、素数判断、完数查找、九九乘法表
  • 循环体内要有使条件趋近于假的语句,否则死循环
  • 累加变量初始化为 0,累乘变量初始化为 1