8.8 循环与while语句介绍

循环介绍

现在真正的乐趣才刚刚开始——在接下来的课程中,我们将学习循环。循环loops是一种控制流结构,它能让一段代码反复执行,直到满足某个条件为止。循环为你的编程工具箱增添了极大的灵活性,让你能够完成许多原本难以实现的任务。

例如,假设你想打印1到10之间的所有数字。若不使用循环,你可能会尝试如下写法:

#include <iostream>

int main()
{
    std::cout << "1 2 3 4 5 6 7 8 9 10";
    std::cout << " done!\n";
    return 0;
}

虽然这可行,但随着需要打印的数字增多,实现难度会逐渐增加:如果要打印1到1000之间的所有数字呢?那可得输入不少内容!不过这种程序之所以能这样编写,是因为我们在编译时就已知需要打印多少个数字。

现在,让我们改变一下参数。如果我们希望让用户输入一个数字,然后打印1到用户输入数字之间的所有数字呢?用户输入的数字在编译时是无法预知的。那么我们该如何解决这个问题呢?


while 语句

while 语句while statement(也称为 while 循环while loop)是 C++ 提供的三种循环类型中最简单的,其定义与 if 语句非常相似:

while (condition)
    statement;

while语句使用while关键字声明。当while语句被执行时,会求值表达式条件。若条件求值为真,则执行关联语句。

但与if语句不同的是,语句执行完毕后,控制权将返回至while语句开头并重复该过程。这意味着只要条件持续求值为真,while语句就会不断循环。

下面来看一个简单的 while 循环示例,它将打印出 1 到 10 之间的所有数字:

#include <iostream>

int main()
{
    int count{ 1 };
    while (count <= 10)
    {
        std::cout << count << ' ';
        ++count;
    }

    std::cout << "done!\n";

    return 0;
}

这输出:

image

让我们仔细看看这个程序的运行过程。

首先,我们定义了一个名为count的变量并将其初始化为1。条件count <= 10为真,因此语句执行。此时语句是一个代码块,因此块内所有语句都会执行。代码块中的第一条语句输出1和一个空格,第二条语句将count递增为2。控制流现在返回while语句的顶部,条件再次被求值。2 <= 10为真,因此代码块再次执行。循环将持续执行直至count变为11,此时11 <= 10为假,循环关联的语句将被跳过。此时循环结束。

虽然这个程序比直接输入1到10之间的所有数字要复杂些,但请考虑修改程序以打印1到1000之间所有数字是多么简单:你只需将count <= 10改为count <= 1000即可。


初始值求值为false的while语句

请注意,如果条件初始值为假,则关联的语句将完全不会执行。请考虑以下程序:

#include <iostream>

int main()
{
    int count{ 15 };
    while (count <= 10)
    {
        std::cout << count << ' ';
        ++count;
    }

    std::cout << "done!\n";

    return 0;
}

条件 15 <= 10 求值为假,因此关联的语句被跳过。程序继续执行,唯一输出的内容是 done!。


无限循环

另一方面,如果表达式始终求值为真,则while循环将永远执行。这被称为无限循环infinite loop。以下是一个无限循环的示例:

#include <iostream>

int main()
{
    int count{ 1 };
    while (count <= 10) // this condition will never be false
    {
        std::cout << count << ' '; // so this line will repeatedly execute
    }

    std::cout << '\n'; // this line will never execute

    return 0; // this line will never execute
}

由于本程序中计数器从未递增,因此条件count <= 10将永远为真。结果循环将永不终止,程序将无限次输出1 1 1 1 1…


故意设计的无限循环

我们可以这样声明一个故意设计的无限循环:

while (true)
{
  // this loop will execute forever
}

退出无限循环的唯一途径是通过 return 语句、break 语句、exit 语句、goto 语句、抛出异常,或是用户终止程序。

以下是一个展示此原理的简单示例:

#include <iostream>

int main()
{

    while (true) // infinite loop
    {
        std::cout << "Loop again (y/n)? ";
        char c{};
        std::cin >> c;

        if (c == 'n')
            return 0;
    }

    return 0;
}

该程序将持续循环运行,直至用户输入数字n。此时if语句将评估为真,关联的return 0;语句将导致main()函数退出,从而终止程序运行。

此类循环在持续运行的Web服务器应用程序中较为常见,这类程序负责处理Web请求。

最佳实践
建议使用 while(true) 实现有意为之的无限循环。


无意中的无限循环

在while循环条件后无意间添加分号,是导致程序卡死的好方法。

以下示例展示了这个简单错误会引发的后果:

#include <iostream>

int main()
{
    int count{ 1 };
    while (count <= 10); // note the semicolon here
    {
        std::cout << count << ' ';
        ++count;
    }

    std::cout << "done!\n";

    return 0;
}

image

该程序的执行效果如同我们编写了以下代码:

#include <iostream>

