for 语句

迄今为止,C++ 中最常用的循环语句是 for 语句。当存在明显的循环变量时,for 语句(亦称 for 循环)更受青睐,因为它能够简洁而清晰地完成循环变量的定义、初始化、测试及修改。

自 C++11 起,for 循环分为两种。本课将讲述传统的 for 语句;而范围 for 语句(16.8 – 基于范围的 for 循环)将在后续具备先决知识后介绍。

抽象地看,for 语句的语法十分简明:

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

理解 for 语句的最简方式,是将其转换为等价的 while 语句:

{ // 注意此处代码块
    init-statement;          // 用于定义循环所需变量
    while (condition)
    {
        statement;
        end-expression;      // 在重新评估条件前修改循环变量
    }
} // 循环内定义的变量在此处离开作用域

for 语句的求值过程
for 语句的求值分为三步:

  1. 首先执行 init-statement,仅在整个循环开始时执行一次。通常用于定义并初始化变量。这些变量拥有“循环作用域”,本质上是一种块作用域,其生存期从定义点延续到整个循环语句结束。上述 while 等价代码将 init-statement 置于包含循环的代码块内,故其作用域随块结束而终止。

  2. 每次迭代前求值 condition。若结果为 true,则执行 statement;若为 false,则循环终止,执行流转至循环后的下一条语句。

  3. statement 执行完毕后,计算 end-expression。通常用于递增或递减在 init-statement 中定义的循环变量。end-expression 求值后,执行流返回第二步,重新评估 condition。

关键洞察
for 语句各部分按以下顺序执行:

  • init-statement
  • condition(若为 false,循环立即结束)
  • 循环体
  • end-expression(完成后跳回 condition)

注意:end-expression 在循环体之后执行,随后再次评估 condition。

示例:

#include <iostream>

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

    std::cout << '\n';

    return 0;
}

执行过程:

  1. 声明循环变量 i 并初始化为 1。
  2. 求值 i <= 10,为 true,执行打印 1 和一个空格。
  3. 计算 ++i,使 i 变为 2,返回第二步。
  4. 重复上述步骤,直至 i 为 11,条件为 false,循环结束。

程序输出:

1 2 3 4 5 6 7 8 9 10

为示范,将上述 for 循环改写为等价 while 循环:

#include <iostream>

int main()
{
    { // 确保 i 具有块作用域
        int i{ 1 };           // init-statement
        while (i <= 10)       // condition
        {
            std::cout << i << ' '; // statement
            ++i;              // end-expression
        }
    }

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

外层花括号必不可少,因为 i 必须在循环结束后离开作用域。

新手常觉 for 循环难读,但经验丰富者偏爱其紧凑:所有与循环变量、条件及修改相关的信息集中顶部,有助于降低错误。

更多 for 循环示例

计算整数幂的函数:

#include <cstdint> // 固定宽度整数

// 返回 base 的 exponent 次方,注意溢出
std::int64_t pow(int base, int exponent)
{
    std::int64_t total{ 1 };

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

    return total;
}
  • 当 exponent 为 0,循环执行 0 次,返回 1,即 base^0。
  • 当 exponent 为 1,循环执行 1 次,返回 base。
  • 当 exponent 为 2,循环执行 2 次,返回 base * base。

递减循环:

#include <iostream>

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

    std::cout << '\n';

    return 0;
}

输出:

9 8 7 6 5 4 3 2 1 0

每次迭代增量大于 1:

#include <iostream>

int main()
{
    for (int i{ 0 }; i <= 10; i += 2) // 每次递增 2
        std::cout << i << ' ';

    std::cout << '\n';

    return 0;
}

输出:

0 2 4 6 8 10

在 for 循环条件中使用 operator!= 的风险

对于涉及数值的条件,可写成多种形式。以下两循环行为一致:

#include <iostream>

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

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

     return 0;
}

应优先选择前者,因为即使 i 跳过 10,循环仍可结束;后者则不会。示例如下:

#include <iostream>

int main()
{
    for (int i { 0 }; i < 10; ++i) // 使用 <,仍正常结束
    {
         std::cout << i;
         if (i == 9) ++i; // 跳过 10
    }

    for (int i { 0 }; i != 10; ++i) // 使用 !=,无限循环
    {
         std::cout << i;
         if (i == 9) ++i; // 跳过 10
    }

     return 0;
}

最佳实践:

在 for 循环条件中避免 operator!=;能使用 operator< 或 operator<= 时优先使用。

差一错误

