【第六章·循环控制结构】第五节:流程的转移控制

目录

[goto 语句](#goto 语句)

[break 语句](#break 语句)

[示例:使用 goto 和 break 实现读入正整数程序,遇负数终止](#示例:使用 goto 和 break 实现读入正整数程序,遇负数终止)

[用 goto 语句编程实现](#用 goto 语句编程实现)

[用 break 语句编程实现](#用 break 语句编程实现)

[break 语句与 goto 语句的区别](#break 语句与 goto 语句的区别)

[continue 语句](#continue 语句)

[break 语句与 continue 语句的区别](#break 语句与 continue 语句的区别)

[示例:使用 contiune 实现读入正整数程序,遇负数跳过](#示例:使用 contiune 实现读入正整数程序,遇负数跳过)

[continue 对 for 转 while 循环的影响](#continue 对 for 转 while 循环的影响)

跳出多重循环

[使用 goto 语句跳出多重循环](#使用 goto 语句跳出多重循环)

[使用 break 语句跳出多重循环](#使用 break 语句跳出多重循环)

对比总结

穷举法编程实例

示例:韩信点兵问题

问题求解方法分析

简单的程序实现

[使用 break 优化程序](#使用 break 优化程序)

[使用 exit() 函数优化程序](#使用 exit() 函数优化程序)

使用标志变量优化程序

[使用 do-while 语句重写程序](#使用 do-while 语句重写程序)


goto 语句

goto 语句break 语句continue 语句return 语句是 C 语言中用于控制流程转移的跳转语句。其中,控制从函数返回值的 return 语句将在第 7 章介绍。

goto 语句为无条件转向语句,它既可以向下跳转,也可往回跳转。其一般形式为:

它的作用是在不需要任何条件的情况下直接使程序跳转到该语句标号(Label)所标识的语句去执行。

其中语句标号代表 goto 语句转向的目标位置,应使用合法的标识符表示语句标号,其命名规则与变量名相同

尽管 goto 语句是无条件转向语句,但通常情况下 goto 语句与 if 语句联合使用。其形式为:

良好的编程风格建议少用和慎用 goto 语句,尤其是不要使用往回跳转的 goto 语句,不要让 goto 制造出永远不会被执行的代码(即死代码)。


break 语句

break 语句除用于退出 switch 结构外,还可用于由 while、do-while 和 for 构成的循环语句的循环体中。

当执行循环体遇到 break 语句时,循环将立即终止 ,从循环语句后的第一条语句开始继续执行**【结束本次循环】**。

break 语句对循环执行过程的影响示意如下:

可见,break 语句实际是一种有条件的跳转语句,跳转的语句位置限定为紧接着循环语句后的第一条语句。若希望跳转的位置就是循环语句后的语句,则可以用 break 语句代替 goto 语句。

示例:使用 goto 和 break 实现读入正整数程序,遇负数终止

【例 6.11】读入 5 个正整数并且显示它们。当读入的数据为负数时,程序立即终止。

这个程序既可用 goto 语句来编程,也可用 break 语句来编程。

用 goto 语句编程实现

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int i, n; // 声明循环计数器 i 和用于存储用户输入的整数 n

    // 使用 for 循环来尝试读取用户输入的 5 个正整数
    for (i = 1; i <= 5; i++)
    {
        printf("Please enter n: ");
        scanf("%d", &n);

        // 如果输入的整数是负数,则跳转到 END 标签处执行代码
        if (n < 0)
            goto END;

        // 如果输入的整数是非负数,则打印出来
        printf("n = %d\n", n);
    }

    // END标签:这里是 goto 语句跳转到的位置
    // 无论循环是否因为遇到负数而跳出,都会执行到这里
END:
    printf("Program is over!\n");

    return 0;
}

程序的 2 次测试结果如下:

用 break 语句编程实现

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int i, n; // 声明循环计数器 i 和用于存储用户输入的变量 n

    // 使用 for 循环尝试读取 5 次用户输入
    for (i = 1; i <= 5; i++)
    {
        printf("Please enter n: ");
        scanf("%d", &n);

        // 检查 n 是否为负数
        if (n < 0)
            // 如果是负数,则跳出循环
            break;

        // 如果 n 是非负数,则打印出来
        printf("n = %d\n", n);
    }

    // 无论循环是否因为达到 5 次或遇到负数而结束,都打印程序结束信息
    printf("Program is over!\n");

    return 0;
}

程序的 2 次测试结果如下:

break 语句与 goto 语句的区别

虽然 break 语句与 goto 语句都可用于终止整个循环的执行,但二者的本质区别在于:

  • goto 语句可以向任意方向跳转,可以控制流程跳转到程序中任意指定的语句位置;
  • break 语句只限定流程跳转到循环语句之后的第一条语句去执行,无须像 goto 语句那样用语句标号指示跳转的语句位置,因此也就避免了因过多使用 goto 语句标号使流程随意跳转而导致的程序流程混乱的问题。

continue 语句

continue 语句与 break 语句都可用于对循环进行内部控制,但二者对流程的控制效果是不同的。

当在循环体中遇到 continue 语句时,程序将跳过 continue 语句后面尚未执行的语句,开始下一次循环,即只结束本次循环的执行,并不终止整个循环的执行【跳过本次循环】

continue 语句对循环执行过程的影响示意如下:

break 语句与 continue 语句的区别

break 语句和 continue 语句在流程控制上的区别可从图 6-8 和图 6-9 的对比中略见一斑。

break 用于立即终止当前的循环(for、while、do-while)或 switch 语句,并跳出循环体,执行循环之后的代码。

continue 用于跳过当前循环的剩余部分,并立即开始下一次循环迭代。它不会终止整个循环,只是跳过当前迭代中 continue 之后的代码。

示例:使用 contiune 实现读入正整数程序,遇负数跳过

**【例 6.12】**用 continue 语句代替例 6.11 程序中的 break 语句,重新编写和运行程序,分析程序功能有什么变化,进而对 continue 与 break 语句进行对比。

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int i, n; // 声明循环计数器 i 和用于存储用户输入的变量 n

    // 使用 for 循环尝试读取 5 次用户输入
    for (i = 1; i <= 5; i++)
    {
        printf("Please enter n: ");
        scanf("%d", &n);

        // 检查 n 是否为负数
        if (n < 0)
            // 如果是负数,则跳过本次循环
            // 跳过 continue 语句后面尚未执行的语句,开始下一次循环
            continue;

        // 如果 n 是非负数,则打印出来
        // 如果 n 是负数,这句代码不会被执行
        printf("n = %d\n", n);
    }

    // 无论循环是否因为达到 5 次或遇到负数而结束,都打印程序结束信息
    printf("Program is over!\n");

    return 0;
}

程序的运行结果如下所示:

从上述测试结果可以看出:当程序读入正数时,显示该数;而当程序读入负数时,程序不显示该数,继续等待用户输入下一个数,直到读完 5 个数据为止。

continue 对 for 转 while 循环的影响

大多数 for 循环可以转换为 while 循环,但并非全部 。例如,当循环体中有 continue 语句时,二者就并非是等价的。

**【思考题】**如果将例 6.12 程序中的 for 语句用 while 语句代替,那么程序的输出结果会和原来一样吗?

如果将例 6.12 程序中的 for 循环用 while 循环代替,并且不正确地处理循环计数器的更新,那么程序的输出结果将会与原来的不同。关键在于 for 循环中自动处理了循环计数器 i 的初始化和更新(i = 1 和 i++),而在 while 循环中,需要手动管理这些操作,并需特别注意 continue 语句可能导致的循环计数器更新被跳过的情况

下面是一个尝试将 for 循环转换为 while 循环但不正确的例子,由于缺少对循环变量 i 的更新,它会导致循环无法结束:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int i = 1, n; // 声明并初始化循环计数器 i 和用于存储用户输入的变量 n

    // 使用 while 循环尝试读取用户输入
    while (i <= 5) // 正确的循环条件
    {
        printf("Please enter n: ");
        scanf("%d", &n);

        // 检查 n 是否为负数
        if (n < 0)
            // 如果是负数,则跳过本次循环的剩余部分
            continue;

        // 如果 n 是非负数,则打印出来
        printf("n = %d\n", n);

        // 缺少对循环变量 i 的更新!这将导致循环无法结束!
    }

    // 这条消息永远不会被打印,因为上面的循环无法结束
    printf("Program is over!\n");

    return 0;
}

程序的运行结果如下所示:

现在我们在加上对循环变量 i 的更新,修改程序如下所示:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int i = 1, n; // 声明并初始化循环计数器 i 和用于存储用户输入的变量 n

    // 使用 while 循环尝试读取用户输入
    while (i <= 5) // 正确的循环条件
    {
        printf("Please enter n: ");
        scanf("%d", &n);

        // 检查 n 是否为负数
        if (n < 0)
            // 如果是负数,则跳过本次循环的剩余部分
            continue;

        // 如果 n 是非负数,则打印出来
        printf("n = %d\n", n);

        // 更新循环计数器 i
        // 但是,需要注意的是它被放在了 continue 后面,
        // 如果用户输入了负数,此处的循环计数器 i 会被跳过,不会执行,即不会计数
        i++;
    }

    printf("Program is over!\n");

    return 0;
}

程序的 2 次测试结果如下:

上述修改后的 while 循环版本中,i 的递增操作被放在了 continue 语句之后。这种情况下,如果用户输入了负数,continue 语句会被执行,导致 i 的递增操作被跳过,从而使循环条件 i <= 5 永远不满足,形成无限循环。

为了避免无限循环,需要确保 i 的递增操作在 continue 语句之前执行。这样无论用户输入什么值,i 都会被正确递增,从而保证循环能够正常结束。

再次进行修改程序,如下所示:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int i = 1, n; // 声明并初始化循环计数器 i 和用于存储用户输入的变量 n

    // 使用 while 循环尝试读取用户输入
    while (i <= 5) // 正确的循环条件
    {
        printf("Please enter n: ");
        scanf("%d", &n);

        // 检查 n 是否为负数
        if (n < 0)
        {
            // 如果是负数,则跳过本次循环的剩余部分
            i++; // 在 continue 之前递增 i,非常重要!!!
            continue;
        }

        // 如果 n 是非负数,则打印出来
        printf("n = %d\n", n);

        // 更新循环计数器 i
        i++; // 不要忘了,n 为非负数时,也需要递增 i
    }

    printf("Program is over!\n");

    return 0;
}

程序的运行结果如下所示:


跳出多重循环

注意,在嵌套循环的情况下,break 语句和 continue 语句只对包含它们的最内层的循环语句起作用,不能用 break 语句跳出多重循环。若要跳出多重循环,使用 break 语句只能一层一层地跳出。而显然 goto 语句是跳出多重循环的一条捷径。

在程序设计语言中保留 goto 语句的主要原因是在某些情况下,使用 goto 语句可以提高程序的执行效率,或者使程序结构更清晰。如下两种情形特别适合于使用 goto 语句:

  • 快速跳出多重循环。
  • 跳向共同的出口位置,进行退出前的错误处理工作。

使用 goto 语句跳出多重循环

下面是一个简单的示例,演示如何使用 goto 语句来快速跳出多重循环,并进行退出前的错误处理工作。

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int outer, inner, value;

    // 外层循环
    for (outer = 1; outer <= 3; outer++)
    {
        // 内层循环
        for (inner = 1; inner <= 3; inner++)
        {
            printf("请输入一个数字(负数退出): ");
            scanf("%d", &value);

            // 如果输入的是负数,使用 goto 语句跳出所有循环
            if (value < 0)
            {
                goto exit_loops;
            }

            // 处理输入的值
            printf("正在处理数值: %d\n", value);
        }
    }

exit_loops:
    // 共同的出口位置,进行退出前的错误处理工作
    printf("由于输入负数,退出所有循环。\n");

    // 进行一些清理工作
    printf("正在进行清理工作...\n");

    return 0;
}

程序的运行结果如下所示:

使用 break 语句跳出多重循环

下面是一个简单的示例,演示如何使用 break 语句来跳出多重循环。

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int outer, inner, value;
    int should_exit = 0; // 用于控制跳出所有循环的标志变量

    // 外层循环
    for (outer = 1; outer <= 3; outer++)
    {
        // 如果 should_exit 为 1,则跳出外层循环
        if (should_exit)
        {
            break; // 最近的封闭循环------这里是外层循环
        }

        // 内层循环
        for (inner = 1; inner <= 3; inner++)
        {
            printf("请输入一个数字(负数退出): ");
            scanf("%d", &value);

            // 如果输入的是负数,设置 should_exit 为 1 并跳出内层循环
            if (value < 0)
            {
                should_exit = 1;
                break;  // 只能跳出最近的封闭循环------这里是内层循环
            }

            // 处理输入的值
            printf("正在处理数值: %d\n", value);
        }
    }

    // 共同的出口位置,进行退出前的错误处理工作
    printf("由于输入负数,退出所有循环。\n");

    // 进行一些清理工作
    printf("正在进行清理工作...\n");

    return 0;
}

程序的运行结果如下所示:

对比总结

使用 goto 语句:可以直接跳出多重嵌套的循环,代码简洁,但可能会降低代码的可读性。

使用 break 语句 :需要在每一层循环中设置标志变量,并在外层循环中检查该标志变量,代码相对冗长,但可读性较好。


穷举法编程实例

示例:韩信点兵问题

**【例 6.13】**韩信点兵问题。韩信有一队兵,他想知道有多少人,便让士兵排队报数。按从 1 至 5 报数,最末一个士兵报的数为 1;按从 1 至 6 报数,最末一个士兵报的数为 5;按从 1 至 7 报数,最末一个士兵报的数为 4;最后再按从 1 至 11 报数,最末一个士兵报的数为 10。请编程计算韩信至少有多少兵?

问题求解方法分析

设兵数为 x,则按题意 x 应满足下述关系式:

cpp 复制代码
x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10

采用穷举法对 x 从 1 开始逐个试验,第一个使得上述关系式成立的 x 值即为所求。

所谓穷举(Exhaustion),简单地说就是通过尝试问题的所有可能来得到最终答案。

简单的程序实现

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int x;

    // 从 1 -3000 开始穷举遍历,不严谨
    for (x = 1; x < 3000; x++)
    {
        if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10)
        {
            printf("x = %d\n", x);
        }
    }

    return 0;
}

程序的运行结果如下所示:

虽然这个程序似乎也得到了正确的结果,但这实属偶然,算是"瞎猫碰到了死耗子",因为程序只从 1 试验到 3000,如果真正的解不在这个范围内,那么程序运行以后将一无所获。

使用 break 优化程序

如果去掉 x < 3000 的限制,又会怎样呢?

显然一定会找到解,而且不止一个解,更严重的是程序将陷入死循环,即循环永远都不会退出。

那么如何让循环在找到第一个满足关系式的解后立即退出循环呢?

显然,可以使用 break 语句,程序如下:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int x;

    for (x = 1;; x++) // 去掉 x < 3000 的限制
    {
        if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10)
        {
            printf("x = %d\n", x);
            break; // 找到第一个满足关系式的解后立即退出循环
        }
    }

    return 0;
}

使用 exit() 函数优化程序

由于本例中,程序退出循环以后什么也不做,直接结束程序的运行,因此还可以调用函数 **exit()**来直接结束程序的运行。程序如下:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h> // 使用 exit() 函数

int main(void)
{
    int x;

    for (x = 1;; x++) // 去掉 x < 3000 的限制
    {
        if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10)
        {
            printf("x = %d\n", x);
            exit(0); // 找到第一个满足关系式的解后立即退出循环
        }
    }

    return 0;
}

使用标志变量优化程序

可读性更好的方法是使用标志变量,即定义一个标志变量 find,标志是否找到了解,先置 find 为假,表示 "未找到",一旦找到了满足给定条件的解,就将 find 置为真,表示 "找到了"。

相应的,循环控制表达式取为 "find 的逻辑非" 的值,当 find 值为 0(假)时,即 !find 的值为真,表示未找到,继续循环,否则表示已找到,退出循环。程序如下:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int x;
    int find = 0; // 先置 find 为假,表示 "未找到"

    // find 为假时继续循环
    for (x = 1; !find; x++)
    {
        if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10)
        {
            printf("x = %d\n", x);
            find = 1; // 一旦找到了满足给定条件的解,就将 find 置为真
        }
    }

    return 0;
}

