Skip to content

循环语句:把重复的活儿丢给机器 ​

计算机最擅长的事情就是不知疲倦地重复做同一件事。倒不如说,计算机就是由无穷无尽的数据存,取,不倦的判断0和1,然后循环的做他们,组成了我们的互联网世界!

人会累,我现在让你手动打印 100 行 "Hello",你只会说CharlieChen114514显然是脑子进水了,但计算机只需要一条循环指令就能搞定。循环语句让我们告诉程序"把这个动作重复 N 遍"或者"一直做,直到某个条件满足为止"——这是几乎所有有意义程序的核心结构。

我们最熟悉的 while 循环:不知道次数就一直做 ​

while 循环是最直白的循环结构:先检查条件,条件为真就执行循环体,执行完再回来检查,直到条件为假才停下来。

C++
while (condition) {
    // 循环体
}

每次进入循环体之前先算一次 condition,结果是 true 就执行大括号里的代码,执行完毕后回到条件再判断。如果一开始条件就是 false,循环体一次都不会执行。

什么时候用 while?最典型的场景就是"我们事先不知道要循环多少次,但是肯定知道退出的条件是什么的场景"。比如让用户不断输入数字累加,直到输入 0 为止:

展开代码收起代码共 22 行
C++
#include <iostream>

int main()
{
    int sum = 0;
    int value = 0;

    std::cout << "请输入数字(输入 0 结束): ";
    std::cin >> value;

    // 嗯,发生啥咱们不知道,但是咱们知道
    // 要是咱们的 value 不是 0了,循环就要退役了。
    while (value != 0) {
        sum += value;
        std::cout << "当前累加和: " << sum << std::endl;
        std::cout << "请继续输入(0 结束): ";
        std::cin >> value;
    }

    std::cout << "最终结果: " << sum << std::endl;
    return 0;
}

咱们编译一下上面的小程序,运行效果看起来就是这样的:

text
请输入数字(输入 0 结束): 10
当前累加和: 10
请继续输入(0 结束): 25
当前累加和: 35
请继续输入(0 结束): 0
最终结果: 35

循环体里面必须有能改变条件的操作(这里是我们每次重新读入 value),否则就会变成死循环。

不是意图内的死循环是 while 最常见的坑。如果循环体里没有任何操作能让条件变成 false,程序就会一直跑下去永远不退出。比如忘了写 std::cin >> value; 这一行,value 永远不变,条件永远为真。写 while 循环的时候,养成习惯检查"循环体里有没有改变条件的代码"。

do-while 循环:先做一次再说的 while 循环变体 ​

do-while 和 while 很像,只有一个关键区别:循环体至少执行一次。条件检查放在循环体之后:

C++
do {
    // 循环体
} while (condition);  // 注意这里有个分号!

因为"先做后判"的特性,do-while 特别适合菜单系统这种场景——菜单至少得显示一次,然后根据用户选择决定是否继续:

C++
int choice = 0;
do {
    std::cout << "\n=== 菜单 ===" << std::endl;
    std::cout << "1. 打印问候  0. 退出" << std::endl;
    std::cout << "请选择: ";
    std::cin >> choice;
    if (choice == 1) {
        std::cout << "你好!欢迎学习 C++!" << std::endl;
    }
} while (choice != 0);

do-while 末尾的分号千万别忘。漏掉的话,编译器会把下一行代码当成 while 的循环体来解析,报错信息可能非常诡异。这是 C++ 里少数几个必须在 } 后面加分号的地方之一,和 if、while、for 都不一样,很容易搞混。

PS,可能有朋友见过 do { /* 一些代码 */ } while(0); 这种代码宏,在C里非常常见,但是笔者要说明的是,在今天的编译器的环境下,这个写法纯粹多余。函数完全可以替代。

另外一位旗鼓相当的选择:for 循环 ​

当循环次数已知的时候,for 循环是最清晰的选择。它把初始化、条件判断和递增操作都集中在一行里,一眼就能看出循环的范围:

C++
for (init; condition; increment) {
    // 循环体
}

执行顺序是:先执行一次 init,然后检查 condition,为真就执行循环体,执行完做 increment,再回去检查 condition,如此往复。