int main()
{
    int count{ 1 };
    while (count <= 10) // this is an infinite loop
        ;               // whose body is a null statement

    { // this is no longer associated with the while loop
        std::cout << count << ' ';
        ++count;
    }

    std::cout << "done!\n";

    return 0;
}

由于循环条件求值为真,循环体被执行。但循环体是一个空语句,不会执行任何操作。随后循环条件再次被求值。由于计数器从未递增,条件永远不会评估为假,因此循环将无限期地执行无操作。程序将呈现卡死状态。

与if语句不同(if语句中条件后加分号总是错误),while语句偶尔会故意这样写。例如,若需持续调用函数直至其返回false,可简洁地写成如下形式:

while (keepRunning()); // will keep calling this function until it returns false

当然,如果函数永远不返回false,你就会陷入无限循环。

警告
请注意在while语句的条件后添加分号,否则将导致无限循环,除非该条件存在某种方式可求值为false。


循环变量与命名

循环变量用于控制循环的执行次数。例如在 while (count <= 10) 中,count 就是循环变量。虽然大多数循环变量类型为 int,但偶尔也会出现其他类型(如 char)。

循环变量通常采用简单命名,其中 i、j 和 k 最为常见。

顺带一提……
循环变量使用i、j、k作为名称,源于这些是Fortran编程语言中整型变量最简短的前三个名称。此约定沿用至今。

然而,若想查找程序中循环变量使用的具体位置,当你搜索i、j或k时,搜索结果将包含程序中一半的行数!因此部分开发者更倾向于使用iii、jjj或kkk这类循环变量名。由于这些名称更具唯一性,能显著简化循环变量的定位过程,并使其作为循环变量更易辨识。更优的方案是采用“真实”变量名,例如count、index,或能体现计数对象的名称(如userCount)。

最常见的循环变量类型称为计数器counter,即记录循环执行次数的变量。在上文示例中,变量count即为计数器。


整型循环变量应采用带符号类型

整型循环变量几乎总是应采用带符号类型,因为无符号整数可能导致意外问题。请考虑以下代码:

#include <iostream>

int main()
{
    unsigned int count{ 10 }; // note: unsigned

    // count from 10 down to 0
    while (count >= 0)
    {
        if (count == 0)
        {
            std::cout << "blastoff!";
        }
        else
        {
            std::cout << count << ' ';
        }
        --count;
    }

    std::cout << '\n';

    return 0;
}

请看上面的示例,看看你能否发现其中的错误。如果你之前没见过这种情况,可能不太明显。

事实证明,这个程序陷入了无限循环。它最初按预期输出“10 9 8 7 6 5 4 3 2 1 发射!”,但随后循环变量count发生溢出,开始从4294967295倒计时(假设使用32位整数)。为什么?因为循环条件count >= 0永远不会为假!当计数为0时,0 >= 0为真。随后执行--count操作,计数器将回绕至4294967295。由于4294967295 >= 0仍为真,程序持续运行。由于计数器是无符号类型,它永远不会变为负数,因此循环永远不会终止。

最佳实践
积分环变量通常应采用带符号的整数类型。


每隔N次迭代执行某项操作

每次循环执行称为一次迭代iteration

通常我们需要每隔2次、3次或4次迭代执行某项操作,例如打印换行符。这可通过在计数器上使用取余运算符轻松实现:

#include <iostream>

// Iterate through every number between 1 and 50
int main()
{
    int count{ 1 };
    while (count <= 50)
    {
        // print the number (pad numbers under 10 with a leading 0 for formatting purposes)
        if (count < 10)
        {
            std::cout << '0';
        }

        std::cout << count << ' ';

        // if the loop variable is divisible by 10, print a newline
        if (count % 10 == 0)
        {
            std::cout << '\n';
        }

        // increment the loop counter
        ++count;
    }

    return 0;
}

该程序输出结果:

image


嵌套循环

循环也可以嵌套在其他循环内部。嵌套循环对新手程序员来说往往有些令人困惑,因此我们先从一个稍简单的示例开始:

#include <iostream>

void printUpto(int outer)
{
    // loop between 1 and outer
    // note: inner will be created and destroyed at the end of the block
    int inner{ 1 };
    while (inner <= outer)
    {
        std::cout << inner << ' ';
        ++inner;
    }
} // inner destroyed here

int main()
{
    // outer loops between 1 and 5
    int outer{ 1 };
    while (outer <= 5)
    {
        // For each iteration of the outer loop, the code in the body of the loop executes once

        // This function prints numbers between 1 and outer
        printUpto(outer);

        // print a newline at the end of each row
        std::cout << '\n';
        ++outer;
    }

    return 0;
}

在此示例中,我们有一个名为 outer 的外层循环变量,用于计数从 1 到 5。循环每次迭代时,循环体都会调用 printUpto() 函数(传入 outer 作为参数),打印换行符,并递增 outer。printUpto() 函数内部也包含一个循环,用于打印 1 到传入值之间的所有数字。