**【思考题】**第 13 行输出 x 值的语句放到 for 循环体外,还能输出正确的结果吗?

如果将第 13 行输出 x 值的语句放到 for 循环体外,程序将无法正确输出结果。这是因为 printf 语句需要在找到符合条件的 x 值时立即执行,而不是等到循环结束后再执行。如果将 printf 语句移到循环体外,程序会在循环结束后只打印一次 x 的值,此时 x 的值将是循环结束时的值,而不是满足条件的那个值。因此,输出的结果将是不正确的。

例如,将 printf 语句移到循环体外,修改代码如下:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int x;
    int find = 0; // 先置 find 为假,表示 "未找到"

    // find 为假时继续循环
    for (x = 1; !find; x++)
    {
        if (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10)
        {
            find = 1; // 一旦找到了满足给定条件的解,就将 find 置为真
        }
    }

    printf("x = %d\n", x); // 输出 x 值
    // 这个值将是正确值 + 1 后的结果
    // 因为上面找到这个数后,又对循环变量 x 进行了 ++ 操作,然后才结束循环

    return 0;
}

程序的运行结果如下所示:

结果分析:

  • 原始代码:当 x 为 2111 时,printf 语句立即执行,输出 x = 2111,然后退出循环。
  • 修改后的代码:当 x 为 2111 时,find 被置为真(1),再次执行 x++,此时 x 为 2112,由于循环条件不再满足,所以循环退出。此时 x 的值已经不再是 2111,而是循环结束时的值 2112。

