第十一节 循环(二)

对while循环的一些补充

while循环是一种入口条件循环。这就是说,程序在每次执行循环时都会先行检验条件表达式的真假。以此来决定是否执行循环。我们在编写循环时一定要使条件表达式的值有变化,并且最终变成0,也就是使得它为假。否则,程序将无法退出循环。造成无限的输出。

同时,我们要对“循环退出的时机”有一个基本的认识。请看以下程序:

//when_to_quit.c 演示循环退出的时机
#include <stdio.h>
int main(void)
{
    int num = 5;
    while (num < 7) {
        num++;
        printf("num = %d\n", num);
    }
    printf("程序结束。");
    return 0;
}

运行结果如下:

num = 6
num = 7
程序结束。

我们注意到,虽然程序在进行第二次循环时,num已经达到了7,但是循环仍在继续。循环的条件判断表达式是在上一次循环结束之后才进行验证的。

我们强烈建议在使用循环时把循环体单独用花括号括起来(哪怕它只有一句),因为while循环在不带花括号的时候,会默认循环执行下一条语句。例如:

//wrong_round.c 演示不使用花括号造成的后果
#include <stdio.h>
int main(void)
{
    int num = 5;
    while (num < 7)
        num++;
    printf("num = %d\n", num);    
    printf("程序结束。");
    return 0;
}

运行结果如下:

num = 7
程序结束。

程序并不打印num = 6这句话,因为我们并未将打印语句也包括在循环体内。

对for循环的一些补充

我们已经知道,for循环是一种计数循环。也就是说,我们在使用这个循环之前,就已经知道了它应该被循环的次数。作为一个计数循环,它必须要做到以下几点:

  1. 计数器要有一个初始值(必须进行初始化);
  2. 计数的值应该与有限的值作比较;
  3. 每次循环结束后,计数的值应该有所改变。

for语句的不同用法

显然,for语句的灵活性使得它可以有多种用法。事实上,for语句的初始化表达式与变量调整表达式都可以不止一个,而且可以是任何合法的表达式。下面我们来演示for语句的几种不同用法。

使用递减运算符
for(i = 10; i <= 0; i--)

代表这个循环将会执行十次,直到循环变量i的值小于等于0为止。

使用不同的测试条件
for(num = 0; num*num < 36; num++)

代表这个循环以num的平方值为限制,这在一些场合很有用(例如寻找平方根的近似值)。

使用任意合法的变量调整表达式
for(x = 1; y <= 75; y = (++x * 5) + 50)

这个循环的三个表达式使用了不同的变量,而且仍然是合法的。我们还注意到,调整变量的表达式和通常的递增不同。这也是合法的。

使用不同的初始化表达式
i = 0;
for(printf("Look!"); i < 10; i++)

for语句的第一个表达式只会执行一次,而且不一定是初始化变量表达式。

省略表达式
for(; num <= 10; )

这个for语句省略了第一和第三个表达式,但是它仍然是合法的。我们回顾这两个表达式的内容(初始化和调整),会发现它们即使写在for语句以外也有用。在这种情况下,for语句可以视作一个while语句。

使用逗号运算符
for(i = 0, j = 10; i <= 10; i++, j--)

在循环进行的同时,变量j的值也会同时变化。这里使用了多个表达式。而它们仍然是合法的。

特别要说明的是:我们在这里使用了一个新的运算符:逗号运算符。在这里,初始表达式中的逗号运算符使这两个变量都进行了初始化。

逗号计算符的意义是:保证表达式从左往右求值。逗号左侧的内容与副作用都在逗号右侧之前发生。这在一些强调顺序的场合非常有用。例如:

//comma.c 演示逗号运算符
#include <stdio.h>
#define YEAR_RATE 0.036
int main(void)
{
    float principal;
    int years, i;
    printf("输入本金:");
    scanf("%f", &principal);
    printf("输入存款年限:");
    scanf("%d", &years);

    for (i = 0; i <= years; principal *=(1 + YEAR_RATE), i++)
        printf("%d年后,存款为%.2f元。\n", i, principal);

    return 0;
}

运行结果如下:

输入本金:10000
输入存款年限:3
0年后,存款为10000.00元。
1年后,存款为10360.00元。
2年后,存款为10732.96元。
3年后,存款为11119.35元。

do_while循环

while循环和for循环都是入口条件循环。这两种循环在循环执行之前会先判断条件表达式的真假。如果条件表达式一开始就为假,那循环体就一次也不会执行。但是有时,我们需要保证循环至少进行一次(比如在回合制游戏中,至少要先进行第一个回合)。这时,我们就要使用do_while循环。do_while循环是出口条件循环。它会在每次循环迭代之后再进行条件判断。

以下是一个使用do_while循环的例子:

//do_while.c 演示出口条件循环
#include <stdio.h>
#define SECRET_NUM 65
int main(void)
{
    int input_num;
    printf("现在我们来玩一个猜数字的小游戏。\n");
    
    do{
        printf("输入你猜的数字吧!");
        scanf("%d", &input_num);
    }
    while(input_num != SECRET_NUM)
        printf("祝贺你!你猜到了!");

    return 0;
}

运行结果如下:

现在我们来玩一个猜数字的小游戏。
输入你猜的数字吧!
1
输入你猜的数字吧!
2
输入你猜的数字吧!
3
输入你猜的数字吧!
65
祝贺你!你猜到了!

我们在之后会学习选择分支语句,使得这个游戏更加生动。

do_while循环的格式如下:

do
{
    循环体
}
while(条件表达式);

需要注意的是:while语句需要以分号结尾。另外,你需要避免这种形式:

