Skip to content

LV050-循环结构

一、while

1. 基本形式

c
while(表达式)
{
    语句块...;
}

先计算“表达式”的值,当值为真(非 0)时, 再执行“语句块”;执行完“语句块”,再次计算表达式的值,如果为真,继续执行“语句块”……这个过程会一直重复,直到表达式的值为假(0),就退出循环,执行 while 整体 后面的代码。

image-20220125205217591

2. 使用实例

shell
#include <stdio.h>

int main(int argc, char *argv[])
{
    int n = 5;
    int count = 0;
    printf("------cycle start------\n");
    while(n > 2)
    {
        printf("Before statement execution: count=%d, n=%d\n", count, n);
        n--;
        count++;
        printf("After statement execution: count=%d, n=%d\n", count, n);
    }
    printf("------cycle end------\n");
    printf("count = %d, n = %d \n", count, n);
    return 0;
}

image-20260209095227520

【注意】 while 循环是 先判断 表达式为真,然后才执行 循环,若为假,则直接结束循环,不会再执行循环体内的语句块。

二、do...while

1. 基本形式

c
do{
    语句块...;
}while(表达式);

先执行“语句块”,再计算“表达式”的值,当值为真(非 0)时,继续执行“语句块”……这个过程会一直重复,直到表达式的值为假(0),就退出循环,执行 do...while 整体后面的代码。

image-20220125210700859

2. 使用实例

shell
#include <stdio.h>

int main(int argc, char *argv[])
{
    int n = 5;
    int count = 0;
    printf("------cycle start------\n");
    do{
        printf("Before statement execution: count=%d, n=%d\n", count, n);
        n--;
        count++;
        printf("After statement execution: count=%d, n=%d\n", count, n);
    }while(n > 2);
    printf("------cycle end------\n");
    printf("count = %d, n = %d \n", count, n);

    return 0;
}

image-20260209095545517

会发现这里和上面的 while 循环执行情况是一样的,当我们吧这里的 n > 2 改为 n > 5 的时候就会发现,这里会执行 1 次,但是前面的 wihle 循环一次都不会执行。

【注意】 do...while 循环是 先执行一次循环,然后才判断 表达式为真,然后才继续执行循环,若为假,则直接结束循环,不会再执行循环体内的语句块。所以当两个实例中的判断条件 n > 2 变为 n > 5 时 while 循环结构一次也不执行,而 do...while 循环结构会执行一次,这点需要注意。

三、for

1. 基本形式

c
for(表达式1; 表达式2; 表达式3)
{
    语句块...;
}

(1) 先执行“表达式 1”。

(2) 再执行“表达式 2”,如果它的值为真(非 0),则执行循环体,否则结束循环。

(3) 执行完循环体后再执行“表达式 3”。

(4) 重复执行步骤 (2) 和 (3),直到“表达式 2”的值为假,就结束循环。

上面的步骤中,(2) 和 (3) 是一次循环,会重复执行,for 语句的主要作用就是不断执行步骤 (2) 和 (3)。

image-20220126111212925

注意

(1)“表达式 1”仅在第一次循环时执行,以后都不会再执行,可以认为这是一个初始化语句。“表达式 1”可省略, 但循环之前应给循环变量赋值

(2)表达式 2”一般是一个 关系表达式,决定了是否还要继续下次循环,称为“循环条件”。“表达式 2”可省略, 将陷入死循环

(3)“表达式 3”很多情况下是一个带有自增或自减操作的表达式,以 使循环条件逐渐变得“不成立”。“表达式 3”可省略, 但在循环体中增加使循环变量值改变的语句

2. 使用实例

c
#include <stdio.h>

int main(int argc, char *argv[])
{
    int n = 5;
    int count = 0;
    printf("------cycle start------\n");
    for(n=5;n>2;n++)
    {
        count++;
        printf("statement execution: count=%d, n=%d\n", count, n);
    }
    printf("------cycle end------\n");
    printf("count = %d, n = %d \n", count, n);

    return 0;
}

四、死循环

在我们实际编程的时候,一般程序都不是只执行 1 次,经常需要一直运行,这个时候就需要死循环,上面三种循环结构都可以进入死循环,只要保证条件一直为真即可。

1. 三种死循环

c
// while 语句
while(1){
    // 要执行的语句
}

// do...while 语句
do {
    // 要执行的语句
}while(1)

// for 循环
for(;;) {
    // 要执行的语句
}

2. for(;;)与 while(1)

在内核源码中,我们会看到很多的 for 死循环,和 while(1)相比,有什么区别?

2.1 功能上等效

在实现无限循环(死循环)这个功能上,while(1) 和 for(;;)在 C 语言里是 100% 等效的。 它们都能让代码一遍又一遍、永无止境地执行下去,直到出现断电或者复位。

为什么可以等效?

while(1):while 后面括号里的表达式是条件。1 在 C 语言里代表真。所以 while(1) 的意思就是“当条件为真时,执行循环体”。因为 1 永远是真,所以循环就永远停不下来。

for(;😉:for 循环的标准结构是 for(初始化; 条件; 增量)。for(;;)的精妙之处就在于它故意省略了所有三个部分。在 C 语言标准里,for 循环如果省略条件部分,默认这个条件就是真,这就和 while(1) 的条件一样了。

2.2 编译器怎么看?

在 C 语言刚诞生那会儿,一些老旧的编译器,对 while(1) 和 for(;😉 的底层处理可能有那么一丁点不同。

  • while(1):编译器需要生成指令去检查条件 1 是否为真。虽然 1 永远是真,但这个检查是否为真的动作理论上还是存在的。

  • for(;😉:因为条件部分直接是空的,被视为真,可能被某些老旧编译器直接编译成一个无条件跳转指令,跳回到循环开头。理论上少了一个检查常数是否为真的冗余步骤。

而在新的编译器上, 两者的差别其实已经没有了, 现在的编译器已经能作出很智能的优化了。我们可以尝试一下:

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

int main()
{
	while(1)
	{
		sleep(1000);
	}
}

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

int main()
{
	for(;;)
	{
		sleep(1000);
	}
}

我们直接生成汇编文件(-S 选项的 GCC 编译过程会为每个被编译的输入文件生成以.s 作为后缀的汇编语言文件):

shell
gcc -S test_while.c
gcc -S test_for.c

我们会得到以下两个汇编文件:

image-20260209104001179

这里使用的环境是 ubuntu18.04,gcc 版本是 7.5.0。在这个环境中,可以看到两个循环对应的汇编其实是一模一样的。

2.2 为什么国外资料 for 更常见?

功能没区别,编译器下效率也没区别,那为啥国外资料里 for(;😉 似乎更常见呢?这主要是历史传统和编程风格偏好:

  • K&R C 的遗产:C 语言的两位祖师爷,Kernighan 和 Ritchie (K&R),在他们那本影响深远的圣经《The C Programming Language》中,就大量使用了 for(;😉 来表示无限循环。这本书奠定了 C 语言的风格基础。很多早期的 C 程序员、Unix 系统开发者都深受此书影响,习惯、延续了这一写法。这是一种祖师爷就这么写的的传统力量。

  • 简洁性:在一些程序员看来,for(;😉 极其简洁,没有任何冗余信息。它一眼就能看出这是个无限循环,因为 for 循环的三个部分都省略了。而 while(1) 看起来像一个正常的条件循环,只是条件碰巧是 1。for(;;)更像是一个为无限循环量身定制的成语。

  • 避免编译后的警告: 如上所述,虽然可以解决,但 for(;😉 天然避免了某些编译器关于常量条件的警告,对追求编译零警告的严格项目有一定吸引力。