8-10 for 语句

迄今为止,C++中最常用的循环语句是for语句。当存在明确的循环变量时,for语句(也称为for循环)是首选方案,因为它能让我们轻松且简洁地定义、初始化、测试和修改循环变量的值。

自C++11起,for循环分为两种类型。本节将讲解经典的for语句,而新型的基于范围的for语句将在后续章节(16.8节——基于范围的for循环(for-each))中介绍,届时我们将先完成其他必修内容的学习。

从抽象层面看,for语句的结构相当简单:

for (init-statement; condition; end-expression)
   statement;

理解for语句工作原理最简单的方法,是将其转换为等效的while语句:

{ // note the block here
    init-statement; // used to define variables used in the loop
    while (condition)
    {
        statement; 
        end-expression; // used to modify the loop variable prior to reassessment of the condition
    }
} // variables defined inside the loop go out of scope here

for语句的求值过程

for语句的求值分为三个部分:

首先执行初始化语句。该操作仅在循环初始化时执行一次。初始化语句通常用于变量定义和初始化。这些变量具有“循环作用域”,本质上是一种块作用域,即变量从定义点存在至循环语句结束。在等效的while循环中,初始化语句位于包含循环的代码块内,因此当循环所在代码块结束时,初始化语句定义的变量即失效。

其次,每次循环迭代时求值条件语句。若条件为真,则执行循环体;若条件为假,循环终止并跳转至循环体外的下一条语句。

最后,在语句执行完毕后,将求值结束表达式。该表达式通常用于递增或递减初始化语句中定义的循环变量。结束表达式求值完成后,执行流程将返回至第二步(此时条件将重新进行求值)。

关键要点
for语句各部分的执行顺序如下:

  • 初始化语句
  • 条件判断(若为假,循环在此终止)
  • 循环体
  • 结束表达式(随后跳转回条件判断)

需注意:结束表达式在循环语句执行后运行,随后条件将重新求值。

让我们看一个 for 循环的示例,并讨论它是如何工作的:

#include <iostream>

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

    std::cout << '\n';

    return 0;
}

首先,我们声明一个名为 i 的循环变量,并将其初始化为 1。

其次,求值 i <= 10,由于 i 为 1,该表达式为真。因此执行语句,输出 1 和一个空格。

最后,求值 ++i,将 i 递增为 2。循环返回至第二步。

此时再次求值 i <= 10。由于 i 值为 2,该条件为真,循环继续迭代。语句输出 2 和空格,i 递增为 3。循环持续迭代直至 i 递增至 11,此时 i <= 10 求值为假,循环终止。

因此该程序输出结果:

image

为了举例说明,让我们将上述for循环转换为等效的while循环:

#include <iostream>

int main()
{
    { // the block here ensures block scope for i
        int i{ 1 }; // our init-statement
        while (i <= 10) // our condition
        {
            std::cout << i << ' '; // our statement
            ++i; // our end-expression
        }
    }

    std::cout << '\n';
}

image

看起来还不错吧?注意这里必须使用外层大括号,因为循环结束时变量i将超出作用域。

对于新手程序员来说,for循环可能难以理解——但经验丰富的程序员却钟爱它,因为这种循环方式非常紧凑,能清晰呈现计数器、循环条件和循环变量修饰符等所有必要信息,从而有效减少错误。


更多 for 循环示例

以下是一个使用 for 循环计算整数幂的函数示例:

#include <cstdint> // for fixed-width integers

// returns the value base ^ exponent -- watch out for overflow!
std::int64_t pow(int base, int exponent)
{
    std::int64_t total{ 1 };

    for (int i{ 0 }; i < exponent; ++i)
        total *= base;

    return total;
}

该函数返回基数^指数的值(即基数乘以指数次方)。

这是一个简单的递增for循环,循环变量i从0开始递增至指数值(不包含指数值本身)。

所有情况下,总值初始化为1。

若指数为0,for循环将执行0次。返回值为total(初始值1),即等同于base^0。

若指数为1,for循环将执行1次。total(初始值1)与基数相乘,此时total值变为base,即等同于base^1,最终返回该值。

当指数为2时,for循环执行2次。将总值(初始值为1)与基数相乘两次,得到值为基数*基数,即等同于基数^2,最终返回该值。

虽然大多数for循环会将循环变量递增1,但同样可以递减:

#include <iostream>

int main()
{
    for (int i{ 9 }; i >= 0; --i)
        std::cout << i << ' ';

    std::cout << '\n';

    return 0;
}

这将输出结果:

image

或者,我们可以在每次迭代中将循环变量的值改变超过1:

#include <iostream>

int main()
{
    for (int i{ 0 }; i <= 10; i += 2) // increment by 2 each iteration
        std::cout << i << ' ';

    std::cout << '\n';

    return 0;
}

这将输出结果:

image


for循环条件中使用不等号运算符的风险

在编写涉及数值的循环条件时,我们常能采用多种不同方式表达条件。以下两个循环的执行效果完全相同:

#include <iostream>