使用 do-while 语句重写程序

上面这个程序还可以用 do-while 语句来重写,使程序更简洁,可读性更好。

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int x = 0;    // 因 do-while 循环中先对 x 加 1,故这里 x 初始化为 0
    int find = 0; // 标志变量初值置为假

    do
    {
        x++;
        find = (x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10);
    } while (!find);

    printf("x = %d\n", x);

    return 0;
}

程序第 11 行语句根据逻辑表达式 x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10 的值为真还是为假,相应地将标志变量 find 置为真或假。

当然,上述程序也可以写成如下形式:

cpp 复制代码
#include <stdio.h>

int main(void)
{
    int x = 0; // 因 do-while 循环中先对 x 加 1,故这里 x 初始化为 0

    do
    {
        x++;
    } while (!(x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10));

    printf("x = %d\n", x);
    
    return 0;
}

在这个程序中,我们没有使用额外的标志变量,而是直接在 while 循环的条件中检查了逻辑表达式。这里,!(x % 5 == 1 && x % 6 == 5 && x % 7 == 4 && x % 11 == 10) 相当于第一个程序中的 !find。当 x 不满足条件时,逻辑表达式的结果为 true(非零值),因此循环继续。一旦 x 满足条件,逻辑表达式的结果变为 false(零值),循环终止。

两种方法的原理都是相同的:它们都通过循环不断增加 x 的值,直到找到一个满足特定条件的 x。

  • 第一个程序使用了一个额外的标志变量 find 来控制循环的结束;
  • 第二个程序则直接在循环条件中检查了该条件。

从代码简洁性和可读性的角度来看,第二个程序更直接和简洁,因为它避免了引入额外的变量。此外,它使循环的逻辑更加清晰,因为循环的继续和终止条件直接体现在 while 语句中。

相关推荐
Thanks_ks1 个月前
【第三章·基本算术运算】第一节:C 语言运算符和表达式
c 语言·算术运算符·复合赋值·表达式语句·增 1 减 1 运算符·未定顺序求值·优先级与结合性
Thanks_ks1 个月前
001【第一章·为什么要学习编程】
c 语言·少儿编程·编程学习·计算思维·编程思想·图灵测试·编程一小时
main工作室4 个月前
2个月搞定计算机二级C语言——真题(1)解析
c 语言
main工作室5 个月前
3个月搞定计算机二级C语言!高效刷题系列进行中
c 语言
顾三殇1 年前
【C++ 进阶】学习导论:C/C++ 进阶学习路线、大纲与目标
开发语言·c++·c 语言