C++
for (int i = 1; i <= 10; ++i) {
    // 这个输出这样读:从 i = 1 开始,一直执行到i不再满足 i <= 10 这个关系,逼近的方式是i自增1.
    std::cout << i << " ";
}
// 注意,出了这个for循环,i 就没办法使用了!
// 输出: 1 2 3 4 5 6 7 8 9 10

for 还支持同时操作多个变量,用一个经典的双指针翻转来演示:

C++
int data[] = {1, 2, 3, 4, 5};
int n = 5;

// 双指针从两端向中间走,交换元素
for (int i = 0, j = n - 1; i < j; ++i, --j) {
    int temp = data[i];
    data[i] = data[j];
    data[j] = temp;
}
// data 现在是 {5, 4, 3, 2, 1}

初始化部分声明了 i 和 j 两个变量,递增部分同时做 ++i 和 --j,从两端向中间逼近,相遇时停止。

差一错误(off-by-one error)是 for 循环最经典的坑。本意是循环 10 次,写成 for (int i = 1; i < 10; ++i) 就只跑了 9 次。一个实用建议:养成固定习惯。您有两个范式选。

  • 要么总是从 0 开始用 <(for (int i = 0; i < n; ++i))
  • 要么从 1 开始用 <=(for (int i = 1; i <= n; ++i))

不要混用,混用就是差一错误的温床。

循环中间的确需要打断了:那就用"紧急出口" break 和 continue ​

break 立即跳出当前循环,不再判断条件,就跟break的含义一样——打破我们的循环!;continue 跳过当前轮次剩余代码,直接进入下一轮迭代。

C++
int data[] = {4, 7, 2, 9, 5, 1};
int target = 9;

for (int i = 0; i < 6; ++i) {
    if (data[i] == target) {
        std::cout << "找到 " << target << ",下标为 " << i << std::endl;
        break;  // 找到了,不用继续搜了
    }
}
// 输出: 找到 9,下标为 3

continue 的例子——打印 1 到 20 之间的奇数:

C++
for (int i = 1; i <= 20; ++i) {
    if (i % 2 == 0) {
        continue;  // 偶数跳过
    }
    std::cout << i << " ";
}
// 输出: 1 3 5 7 9 11 13 15 17 19

注意 break 只能跳出最内层的循环。嵌套两层时,内层的 break 只会跳出内层,外层该怎么转还怎么转。想一次性跳出多层循环,通常用一个标志变量配合外层条件判断,或者把逻辑封装成函数用 return 退出。

break 和 continue 用多了会让代码逻辑变得支离破碎,读代码的人得在脑子里跳来跳去地追踪执行流程。如果一个循环体里有超过两三个 break 或 continue,就该考虑是不是该把循环条件写得更清晰,或者把部分逻辑抽成单独的函数。简单直接的循环条件永远比到处 break 更容易维护。

第五步——嵌套循环:循环里的循环 ​

循环体里面可以再放一个循环,它解决的是"对每一行做 X,每一行里的每一列做 Y"这类二维问题。来看经典的九九乘法表:

C++
#include <iostream>
#include <iomanip>  // std::setw