int main()
{
    for (int i { 0 }; i < 10; ++i) // uses <
         std::cout << i;

    for (int i { 0 }; i != 10; ++i) // uses !=
         std::cout << i;

     return 0;
}

那么我们应该选择哪一种?前者是更好的选择,因为即使i跳过值10,它也会终止,而后者则不会。以下示例说明了这一点:

#include <iostream>

int main()
{
    for (int i { 0 }; i < 10; ++i) // uses <, still terminates
    {
         std::cout << i;
         if (i == 9) ++i; // jump over value 10
    }

    for (int i { 0 }; i != 10; ++i) // uses !=, infinite loop
    {
         std::cout << i;
         if (i == 9) ++i; // jump over value 10
    }

     return 0;
}

最佳实践
在 for 循环条件中进行数值比较时,避免使用运算符 !=。尽可能优先使用运算符 < 或 <=。


差一错误

新手程序员在使用 for 循环(及其他使用计数器的循环)时遇到的最大问题之一就是差一错误Off-by-one errors。当循环迭代次数多一次或少一次时,就会产生差一错误,导致结果与预期不符。

以下是一个示例:

#include <iostream>

int main()
{
    // oops, we used operator< instead of operator<=
    for (int i{ 1 }; i < 5; ++i)
    {
        std::cout << i << ' ';
    }

    std::cout << '\n';

    return 0;
}

该程序本应输出 1 2 3 4 5,但实际只输出 1 2 3 4,原因是使用了错误的关系运算符。

虽然这类错误最常见的原因是使用了错误的关系运算符,但有时也可能因将前自增/前自减误用为后自增/后自减,或反之而导致。


省略表达式

可以编写省略任何或全部语句或表达式的 for 循环。例如,在下面的示例中,我们将省略初始化语句和结束表达式,仅保留条件:

#include <iostream>

int main()
{
    int i{ 0 };
    for ( ; i < 10; ) // no init-statement or end-expression
    {
        std::cout << i << ' ';
        ++i;
    }

    std::cout << '\n';

    return 0;
}

此 for 循环产生以下结果:

image

与让 for 循环执行初始化和递增操作不同,我们在此处进行了手动处理。本例中纯粹出于教学目的而如此操作,但实际应用中确实存在无需定义循环变量(因已存在变量)或无需在终止表达式中递增变量(因通过其他方式递增)的情况。

尽管此类情况较为罕见,但值得注意的是以下示例将导致无限循环:

for (;;)
    statement;

上述示例等同于:

while (true)
    statement;

这可能有些出人意料,因为您可能认为省略的条件表达式会被视为假。然而,C++标准明确(且不一致地)规定:for循环中省略的条件表达式应视为真。

我们建议完全避免使用这种形式的for循环,转而采用while(true)的写法。


带有多个计数器的 for 循环

虽然 for 循环通常只遍历一个变量,但有时需要处理多个变量。为此,程序员可以在初始化语句中定义多个变量,并利用逗号运算符在结束表达式中同时修改多个变量的值:

#include <iostream>

int main()
{
    for (int x{ 0 }, y{ 9 }; x < 10; ++x, --y)
        std::cout << x << ' ' << y << '\n';

    return 0;
}

此循环定义并初始化两个新变量:x 和 y。它将 x 在 0 到 9 的范围内进行迭代,每次迭代后 x 递增而 y 递减。

该程序输出结果:

image

这是C++中唯一允许在同一语句中定义多个变量并使用逗号运算符的情况。

相关内容
我们在第6.5课——逗号运算符中讲解了逗号运算符。

最佳实践
在 for 语句内部,允许在初始化语句中定义多个变量,并在结束表达式中使用逗号运算符。


嵌套的 for 循环

与其他类型的循环类似,for 循环可以嵌套在其他循环内部。在下面的示例中,我们正在将一个 for 循环嵌套在另一个 for 循环内部:

#include <iostream>

int main()
{
	for (char c{ 'a' }; c <= 'e'; ++c) // outer loop on letters
	{
		std::cout << c; // print our letter first

		for (int i{ 0 }; i < 3; ++i) // inner loop on all numbers
			std::cout << i;

		std::cout << '\n';
	}

	return 0;
}

每次外层循环迭代时,内层循环都会完整运行一次。因此,输出结果为:

image

以下是关于此处发生情况的更多细节。外层循环首先运行,字符 c 初始化为 ‘a’。随后评估 c <= ‘e’,该条件为真,因此循环体执行。由于 c 被设置为 ‘a’,这首先打印 a。接着内层循环完整执行(打印 0、1 和 2)。随后输出换行符。此时外层循环体执行完毕,循环返回起始位置,c 递增为 ‘b’,循环条件重新评估。由于条件仍为真,外层循环进入下一次迭代,输出 b012\n。如此循环往复。


仅在循环内部使用的变量应在循环内部定义

新手程序员常认为创建变量代价高昂,因此宁可一次性创建变量(随后赋值),也不愿多次创建变量(并进行初始化)。这导致循环代码呈现如下变体形式:

#include <iostream>