do{
    询问用户是否要进行
        要进行的循环体
}
while(用户回答“是”)

这会导致用户在回答“否”时,程序仍然运行一次循环体。

do_while循环会在第一次循环结束后判断条件表达式,以确认循环是否要继续进行。循环提部分至少会执行一次。

(扩展:如何选择循环)

首先确定需要入口条件循环还是出口条件循环(通常使用入口条件循环);

其次确定你是否知道循环的次数,如果知道,建议用for循环;如果不知道,就建议用while循环。

嵌套循环

接下来,我们将介绍一种十分关键的循环用法:嵌套循环

有时。我们希望计算机不止能输出指定数目的行,还能输出指定数目的列。这时候就需要运用到嵌套循环。顾名思义:嵌套循环是在一个循环里还包含着另一个循环。外层循环用来控制行数,内层循环用来控制每行的字数(或者称为列数)。

我们看以下的例子:

//rowsl.c 演示嵌套循环
#include <stdio.h>
int main(void)
{
    int i, num_of_row, num_of_column;
    char ch;

    printf("输入行数:");
    scanf("%d", &num_of_row);
    printf("输入列数:");
    scanf("%d", &num_of_column);
  

    for (i = 0; i < num_of_row; i++) {
        for (ch = 'A'; ch < ('A' + num_of_column); ch++) {
            printf("%c", ch);
        }
        printf("\n");
    }

    return 0;
}

试运行:

输入行数:3
输入列数:4
ABCD
ABCD
ABCD

重新运行程序:

输入行数:4
输入列数:3
ABC
ABC
ABC
ABC

让我们来详细解释这个嵌套循环。首先观察它的外层循环:

for(i = 0; i < num_of_row; i++){}

这个语句含义是明确的:当i小于用户给出的行数时,循环将继续。

接下来来看循环体:

for (ch = 'A'; ch < ('A' + num_of_column); ch++) {
            printf("%c", ch);
        }
printf("\n");

这个循环体由一个内层循环和换行符组成。先来看这个内层循环。内容是打印从“A”到用户给定的列数代表的字母。循环结束后,打印一个换行符来实现换行。关键在于:外部循环仍在继续(因为外层条件表达式仍然为假)。所以在内层循环结束后,会又重新进行一次。即在下一行又打印相同的字母。外层循环每进行一次,内层循环就会完整进行一次。对应到打印上,就是行数与每行打印的字符个数。

嵌套循环的应用

嵌套循环有着广泛的应用。比如:

//pyramid.c 打印指定层数的金字塔
#include <stdio.h>
int main() 
{
    int rows;
    printf("请输入金字塔的层数: ");
    scanf("%d", &rows);

    for (int i = 1; i <= rows; i++) {
        for (int j = 1; j <= rows - 1; j++) {
            printf(" ");// 打印空格
        }
        for (int k = 1; k <= 2 * i - 1; k++) {
            printf("@");//打印“@”
        }
        printf("\n");
    }
    
    return 0;
}   

我们试运行这个程序:

请输入金字塔的层数: 5
@
@@@
@@@@@
@@@@@@@
@@@@@@@@@

可以看到:程序输出了一个“金字塔”(等宽字体下)。我们来解释这个程序。(考试中可能会出现要求输出这种类型图案的式子)

首先:要输出这样的正三角形,就要同时输出“空格”与“@”。首先注意到:每一行都由单数个“@”组成。所以控制“@”个数的循环应该是单数的递增。比如说k <= 2 * i - 1 。我们还知道:程序是从左往右打印的。所以我们首先打印空格,并且在打印完空格之后就打印“@”,右侧的空隙则会自然留下.如果把空格替换成“□”,显示的效果会更加明了:

□□□□@
□□□@@@
□□@@@@@
□@@@@@@@
@@@@@@@@@

打印空格的个数逐一减少,并且在最后一行没有空格*(打印空格的循环不执行)*。这意味着我们需要让循环变量的数值比用户给出的层数小1,而且逐层递减。所以有 for (int j = 1; j <= rows - 1; j++)

我们在外层循环中已经用i指代了当前打印光标所在的层数。观察金字塔,可以发现@的数量按1,3,5……排列,每一行正好是2i-1个,综合以上信息,我们就可以得出for (int k = 1; k <= 2 * i - 1; k++)

把这两个循环按照上下顺序都放在外层循环里,再在外层循环里加上换行符。就实现了打印金字塔的场景。

利用嵌套循环,我们还能打印九九乘法表:

//multiplication_table.c 打印九九乘法表
#include <stdio.h>
int main() 
{
    int i, j;
    for (i = 1; i <= 9; i++) {
        for (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
1 × 6 = 6 2 × 6 = 12 3 × 6 = 18 4 × 6 = 24 5 × 6 = 30 6 × 6 = 36
1 × 7 = 7 2 × 7 = 14 3 × 7 = 21 4 × 7 = 28 5 × 7 = 35 6 × 7 = 42 7 × 7 = 49
1 × 8 = 8 2 × 8 = 16 3 × 8 = 24 4 × 8 = 32 5 × 8 = 40 6 × 8 = 48 7 × 8 = 56 8 × 8 = 64
1 × 9 = 9 2 × 9 = 18 3 × 9 = 27 4 × 9 = 36 5 × 9 = 45 6 × 9 = 54 7 × 9 = 63 8 × 9 = 72 9 × 9 = 81

这里最关键的部分在于 for (j = 1; j <= i; j++),它使得每次输出的最大的j值(也就是第一个乘数)始终少于i(也就是行数)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值