int main()
{
    for (int i = 1; i <= 9; ++i) {
        for (int j = 1; j <= i; ++j) {
            std::cout << j << "x" << i << "=" << std::setw(2) << i * j << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

运行结果:

text
1x1= 1
1x2= 2 2x2= 4
1x3= 3 2x3= 6 3x3= 9
1x4= 4 2x4= 8 3x4=12 4x4=16
...
1x9= 9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81

外层循环控制行号 i,内层循环控制列号 j,j 从 1 遍历到 i,打印出来是三角形。std::setw(2) 让输出项占 2 个字符宽度,一位数和两位数能对齐。

嵌套循环的执行次数是各层循环次数的乘积。外层 N 次、内层 M 次,总共 N * M 次内层循环体。对于 N=1000 的双层嵌套,内层要执行一百万次——所以心里要有这个概念:数据量大的时候,嵌套层数越少越好。

完整实战——loops.cpp ​

我们把前面学的几种循环综合到一个程序里:九九乘法表、猜数字小游戏(while + break)、金字塔图案打印(嵌套 for)。

展开代码收起代码共 71 行
C++
// loops.cpp -- 综合循环练习
// 编译: g++ -Wall -Wextra -o loops loops.cpp

#include <iostream>
#include <iomanip>

/// @brief 打印九九乘法表
void print_multiplication_table()
{
    std::cout << "=== 九九乘法表 ===" << std::endl;
    for (int i = 1; i <= 9; ++i) {
        for (int j = 1; j <= i; ++j) {
            std::cout << j << "x" << i << "=" << std::setw(2) << i * j << " ";
        }
        std::cout << std::endl;
    }
}

/// @brief 猜数字游戏,演示 while + break 的配合
void guess_number_game()
{
    const int kSecret = 42;
    int guess = 0;
    int attempts = 0;

    std::cout << "\n=== 猜数字游戏 ===" << std::endl;
    std::cout << "我想了一个 1-100 之间的数字,你来猜!" << std::endl;

    while (true) {
        std::cout << "你的猜测: ";
        std::cin >> guess;
        ++attempts;

        if (guess == kSecret) {
            std::cout << "恭喜!你用了 " << attempts << " 次猜中了!" << std::endl;
            break;
        } else if (guess < kSecret) {
            std::cout << "太小了,再试试。" << std::endl;
        } else {
            std::cout << "太大了,再试试。" << std::endl;
        }
    }
}

/// @brief 打印由星号组成的金字塔
void print_pyramid()
{
    const int kHeight = 5;

    std::cout << "\n=== 金字塔图案 ===" << std::endl;
    for (int row = 1; row <= kHeight; ++row) {
        // 打印前导空格
        for (int space = 0; space < kHeight - row; ++space) {
            std::cout << " ";
        }
        // 打印星号(第 row 行有 2*row - 1 个星号)
        for (int star = 0; star < 2 * row - 1; ++star) {
            std::cout << "*";
        }
        std::cout << std::endl;
    }
}

int main()
{
    print_multiplication_table();
    guess_number_game();
    print_pyramid();

    return 0;
}

编译运行:

bash
g++ -Wall -Wextra -o loops loops.cpp
./loops
text
=== 九九乘法表 ===
1x1= 1
1x2= 2 2x2= 4
...(中间省略)
1x9= 9 2x9=18 ... 9x9=81

=== 猜数字游戏 ===
你的猜测: 50
太大了,再试试。
你的猜测: 25
太小了,再试试。
你的猜测: 42
恭喜!你用了 3 次猜中了!

=== 金字塔图案 ===
    *
   ***
  *****
 *******
*********

来拆一下金字塔的逻辑。第 row 行需要 kHeight - row 个前导空格让星号居中,然后打印 2 * row - 1 个星号。这个 2n-1 的规律在图案打印中非常常见。猜数字游戏里的 while (true) + break 也是一种经典模式——当退出条件不容易浓缩成一个布尔表达式时,在循环体内部判断然后 break 是一种清晰的做法。

在线运行 ​

在线运行综合循环示例,观察九九乘法表、金字塔图案和素数筛选的输出:

Compiler Explorer

循环语句综合演示:乘法表、金字塔、素数

在线运行并观察 for 循环、嵌套循环和 break 的综合运用。试着修改 kHeight 或素数范围。

code/examples/vol1/06_loops.cpp

动手试试 ​

光看懂不够,得自己写一遍才算真的会。以下是四个练习,建议每个都动手完成。

练习一:打印空心正方形 ​

输入一个正整数 N,打印一个 N x N 的空心正方形。比如 N=5 时:

text
 *  *  *  *  *
 *           *
 *           *
 *           *
 *  *  *  *  *

只有第一行、最后一行、第一列和最后一列打印星号,中间全是空格。提示:用嵌套 for 循环,内层判断当前是不是边界位置。

参考答案
展开代码收起代码共 29 行
C++
#include <iostream>

int main()
{
    int n = 0;
    std::cout << "输入一个正整数 N: ";
    if (!(std::cin >> n) || n < 1)
    {
        std::cout << "输入无效,请输入一个正整数!" << std::endl;
        return 1;
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            if ((i != 1) && (i != n))
            {
                if ((j != 1) && (j != n))
                {
                    std::cout << "   ";
                    continue;
                }
            }
            std::cout << " * ";
        }
        std::cout << std::endl;
    }
    return 0;
}

编译运行:

bash
g++ -std=c++17  -Wall -Wextra main.cpp -o main &&./main

运行结果:

text
输入一个正整数 N: 5
 *  *  *  *  *
 *           *
 *           *
 *           *
 *  *  *  *  *

练习二:计算阶乘 ​

用 for 循环计算 N 的阶乘(N!)。比如 5! = 120。注意阶乘增长极快,用 int 的话 13! 就会溢出,可以试试 long long 能撑到多大。

参考答案
展开代码收起代码共 21 行
C++
#include <iostream>

int main()
{
    int n = 0;
    long long factorial = 1;
    std::cout << "输入一个正整数 N: ";
    if (!(std::cin >> n) || n < 1)
    {
        std::cout << "输入无效,请输入一个正确的正整数!" << std::endl;
        return 1;
    }

    for (int i = n; i >= 1; i--)
    {
        factorial *= i;
    }
    std::cout << n << "的阶乘" << "(" << n << "!): " << factorial << std::endl;

    return 0;
}

编译运行:

bash
g++ -std=c++17  -Wall -Wextra main.cpp -o main &&./main

运行结果:

text
输入一个正整数 N: 5
5的阶乘(5!): 120

练习三:找素数 ​

输入一个正整数 N,打印从 2 到 N 之间所有的素数。判断素数的方法:对于数 m,检查 2 到 m-1 之间有没有能整除 m 的数,如果没有就是素数。提示:外层循环遍历候选数,内层循环做整除检查,找到因子后用 break 提前退出内层。

参考答案
展开代码收起代码共 33 行
C++
#include <iostream>

int main()
{
    int n = 0;
    bool flag=0;
    std::cout << "输入一个正整数 N: ";
    if (!(std::cin >> n) || n < 2)
    {
        std::cout << "输入无效,请输入一个正确的正整数!" << std::endl;
        return 1;
    }
    std::cout << "2" << "到" << n << "之间所有的素数: " << std::endl;
    for (int i = 2; i <= n; i++)
    {
        flag = 0;
        for (int j = 2; j <= i - 1; j++)
        {

            if (i % j == 0)
            {
                flag = 1;
                break;
            }
        }
        if (flag != 1)
        {
            std::cout << i << std::endl;
        }
    }

    return 0;
}

编译运行:

bash
g++ -std=c++17  -Wall -Wextra main.cpp -o main &&./main

运行结果:

text
输入一个正整数 N: 5
2到5之间所有的素数:
2
3
5

练习四:打印菱形 ​

输入一个奇数 N,打印一个 N 行的菱形图案。比如 N=5 时:

text
  *
 ***
*****
 ***
  *

提示:上半部分和金字塔一样,下半部分是金字塔的镜像——行号从大到小。

参考答案
展开代码收起代码共 36 行
C++
#include <iostream>

int main()
{
    int n = 0;
    std::cout << "输入一个正奇数 N: ";

    if (!(std::cin >> n) || n <= 0 || n % 2 == 0)
    {
        std::cout << "输入无效,请输入一个正确的正奇数!" << std::endl;
        return 1;
    }

    const int middleRow = (n + 1) / 2;
    for (int row = 1; row <= n; ++row)
    {
        const int stars = row <= middleRow
                              ? 2 * row - 1
                              : 2 * (n - row) + 1;
        const int spaces = (n - stars) / 2;

        for (int column = 0; column < spaces; ++column)
        {
            std::cout << ' ';
        }

        for (int column = 0; column < stars; ++column)
        {
            std::cout << '*';
        }

        std::cout << std::endl;
    }

    return 0;
}

编译运行:

bash
g++ -std=c++17  -Wall -Wextra main.cpp -o main &&./main

运行结果:

text
输入一个正奇数 N: 5
  *
 ***
*****
 ***
  *

pdf-latest-4-g85128cc · 85128cc · 2026-10-05