int main()
{
    int i {}; // i defined outside loop
    for (i = 0; i < 10; ++i) // i assigned value
    {
        std::cout << i << ' ';
    }

    // i can still be accessed here

    std::cout << '\n';

    return 0;
}

然而,创建变量本身并无成本——真正产生成本的是初始化操作,而初始化与赋值通常在成本上并无差异。上述示例使得变量i在循环外部可用。除非必须在循环外部使用该变量,否则在循环外定义变量可能带来两点后果:

  1. 它会增加程序复杂度,因为我们需要阅读更多代码才能找到变量使用位置。
  2. 实际运行可能更慢,因为编译器对作用域较大的变量优化效果往往较差。

遵循“在合理最小作用域内定义变量”的最佳实践,仅在循环内部使用的变量应在循环内部定义,而非外部。

最佳实践
仅在循环内部使用的变量应在循环作用域内定义。


结论

for循环是C++语言中最常用的循环结构,因为它将循环变量、循环条件和循环变量修改操作所需的所有信息都置于循环顶部,这有助于减少错误。尽管其语法对新手程序员而言通常略显复杂,但你很快就会频繁接触for循环,转眼间就能完全掌握!

当存在计数变量时,for语句表现尤为出色。若无需计数功能,while语句可能是更优选择。

最佳实践
当存在明显的循环变量时,优先使用 for 循环而非 while 循环。
当不存在明显的循环变量时,优先使用 while 循环而非 for 循环。


测验时间

问题 #1

编写一个 for 循环,打印 0 到 20 之间的所有偶数。

image

显示方案

for (int i{ 0 }; i <= 20; i += 2)
    std::cout << i << '\n';

问题 #2

编写一个名为 sumTo() 的函数,该函数接受一个名为 value 的整数参数,并返回 1 到 value 之间所有数字的和。

例如,sumTo(5) 应返回 15,即 1 + 2 + 3 + 4 + 5。

提示:使用非循环变量累加从1到输入值的总和,类似上文pow()示例中使用total变量累加每次迭代的返回值。

image

显示答案

int sumTo(int value)
{
    int total{ 0 };
    for (int i{ 1 }; i <= value; ++i)
        total += i;

    return total;
}

问题 #3

以下for循环存在什么问题?

// Print all numbers from 9 to 0
for (unsigned int i{ 9 }; i >= 0; --i)
    std::cout << i<< ' ';

显示答案

此 for 循环在 i >= 0 时持续执行。换言之,它会运行至 i 变为负数为止。然而由于 i 是无符号变量,其值永远不会变为负数。因此该循环将永远运行下去(哈哈)!通常情况下,除非必要,应避免对无符号变量进行循环操作。

问题 #4

Fizz Buzz 是一款用于教导儿童理解可除性概念的简单数学游戏,有时也被用作面试题来评估基础编程能力。

游戏规则很简单:从1开始向上计数,凡仅能被3整除的数字替换为“fizz”,仅能被5整除的数字替换为“buzz”,同时能被3和5整除的数字替换为“fizzbuzz”。

请在名为fizzbuzz()的函数中实现此游戏,该函数需接收一个参数以确定计数上限。使用for循环和单个if-else链(即可自由使用多个else-if语句)。

fizzbuzz(15)的输出应与以下结果一致:

1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz

image

显示答案

// h/t to reader Waldo for suggesting this quiz
#include <iostream>

void fizzbuzz(int count)
{
	for (int i{ 1 }; i <= count; ++i)
	{
		if (i % 3 == 0 && i % 5 == 0)
		{
			std::cout << "fizzbuzz\n";
		}
		else if (i % 3 == 0)
		{
			std::cout << "fizz\n";
		}
		else if (i % 5 == 0)
		{
			std::cout << "buzz\n";
		}
		else
		{
			std::cout << i << '\n';
		}
	} // end for loop
}

int main()
{
	fizzbuzz(15);

	return 0;
}

问题 #5

修改你在前次测验中编写的 FizzBuzz 程序,添加一条规则:凡能被 7 整除的数字应替换为“pop”。运行该程序 150 次迭代。

在此版本中,若使用if/else链条显式覆盖所有可能的字符组合,将导致函数过于冗长。请优化函数结构,仅使用4个if语句:分别对应三个非复合词(“fizz”、‘buzz’、“pop”),以及数字输出情况。

显示提示

提示:使用布尔变量来记录某个数字是否满足其中一个条件。

以下是预期输出示例片段:

image
image
image

显示答案

// h/t to reader Waldo for suggesting this quiz
#include <iostream>

void fizzbuzz(int count)
{
	for (int i{ 1 }; i <= count; ++i)
	{
		bool printed{ false };
		if (i % 3 == 0)
		{
			std::cout << "fizz";
			printed = true;
		}
		if (i % 5 == 0)
		{
			std::cout << "buzz";
			printed = true;
		}
		if (i % 7 == 0)
		{
			std::cout << "pop";
			printed = true;
		}

		if (!printed)
			std::cout << i;

		std::cout << '\n';
	} // end for loop
}

int main()
{
	fizzbuzz(150);

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