【在主画面加入捷径】
       
【选择语系】
繁中 简中

[C 语言] 程序设计教学:如何使用控制结构 (Control Structure) 改变程序执行顺序

【赞助商连结】

    默认情形下,程序执行的顺序是由上至下,但我们可以透过控制结构 (control structure) 来改变程序执行的流程,让程序有基本的判断能力。本文介绍 C 语言可用的控制结构。

    if

    if 算是最基础的选择结构,由于英文中的 if 的语义相当符合这个情境,几乎所有的程序语言都保留 if 这个保留字。if 的 C 伪代码如下:

    if (condition_a) {
        // Do something_a.
    } else if (condition_b) {
        // Do something_b.
    } else {
        // Do something_c.
    }
    

    以本例来说,若程序符合 condition_a,程序会执行 something_a 内的程序代码,然后跳出整个 if 叙述。若程序不符合 condition_a,会检查下一个条件,若程序符合 condition_b,则会执行 something_b 内的程序代码,然后跳出整个 if 叙述。若前述条件皆不符合,则会执行 else 区块内的程序代码。

    除了 if 区块本身是必需的,else ifelse 都是选择性的。else if 可以多个,而 else 仅有一个,且需放最后。这用逻辑思考来想即可,不要硬背。

    由于 C 没有强制空格,else ifelse 可以换行,这种方式很适合搭配注解一起使用:

    // Comment_a
    if (condition_a) {
        // Do something_a.
    }
    // Comment_b
    else if (condition_b) {
        // Do something_b.
    }
    // Comment_c.
    else {
        // Do something_c.
    }

    读者可从中自行选择喜好的风格。

    以下是实例:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        // Prompt for input.
        printf("Input an integer: ");
    
        int num;
        // Valid input.
        if (scanf("%d", &num) == 1) {
            if (num % 2 == 0) {
                printf("%d is even\n", num);
            } else {
                printf("%d is odd\n", num);
            }
        }
        // Invalid input.
        else {
            fprintf(stderr, "Invalid input\n");
            return EXIT_FAILURE;
        }
        
        return EXIT_SUCCESS;
    }

    一开始,我们用一个 prompt 引导使用者输入整数。但我们不能一厢情愿地认定输入的数据格式正确,仍要检查使用者输入的内容。以本例来说,若输入的格式正确,我们就检查该数字是奇数或偶数;若不正确,则跳出错误讯息。

    switch

    switch 算是一种小小的语法糖,主要是用来简化 if 叙述。switch 的 C 伪代码如下:

    switch (value) {
    case a:
        // Do something.
        break;
    case b:
    case c:
        // Fallthrough.
        // Do something.
        break;
    default:
        // Do something.
        break;
    }
    

    要注意在 switch 叙述中,若没写 break 则会继续前往下一个条件,这种特性叫做 *fallthrough*。由于这种特性有时会造成 bug,在 Go 等现代语言中将其修改掉了。

    以下是实例:

    #include <stdio.h>
    #include <time.h>
    
    int main(void)
    {
        // Get the object to `tm *` for current time.
        time_t t = time(NULL);
        struct tm *now = localtime(&t);
    
        switch (now->tm_wday) {
        case 0:  // Sunday.
        case 6:  // Saturday.
            // Fallthrough.
            printf("Weekend\n");
            break;
        case 5:  // Friday.
            printf("Thank God. It's Friday.\n");
            break;
        case 3:  // Wednesday.
            printf("Hump day\n");
            break;
        default:  // All other days of week.
            printf("Week\n");
            break;
        }
    
        return 0;
    }

    一开始,我们建立一个时间物件,接着,取得当下时间。根据当下时间来吐出相对应的讯息,藉由 switch 来进行判断。读者目前不用在纠结在时间物件的使用细节,目前就当固定用法即可,学到指针后自然会这种写法。

    switchif 是相通的,读者可试着用 if 改写这个例子。

    while

    while 是一种未定次数的循环,主要是用来反覆执行某一区块的程序代码。while 的 C 伪代码如下:

    while (condition) {
        // Do something.
    }
    

    condition 的状况为真时,就会执行区块内的程序代码;反之,则不执行。

    以下是实例:

    #include <stdio.h>
    
    int main(void)
    {
        int i = 10;
        
        while (i > 0) {
            printf("Counting down %d\n", i);
            
            i--;
        }
        
        printf("Blast\n");
    
        return 0;
    }

    如果写成 while(1) 或是 while(true) 等,代表该循环是无限循环。C 伪代码如下:

    // Run infinitely.
    while (1) {
      // Do something.
    }
    

    初学者可能会在无意间写出无限循环,这也算是一种常见的 bug。不过,无限循环并不是什么可怕的事,电脑游戏的 game loop 或是视窗程序的 event loop 基本上都是某种无限循环。无限循环会搭配 break 叙述以跳出此循环,详见下文。

    do ... while

    do ... while 算是 while 的变体,和 while 不同的是 do ... while 至少会执行一次。其伪代码如下:

    do {
    
    } while (condition);
    

    相对于 while 来说,do ... while 比较少用,一些使用的时机像是减少重覆程序代码。

    有一个小技巧是用 do ... while(0) 执行单次循环,像是以下 C 伪代码:

    do {
        // do some things.
    
        if (error) {
            break;
        }
    
        // do more things.
    } while (0);
    

    这个小技巧会有用是因为我们不能在 if 叙述中加上 break,但在 while 中可以。在这个技巧中,我们实质上是使用一个可以加上 break 的单一区块,这样可以简化错误处理的步骤。

    for

    for 循环是用来执行有特定次数的程序区块,在 C 语言中,for 只有一种形式,就是用计数器来控制。以下是 for 的伪代码:

    for (start; end; adjustment) {
        // Do something.
    }
    

    start 中要将计数器初始化,end 为计数中止条件,adjustment 为每次调整计数器的步骤。

    其实 for 可以改写成等义的 while

    start;
    while (end) {
        // Do something.
        
        adjustment;
    }
    

    for 改写成等义的 while 或反过来也是一种基本的程序练习,读者可自行尝试。

    我们将先前 while 的例子改写:

    #include <stdio.h>
    
    int main(void)
    {
        // C99
        for (int i = 10; i > 0; i--) {
            printf("Counting down %d\n", i);
        }
        
        printf("Blast\n");
    
        return 0;
    }

    要注意在 for 条件内初始化变量的方法仅限 C99 后可用,在 C89 前只能用以下写法:

    int i;
    for (i = 0; i < 10; i++) {
        // Do something.
    }

    但是命名空间中会多一个 i 变量,而 i 又是循环中惯用的变量。如果为了某些原因需停留在 C89,可将整个 for 叙述包在区块中以解决命名空间污染的问题:

    {
        int i;
        for (i = 0; i < 10; i++) {
            // Do something.
        }
    }

    for (;;) 也代表无限循环:

    // Run infinitely.
    for (;;) {
        // Do something.
    }

    但语义上不若 while (true) 自然。

    continuebreak

    这两种语法算是限缩版本的 goto,用来改变循环的行进,差别如下:

    • continue:重新开始循环
    • break:跳出循环

    由于有 continuebreak,我们现在很少需要撰写 goto 语句。

    以下是实例:

    #include <stdbool.h>
    #include <stdio.h>
    
    int main()
    {
        int num;
        char b[256];
        // Run infinitely until the user input a valid number.
        while(true) {
            // Prompt for user input.
            printf("Input a number: ");
    
            if (scanf("%d", &num) != 1) {
                // Trick to prevent infinite loop.
                scanf("%s", b);
    
                // Show error message to the user.
                fprintf(stderr, "Invalid input\n");
    
                continue;  // Re-start the loop.
            }
    
            // Show info message to the user.
            printf("You input %d\n", num);
    
            break;  // Exit the loop.
        }
    
        return 0;
    }

    goto

    goto 是一种短程跳跃的语法,在同函式内可前往任意的位置。结构化的程序设计 (structured programming) 会尽量避免使用 goto,因过度使用 goto 会使得程序难以维护。不过,goto 偶尔可以使语法更简洁,像是在程序运行结束或中止后释放系统资源。我们会于后续相关章节会展示其用法。

    return

    return 用于跳出函式叙述并回到函式调用所在的位置,若是跳出主函式 (main) 则结束程序,若函式中没有使用 return 则会执行完所有的函式叙述后自动跳出函式。详见后文有关函式的介绍。

    【赞助商连结】