因此当outer为1时,循环体调用printUpto(1)输出数字1,随后打印换行符并递增outer。此时outer变为2,循环体再次调用printUpto(2)输出1 2,接着打印换行符并递增outer。后续迭代依次调用 printUpto(3)、printUpto(4) 和 printUpto(5)。

因此该程序输出:

image

请注意,这是一种嵌套循环的形式——在外层循环的循环体中,我们调用了一个自身包含循环的函数。换言之,外层循环的每次迭代都会触发函数内部循环的执行。

现在让我们转向更复杂的示例:

#include <iostream>

int main()
{
    // outer loops between 1 and 5
    int outer{ 1 };
    while (outer <= 5)
    {
        // For each iteration of the outer loop, the code in the body of the loop executes once

        // inner loops between 1 and outer
        // note: inner will be created and destroyed at the end of the block
        int inner{ 1 };
        while (inner <= outer)
        {
            std::cout << inner << ' ';
            ++inner;
        }

        // print a newline at the end of each row
        std::cout << '\n';
        ++outer;
    } // inner destroyed here

    return 0;
}

该程序的输出结果完全相同:

image

由于我们在另一个while循环中直接嵌套了while循环,这看起来有些混乱。但实际上,我们只是将原本位于printUpto()函数内的代码直接移到了外层循环体中。

让我们更详细地分析其工作原理。

首先,外层循环(循环变量outer)将执行5次迭代(outer依次取值1、2、3、4、5)。

外层循环首次迭代时,outer值为1,随后执行外层循环体。在外层循环体内,存在另一个以inner为循环变量的嵌套循环。该内层循环从1迭代至outer(此时outer值为1),因此仅执行一次并输出1。随后输出换行符,并将outer递增至2。

外层循环第二次迭代时,outer值变为2,循环体再次执行。在外层循环体内,inner再次从1迭代至outer(此时outer值为2),因此该内层循环执行两次,分别输出1和2。随后打印换行符,并将outer增量至3。

此过程持续进行,内层循环在后续迭代中依次输出1 2 3、1 2 3 4、1 2 3 4 5。最终当外层变量增至6时,由于外层循环条件(outer <= 5)不再成立,外层循环终止,程序随之结束。

若仍感困惑,建议使用调试器逐行执行程序,同时观察inner和outer的值变化,这将有助于深入理解运行机制。


测验时间

问题 #1

在上面的程序中,为什么变量 inner 被声明在 while 循环块内部,而不是紧跟在 outer 的声明之后?

显示解答

变量 inner 在 while 循环块内部声明,因此每次外部循环执行时都会重新创建(并初始化为 1)。如果将变量 inner 声明在外部 while 循环之前,其值将永远不会重置为 1,或者我们必须使用赋值语句来实现。此外,由于变量 inner 仅在外部 while 循环块内部使用,因此在该处声明它更为合理。请谨记:变量应尽可能在最小作用域内声明!

问题 #2

编写一个程序,打印出字母 a 到 z 及其对应的 ASCII 码。使用类型为 char 的循环变量。

显示提示

提示:要将字符作为整数打印,必须使用 static_cast。

image

显示解答

#include <iostream>

int main()
{
    char myChar{ 'a' };
    while (myChar <= 'z')
    {
        std::cout << myChar << ' ' << static_cast<int>(myChar) << '\n';
        ++myChar;
    }

    return 0;
}

image


问题 #3

将嵌套循环示例反转,使其打印出以下内容:

5 4 3 2 1
4 3 2 1
3 2 1
2 1
1

image

显示答案

#include <iostream>

// Loop between 5 and 1
int main()
{
	int outer{ 5 };
	while (outer >= 1)
	{
		// loop between outer and 1
		int inner{ outer };
		while (inner >= 1)
        {
			std::cout << inner-- << ' ';
        }

		// print a newline at the end of each row
		std::cout << '\n';
		--outer;
	}

	return 0;
}

问题 #4

现在让数字以这种方式打印:

        1
      2 1
    3 2 1
  4 3 2 1
5 4 3 2 1

提示:先弄清楚如何让它像这样打印出来:

X X X X 1
X X X 2 1
X X 3 2 1
X 4 3 2 1
5 4 3 2 1

image

显示答案

// Thanks to Shiva for this solution
#include <iostream>

int main()
{
	// There are 5 rows, we can loop from 1 to 5
	int outer{ 1 };

	while (outer <= 5)
	{
		// Row elements appear in descending order, so start from 5 and loop through to 1
		int inner{ 5 };

		while (inner >= 1)
		{
			// The first number in any row is the same as the row number
			// So number should be printed only if it is <= the row number, space otherwise
			if (inner <= outer)
				std::cout << inner << ' '; // print the number and a single space
			else
				std::cout << "  "; // don't print a number, but print two spaces

			--inner;
		}

		// A row has been printed, move to the next row
		std::cout << '\n';

		++outer;
	}

	return 0;
}
posted @ 2026-02-27 09:46  游翔  阅读(0)  评论(0)    收藏  举报