新手最常犯的 for 循环错误之一是差一错误,即循环次数多一次或少一次。

示例:

#include <iostream>

int main()
{
    // 误用 operator< 而非 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 循环的三部分均可省略。下例省略 init-statement 与 end-expression,仅保留 condition:

#include <iostream>

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

    std::cout << '\n';

    return 0;
}

输出:

0 1 2 3 4 5 6 7 8 9

本例仅为演示,实际中若变量已存在或增量在其他位置完成,可省略相应部分。

以下形式产生无限循环:

for (;;)
    statement;

等价于:

while (true)
    statement;

C++ 标准明确规定:省略 condition 时视为 true。建议避免此写法,改用 while(true)。

多计数器 for 循环

多数 for 循环仅操作一个变量,但也可同时处理多个。init-statement 可一次定义多个变量,end-expression 可使用逗号运算符同时修改多个变量:

#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,y 自 9 减至 0。输出:

0 9
1 8
2 7
3 6
4 5
5 4
6 3
7 2
8 1
9 0

这是 C++ 中少数允许在同一条语句中定义多个变量及使用逗号运算符的可接受场景。

相关内容:逗号运算符见 6.5 课。

最佳实践:
在 for 语句内部,定义多个变量及使用逗号运算符是可接受的。

嵌套 for 循环

与其他循环一样,for 循环可嵌套。下例将 for 循环嵌套在另一个 for 循环内:

#include <iostream>

int main()
{
	for (char c{ 'a' }; c <= 'e'; ++c) // 外层循环遍历字母
	{
		std::cout << c; // 先打印字母

		for (int i{ 0 }; i < 3; ++i) // 内层循环遍历数字
			std::cout << i;

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

	return 0;
}

外层每迭代一次,内层完整执行一次,输出:

a012
b012
c012
d012
e012

细节:外层循环先执行,c 初始化为 ‘a’,条件为 true,打印 a;接着内层循环输出 0 1 2;打印换行。外层回到顶部,c 增为 ‘b’,再次判断条件并继续。

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

新手常误以为创建变量开销大,于是倾向在循环外定义变量,循环内仅赋值,例如:

#include <iostream>

int main()
{
    int i {}; // i 定义在循环外
    for (i = 0; i < 10; ++i) // 仅赋值
    {
        std::cout << i << ' ';
    }

    // i 在此处仍可访问

    std::cout << '\n';

    return 0;
}

然而,创建变量本身无开销,真正开销在于初始化,而初始化与赋值通常无成本差异。上述写法使 i 在循环后仍可见,导致:

  • 代码复杂度增加,需阅读更多代码才能确定变量用途;
  • 作用域扩大,可能妨碍编译器优化。

遵循“最小合理作用域”原则,仅在循环内使用的变量应定义在循环内部。

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

结论

for 语句是 C++ 最常用的循环,它将循环变量、条件及修改方式集中置于顶部,减少错误。虽然语法对初学者略显晦涩,但其出现频率极高,很快就能熟练掌握。

当存在明显计数器变量时,for 循环优于 while 循环;若无明显计数器,while 循环更佳。

最佳实践:

  • 有明显循环变量时,优先使用 for 循环;
  • 无明显循环变量时,优先使用 while 循环。

测验

问题 1
编写 for 循环,打印 0 至 20 的所有偶数。

问题 2
编写函数 sumTo(int value),返回 1 至 value 所有整数之和。
示例:sumTo(5) 应返回 15(1+2+3+4+5)。
提示:使用非循环变量累加,如 pow() 例中的 total。

问题 3
以下 for 循环有何错误?

// 打印 9 到 0
for (unsigned int i{ 9 }; i >= 0; --i)
    std::cout << i << ' ';

问题 4
Fizz Buzz 是一个简单数学游戏,用于教学及面试考察。规则:自 1 开始向上计数,仅被 3 整除的数替换为“fizz”,仅被 5 整除的替换为“buzz”,同时被 3 和 5 整除的替换为“fizzbuzz”。
在函数 fizzbuzz(int max) 中实现该游戏,使用 for 循环及单一 if-else 链。
示例:fizzbuzz(15) 输出:

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

问题 5
修改上一题的 FizzBuzz,新增规则:被 7 整除的替换为“pop”。运行 150 次。
若用 if/else 链枚举所有组合,函数将过长。请仅用 4 个 if 语句优化:分别对应 “fizz”、“buzz”、“pop” 及打印数字的情况。
提示:见输出片段。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"