---
title: "循环"
description: "从环境准备到 STL，系统学习 C++ 编程的基础知识。"
---

> Documentation Index
> Fetch the complete documentation index at: https://cppguide.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# 循环

![](https://img.dailyup.xyz/originals/8208463152172-d11e0665-f2e2-4e24-8f5c-3f002d68c190.png)

在编写程序时，程序的逻辑构成，通常有三种基本的流程结构，包括：

-   顺序（Sequence）
-   分支（Condition）
-   循环（Loop）

**顺序结构**是代码从上到下，依次执行；**分支结构**往往与条件判断结构结合起来使用，代码在执行的过程中会遇到岔路，需要根据条件来选择去走哪一条路；而**循环结构**是在执行的过程中，当满足循环的条件判断后，就会重复执行指定的某一段代码，直到条件不满足退出循环为止。

**对于多次重复做同一件事情这类问题，都可以用循环结构去完成。**

前面我们已经讲过顺序结构与分支结构，接下来我们来看看循环结构，下面是循环结构的一个简单流程示意图。

![](https://img.dailyup.xyz/optimized/8208463151568-21356d3a-d6bf-4918-9ad3-305ba252882b.png)

很多时候，我们需要多次重复做一件事情时，就需要用到循环结构。例如，我们要将 1 ～ 100 的所有整数打印出来，我们总不能像下面这样一行一行的打印：

```cpp
cout << 1 << endl;
cout << 2 << endl;
cout << 3 << endl;
...
cout << 100 << endl;
```


这样要写 100 次输出语句 ，那得累死了，循环结构就是为了解决这种麻烦而设计的。

在生活中，也有许多与循环执行相关的现象。比如说在操场跑步，一圈 400 米，如果要跑 10 公里，那就要绕着操场重复跑 25 圈，也就是重复跑 25 次 400 米。

在数学中，有一些运算符号最初也是为了解决重复运算的麻烦而引入的。比如说乘法，最简单的理解，乘法就是重复的加法，在引入乘法之前，4 + 4 + 4 + 4 + 4 就要写很多遍，而引入乘法符号可以直接写成 4 × 5，其实是代表 5 个 4 相加（或 4 个 5 相加），用重复来表述，就是重复加 5 次 4 。

在 C++ 中，有下面三种方式来实现循环结构。

- `while` 循环 
- `do while` 循环 
- `for` 循环

这三种方式都有自己的适用场景，在学习时注意区分他们相同和不同的地方。

## 一、while 循环

```cpp
循环的初始条件
while (满足循环的条件判断):
	需要重复的逻辑功能代码
	每一轮循环条件的变动
```

例如，像前面要输出 1 ~ 100 之间的所有整数，可以像下面这样写：

```cpp
int i = 1; // 循环的初始条件
while (i <= 100) { // 满足循环的条件判断
  cout << i << endl; // 需要重复的逻辑代码
  i = i + 1; // 每一轮循环条件的变动
}
```

如果把上面的 `100` 改成 `1000`，那就是打印 1 ~ 1000 的所有整数了。是不是简单太多了？

写 `while` 循环的代码时，下面这三个条件需要注意：

1. **初值**，循环的初始条件，也就是这里的`int i = 1`
2. **条件**，满足并进入循环的条件，也就是这里的 `i <= 100`， 在这里表示只有当满足 `i` 小于等于 `100` 这个条件时，才会执行循环体内的代码；如果不满足这个条件，则退出循环。
3. **步进**，每轮循环条件变动，也就是这里的 `i = i + 1`，大部分情况下，都需要在每一轮循环中更改循环的条件，直接某一轮条件不满足，就退出循环。

下面列表显示了打印 1 ～ 100 的代码执行过程中的数据变化。

![](https://img.dailyup.xyz/optimized/8208463149602-75dd9bb1-9915-4331-a2a7-f0ef89ed5394.png)

实际上，**上面的三个条件缺一不可**。刚开始学习编程的同学经常会忘记掉某一个条件，造成程序执行的错误。大家可以试试，将某一个条件去掉，例如去掉第 3 个条件，比如去掉 `i = i + 1`，执行时会出现什么情况呢？

```cpp
int i = 1;
while (i <= 100) {
  cout << i << endl;
}
```

很可能你会发现程序卡死没反应了，这是因为初始条件 `int i = 1`，而`1`永远小于 `100`，也就是进入循环的条件永远是满足的，这个程序会无限执行下去，程序进入所谓的**「死循环」**状态。也这是为什么必须要有第 3 个条件的原因。

下面我们尝试更改这三个条件中的一个或多个，来完成一些小练习。

上面演示了将 1 ~ 100 内的整数数字依次打印出来，稍微改动，如果要**将 1 ~ 100 内的所有奇数依次打印出来**，程序该怎么写呢？

实际上，只需要更改第 3 个条件，将 `i = i + 1` 更改为 `i = i + 2` 就行了，代码如下：

```python
# 依次打印出 1 ~ 100 内的所有奇数
int i = 1;
while (i <= 100) {
  cout << i << endl;
  i = i + 2;
} 
```

想一想是不是对的，`i` 的初始值是 1，第 1 次执行则打印出 1，紧跟着 `i = i + 2` ，这句执行后 `i` 就变成 3 了，再进入循环则打印出 3，依次下去，就会将 1 ~ 100 中所有的奇数打印出来。

那如果要将 1 ~ 100 内的所有偶数依次打印出来呢？只需要在打印奇数的程序基础上，再更改第 1 个条件，将初始值更改为 2 就可以了。

```cpp
# 依次打印出 1 ~ 100 内的所有偶数
int i = 2;
while (i <= 100) {
  cout << i << endl;
  i = i + 2;
} 
```

有的同学说，我们前面学过如何判断偶数和奇数，我们可以在循环中用一个条件判断来控制打印奇数还是偶数，可以吗？

当然可以，用这种方法来打印偶数的代码如下：

```cpp
// 方法二：依次打印出 1 ~ 100 内的所有偶数
int i = 1;
while (i <= 100) {
  if (i % 2 == 0) {
    cout << i << endl;
  }
  i = i + 1;
}
```

活学活用，非常棒！给大家留一个小问题：仅仅从这个题目的要求来说，你觉得方法一更好一点，还是方法二更好一点呢？说说你的理由。

好吧，虽然有点无聊，但还是要「刁难」一下大家，在上面几个程序的的基础上，再稍微变化一点，**要求按倒序来打印出结果**，例如，同样是打印 1 ～ 100 内的所有整数，但倒着来，从 100 开始输出，一直到 1 ，程序应该怎么修改呢？

```cpp
// 倒序打印出 1 ~ 100 内的所有整数
int i = 100;
while (i >= 1) {
  cout << i << endl;
  i = i - 1;
} 
```

来看一个例子。

### 示例：奇偶数判断

**题目描述**

输入 $n$ 个整数，如果是整数则输出 "Y"；如果是奇数则输出 "N"

**输入描述**

输入为两行。

- 第一行为一个正整数 $n$，代表接下来要输入的数字个数
- 第二行为 $n$ 个正整数，代表要判断的数字

**输出描述**

输出为一行，根据题目要求输出 "Y" 或 "N"


**输入数据**

```
5
8 9 2 5 1
```

**输出数据**

```
Y N Y N N
```

**代码实现**

```cpp
#include <iostream>
using namespace std;

int main() {
  int k;
  cin >> k;
  
  while (k--) {
    int t;
    cin >> t;
    if (t % 2 == 0) cout << "Y" << " ";
    else cout << "N" << " ";
  }
  
  return 0;
}
```

**代码说明**

注意这里 `k--` 的用法，等价于下面的写法：

```cpp
while (k) {
  // 代码逻辑
  k--；
}
```

直接写在 `while` 条件里代码更简洁一些。

另外，在 C++ 中，任何非零的数如果直接作为条件，相当于 `true` ，0 相当于 `false`，这一点需要注意。

### 示例: 整数数字之和


输入一个正整数 $n$，求这个数的所有位上的数字之和。

例如，输入数字 `12345`，因为 `1+2+3+4+5=15`，因此输出 `15`。

**代码实现**

```cpp
#include <iostream>
using namespace std;

int main() {
  int n, s = 0;
  cin >> n;
  
  while (n > 0) {
    s = s + n % 10;
    n = n / 10;
  }
  
  cout << s << endl;
  return 0;
}
```
## 二、do while 循环

`do ... while` 循环是 `while` 循环的变形，形式如下：

```cpp
do {
  // 循环体
} while (条件);
```

与 `while` 循环最大的不同是：

- `while` 是先判断条件再执行循环体的内容
- `do...while` 是先执行后判断条件

也就是说，使用 `do...while` ，**无论是否满足条件， 都一定会执行一次循环。**

来看一个简单的例子：

```cpp
int i = 1;
do {
  i++;
} while (i > 5);

cout << i << endl; // 输出：2
```

尽管 `i` 的初始值为 1，不大于 5，但也会执行一次循环，从而导致 `i` 增加 1 。

对比一下 `while` 循环：

```cpp
int i = 1;
while (i > 5) {
  i++;
}

cout << i << endl; // 输出：1
```

由于条件（`i > 5`）不满足，永远不会进入循环。

## 三、for 循环

`for` 循环是另一种常见的循环结构形式，它的语法格式为：

```cpp
for (初值; 条件; 步进) {
  // 循环的功能逻辑
}
```

这里的 `初值`、`条件`、`步进` 分别对应于 `while` 循环中的三个条件。

不同的是 `while` 里这三个条件是分开给出的，而 `for...in` 是放在一起的，在写法上比较简练，更不容易出错。

## 四、选择 `while` 还是 `for`

大多数情况下，能用 `while` 实现的循环，也可以用 `for` 循环完成，反过来也是。

当然，既然设计了这两种不同的循环类别，一定有不同的适用场景。

- 当明确知道循环执行的次数时，建议用 `for` 循环
- 当不知道明确的循环执行次数时，建议用 `while` 循环

相对来说，`for` 更规范一些，循环的三个条件一般是显示给出；而 `while` 循环更灵活一些，更关心进入循环的那个条件。

通过下面这个纸张对折的问题，大家也可以体会一下这两者的不同。

### 1. 纸张对折问题 - for

题目要求，如果给你一张无限大的白纸，白纸的厚度为 0.0001 米，请问，将对纸对折 20 次后，对折后的纸有多厚呢（以米为单位，结果保留到小数后两位）？

> 建议大家可以拿一张普通 A4 白纸，尽最大可能试一试，看看你能将这张纸对折多少次呢？

这个问题的**核心逻辑是，每对折一次，其厚度翻一倍**，通过循环可以完成，实现代码如下：

```cpp
#include <iostream>
#include <cstdio>
using namespace std;

int main() {
  int n;
  cin >> n;
  double h = 0.0001; 
  
  for (int i = 1; i <= n; i++) {
    h = h * 2;
  }
  
	printf("%.2f", h);
  return 0;
}
```

**输入数据**

```
20
```

**输出数据**

```
104.86
```

结果超过想像，竟然有一百多米厚。

### 2. 纸张对折问题 - while

同样的对折问题，换个角度来看，同样的白纸，请编写程序计算一下，对折多少次后，厚度就会超过珠穆朗玛峰呢？

> 我们知道，位于中国与尼泊尔边境的珠穆朗玛峰是世界最高峰，海拔高度是8848.86米

```cpp
#include <iostream>
using namespace std;

int main() {
  double k;
  cin >> k;
  
  double h = 0.0001;
  int i = 0;
  
  while (h < k) {
    h = h * 2;
    i = i + 1;
  }
  
  cout << i << endl;
  return 0;
}
```

**输入数据**

```
8848.86
```

**输出数据**

```
27
```

> 编程对于我们理解特别大的数，以及理解数的增长是非常有用的，因为在现实生活中，我们很难去模拟出这种增长所带来的变化，而编程给我们提供了这样一个无限潜能的模拟工具！


## 五、多层循环

**多层循环通常也叫嵌套循环，也就是循环里还有循环**。在几乎所有编程语言中，无论是条件分支结构，还是循环结构，都是可以多层嵌套使用的。

嵌套循环在有的时候是非常有用的，但**为了便于阅读和理解代码，循环嵌套的层数不宜过多，一般来说不超过三层**。

下面来看一个例子。

### 示例: 打印九九乘法表

利用嵌套循环打印输出九九乘法表

**代码实现**

> 这里为了显示方便，只打印了其中一部分乘法表（5 列）

```cpp
#include <iostream>
using namespace std;

int main() {
  for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
      printf("%d*%d=%d\t", j, i, i*j);
    }
    printf("\n");
  }
  return 0;
}
```

**输出结果**

```
1*1=1	
1*2=2	2*2=4	
1*3=3	2*3=6	3*3=9	
1*4=4	2*4=8	3*4=12	4*4=16	
1*5=5	2*5=10	3*5=15	4*5=20	5*5=25	
```

**代码说明**

本题中使用 `printf()` 输出更加方便一些，其中 `\t` 是每输出一个乘法算式后输出一个制表符（tab），制表符宽度根据不同的环境会有所不同，一般来说是 4 个或 2 个空格。

### 示例: 打印数字方阵

输入一个正整数 n，输出 n 行，每行都是从1到 n 的所有正整数。

**输入数据**

```
5
```

**输出数据**

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

**实现代码**

```cpp
#include <iostream>
using namespace std;

int main() {
  int n;
  cin >> n;
  
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
      cout << j << " ";
    }
    cout << endl; // 每一行输出完后，换行
  }
    
  return 0;
}
```

**代码说明**

在双层循环中，通常外层循环用于控制有多少行，内层循环用于控制每一行的所有列。

参考后面「模式与编程」的独立章节，可以看到更多利用双层循环输入数字、符号或字母图案的练习。

## 六、循环跳转

前面都是讲的循环，但在有的时候，在循环内部，当满足一定条件时，我们希望打破正常的循环流程，该怎么办？

在 C++ 中，提供了下面几种打破正常循环流程的跳转方式：
- `break` ，直接跳出当前层的循环
- `continue`，跳过当前这一轮循环（`continue`之后的代码都不会执行了），继续下一轮
- `return` 跳转

### 1. `break` 终止循环

来看一个简单的例子

```cpp
for(int i = 1; i <= 10; i++) {
  if (i % 3 == 0) break;
  cout << i << endl;
}
```

程序运行结果：

```
1
2
```

如果没有第 2、3 行，这段代码运行的结果是直接输出 1 ～ 10，但加上第 2、3 行， 表示一旦遇到 `i` 是 3 的倍数 ，循环就终止了。而第 1 次遇到 `i` 是 3 的倍数也就是当 `i` 等于 3 的时候，因此程序的运行结果只会输出 1 和 2 。

### 2. `continue` 退出本轮循环

`continue` 也是一种退出循环的方式，用于跳过当前一轮循环，跳过后继续进行后面的循环。

将上面示例中的 `break` 更改为 `continue`，来看看两个程序的输出有什么不同。

```cpp
for(int i = 1; i <= 10; i++) {
  if (i % 3 == 0) continue;
  cout << i << endl;
}
```

程序运行结果：

```
1
2
4
5
7
8
10
```

下面再来看一个比较典型的退出循环相关的例子。

### 3. `return` 跳转

严格意义上 `return` 并不算标准退出循环方式，主要用于直接返回函数的值，将控制权交给调用函数的地方，由于我们写的 C++ 本身就是在一个主函数（`main`）内，因此也可以用 `return` 来实现跳出循环的作用。

回想一下，我们在 `main` 函数的最后一行 `return 0`，当遇到这一行时，就表示直接返回 `main` 函数的值了（将控制权交给调用 `main` 函数的 C++ 编译器了，告诉调用方，该函数正常结束了）。

看下面这个程序：

```cpp
#include <iostream>
using namespace std;

int main() {
  int n;
  cin >> n;
  
  for (int i = 1; i <= n; i++) {
    if (i % 3 == 0) {
      return 0;
    }
    cout << i << endl;
  }
  cout << "bye" << endl;
  return 0;
}
```

程序运行结果：

```
1
2
```

注意，我们程序里多加了一个，输出字符串 "bye"，但最终的结果是没有的，也正好说明了 `return 0` 的作用，是结束整个程序，在 `return 0` 后的所有代码都不会再执行了。


## 七、求和与计数
### 求 1 + 2 + ... + n 的和

输入一个正整数 n，求 1 + 2 + 3 + ... + n 的和


**输入数据**

```
100
```

**输出数据**

```
5050
```

**参考代码**

```cpp
#include <iostream>
using namespace std;

int main() {
  int n, sum = 0;
  cin >> n;
  
  for (int i = 1; i <= n; i++) {
    sum = sum + i;
  }
  
  cout << sum << endl;
  return 0;
}
```


理解了累加求和的做法，还可以配合条件判断来筛选，完成不同情况下的数字之和，例如，下面代码求 `1 ~ n` 中所有偶数的和。

```cpp
#include <iostream>
using namespace std;

int main() {
  int n, sum = 0;
  cin >> n;
  
  for (int i = 1; i <= n; i++) {
    if (i % 2 == 0) {
      sum = sum + i;
    }
  }
  
  cout << sum << endl;
  return 0;
}
```


## 八、质数判断

**题目描述**

判断一个大于 1 的数是否是质数，如果是质数则输出 "Y"，否则输出 "N"。

**题目分析**

回顾一下质数的定义，质数（Prime number），又称素数，是指在大于 1 的自然数中，除了 1 和该自数自身外，无法被其它自然数整除的数。也就是说，**一个质数，只能被 1 和它本身整除**。

**代码实现**

```cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    
    bool is_prime = true;
    
    for (int i = 2; i < n; i++) {
      if (n % i == 0) {
        is_prime = false;
        break;
      }
    }
    
    if (is_prime) {
      cout << "Y" << endl;
    } else {
      cout << "N" << endl;
    }
    
    return 0;
}
```

这里的判断的核心逻辑在于代码中的 `for` 循环中的代码。

`for` 循环中，i 取值从 2 ~ n-1，也就是除 1 和 本身以外的数的范围，一旦有任何一次被这个范围内的数整除的情况出现，那这个数一定不是质数，剩下的就不用再判断了，直接使用 `break` 直接退出循环。

标志位 `is_prime` 是为了后面根据该标志位来输出最终判断的结果。

**想一想：** 如果这里把程序中的 `break` 这条语句去掉，程序运行的结果会有不同吗？程序的执行过程与去掉前有什么不同呢？


## 九、任务练习

### 1. 整除几次2

**题目描述**

请问一个正整数 $n$ 能够整除几次 2？

例如： 
- 输入 `n = 4`，由于 4 可以整除 2 次 2，因此输出 2
- 输入 `n = 9`，由于 9 不能被 2 整除 ，因此输出 0

**代码实现**

```cpp
#include <iostream>
using namespace std;

int main() {
  int n, cnt = 0;
  cin >> n;
  
  while (n % 2 == 0) {
    n = n / 2;
    cnt++;
  }
  
  cout << cnt << endl;
  return 0;
}
```

### 2. 求Fibonacci 数列的第 n 项

**题目描述**

输入正整数 n，求斐波那契数列的第 n 项，并打印出来

**什么是斐波那契数列：**

**斐波那契数列（Fibonacci sequence）**，又称黄金分割数列，这个数列是由意大利数学家莱昂纳多·斐波那契（Leonardo Fibonacci）在他的《算盘书》中提出而得名。

在数学上，**斐波那契数**是以递归的方法来定义，数学表达如下所示：

$\begin{cases}  & F_0=0 \\  & F_1=1 \\ & F_n=F_{n-1} + F_{n-2}\end{cases}$

用文字来说，就是斐波那契数列是由 0 和 1 开始，之后的斐波那契数是由之前的两数相加而得。

前面的几个斐波那契数是：

1、1、2、3、5、8 ......

> 需要注意的是，0 是第零项，而不是第一项


**输入数据**

```
8
```

**输出数据**

```
21
```

**代码实现**

```cpp
#include <iostream>
using namespace std;

int main() {
  int n, f;
  cin >> n;
  
  int f0 = 0;
  int f1 = 1;
  for (int i = 2; i <= n; i++) {
    f = f0 + f1; // 每项等于前两项的和
    f0 = f1; // 接力往后传递 f0
    f1 = f; // 接力往后传递 f1
  }
  if (n == 0) f = f0; // 第 0 项特殊处理
  else if (n == 1) f = f1; // 第 1 项特殊处理
  
  cout << f << endl;
  return 0;
}
```


### 3. 含 0 的数有多少个

**题目描述**

请求出 1 ~ $n$ 中含有数字 0 的数，共有多少个？

**输入描述**

一个整数 n（$n \le 999$）

**输出描述**

一个整数，代表 1 ～$n$ 中含有数字 0 的数的个数。


**输入数据 1**
```
80
```

**输出数据 1**

```
8
```

**输入数据 2**
```
999
```

**输出数据 2**

```
180
```

**代码实现**

```cpp
#include <iostream>
using namespace std;

int main() {
  int n, cnt = 0;
  cin >> n;
  for (int i = 1; i <= n; i++) {
    int j = i;
    while (j > 0) {
      if (j % 10 == 0) {
        cnt++;
        break;
      }
      j = j / 10;
    }
  }
  cout << cnt << endl;
  return 0;
}
```

## 十、小结

循环结构是编程中最常用的控制结构之一，使用循环结构可以简化代码，避免重复编写相同的代码段，还可以提高代码的可读性和可维护性。同时，合理使用循环结构可以实现迭代、遍历、计数等各种功能，为程序的实现提供灵活性和效率。

Source: https://cppguide.xyz/basic/loop/index.mdx
