【c++】【基础】【primer_plus】【第五章】循环语句

前言

顺序、选择、循环,是无论任何语言都必须要实现的机制,有了这三种语句,我们才有可能实现比较复杂的逻辑。本章便是对c++语言的循环语句进行简单的介绍。

循环语句的意思就是可以重复执行某些语句。比如,我们要计算一个整型数组所有元素的和,或者我们想计算10的阶乘等,这都需要重复相同的逻辑,只不过输入的值不同罢了。

c++主要有for循环while循环do_while循环等最为常用。c++11标准增加了foreach循环使其运用更加灵活。

for循环

for循环的一般格式如下。

1
2
3
for(init; test; update) {       // 初始化; 测试条件; 更新(步长)
statement;
}

下面是一些例子。

1
2
3
for(int i = 0; i < 10; ++i) {
cout << i << endl;
}
1
for(int i = 0; i <= 10; i += 2) cout << i << endl;
1
for(int i = vi.size()-1;  i >= 0; --i) cout << vi[i] << endl;

可以看出,for循环的初始化语句可以用来声明变量,而测试条件则是一个逻辑表达式,最后一个步长表示变量在每一步的变化。其执行过程大致如下。

先执行初始化语句。

再执行判断语句。此时如果判断语句为真,则执行循环体内代码,否则结束,跳出循环。

当循环体执行完后,执行步长更新语句,更新相关变量。

步长更新语句执行完后回到第二步,重新执行判断语句,依次循环,直到跳出。

需要注意的是,for循环中的三条表达式都是可以省略的,即下面的语句也是合法的,它会一直输出hello world,即死循环。也可以看出省略的判断语句默认为true

1
for( ; ; ) { cout << "hello world"; }

递增与递减

1
2
3
4
5
int a = 5;
int b = ++a; // b = 6, a = 6

a = 5;
int c = a++; // c = 5, a = 6
1
2
3
4
5
int a = 5;
int b = --a; // b = 4, a = 4

a = 5;
int c = a--; // c = 5, a = 4

如上代码所示,递增运算符++和递减运算符--都用来表示一个变量递增或者递减一个单位。但是结果却有一些区别,这里以递增++为例。

递增++分为两类,一类为前缀++,一类为后缀++。其区别如下。

前缀++表示先进行加一操作,再以当前值计算表达式,形如++a

后缀++表示先以当前值计算表达式,再进行加一操作,形如a++

上述代码中,b = ++a等价于如下代码。

1
2
a = a + 1;
b = a;

b = a++则等价于如下代码。

1
2
b = a;
a = a + 1;

注意,同一条语句尽量不要使用多个++,容易引起在不同的系统上(不同的编译与汇编原理)将会有不同的结果。

1
x = 2 * x++ * (3 - ++x);    // 在我的系统上输出为0

从效率上来讲,推荐使用前缀++,因为后缀++的实现需要复制一个副本进行加一操作后再返回副本。

foreach循环

foreach循环是c++11标准增加的新的循环方式,它在一定程度上是对for循环的一种升级。其一般格式如下。

1
2
3
for(type element_name : array_name) {       // 元素类型 元素名称 : 数组名称
statement;
}

具体实例如下。

1
2
int a[5] = {1, 2, 3, 4, 5};
for(int i : a) cout << i << endl;

其中int为数组中元素的类型,i为数组的某个元素,它会遍历数组(或其他stl容器)的每一个元素,而冒号:后面的便是想要遍历的数组或容器。

若要修改原始数据的话可以使用引用(后面章节会讲到引用)。

1
for(int& i : a) i += 1;

也可结合初始化表使用。

1
for(int i : {1, 2, 3}) cout << i << endl;

while循环

1
2
3
4
5
init;                   // 初始条件
while (test) { // 测试条件
statement;
update; // 更新(步长)
}

如上代码为while循环的一般格式,其中inittestupdate语句分别与for循环的相对应。其执行过程大致如下。

首先执行初始化语句。

然后执行测试条件语句。如果测试条件为true,则进入循环体,循环体内大多时候都包含有步长更新语句(不一定在尾部)。如果测试条件为false,则不进入循环体。

一遍循环体执行结束之后,将继续执行测试条件,然后循环执行,直至跳出。

具体实例如下。

1
2
3
4
5
int i = 0;
while (i < 5) {
cout << i << endl;
++i;
}

再来举一个复杂一点的例子,我们使用while循环配合<ctime>头文件提供的api来编写带有延迟功能的循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <ctime>
using namespace std;

int main() {
float secs;
cin >> secs;

clock_t delay = secs * CLOCKS_PER_SEC; // 延迟时间

clock_t start = clock(); // 开始时间
cout << start << endl;
while (clock() - start < delay);
// 当前时间 - 开始时间 < 延迟时间时 等待

cout << "done\n";

return 0;
}

这段代码会在开始之后御延迟delay时间才会输出done

do_while循环

while循环不同的是,do_while循环是一种出口条件循环,即它会先执行循环体,再进行测试语句判断。其一般格式如下所示。

1
2
3
4
5
init;
do {
statement;
update;
} while (test);

具体实例如下。

1
2
3
4
5
int i = 0;
do {
cout << i <<< endl;
++i;
} while (i < 5);

可以看出,无论如何,do_while循环的循环体至少都要执行一遍,这也是它与while循环最大的区别,有时候能够合理地利用这一特征可以帮助简化某些特定的问题,尤其是某种至少需要执行一遍的重复操作。

跳出循环

我们使用break关键字和continue关键字跳出循环。

break直接跳出循环体。

continue跳出当前执行的这一轮循环体,重新开始下一轮。

循环和文本输入

我们经常使用while循环来持续输入文本,尤其是在打acm竞赛的时候。但是接下来的格式与acm的输入不同,主要解释一般的输入问题。

那么最重要的便是如何判断输入结束。一般我们会使用某种哨兵字符,即当输入该字符时结束输入。如下代码所示。

1
2
3
4
5
6
char ch;
cin.get(ch); // 不用cin>>ch是为了读取空格
while(ch != '#') {
cout << ch;
cin.get(ch);
}

当然,cin也自带了用于检测输入是否结束的标记,我们可以使用cin.eof()或者cin.fail()来判断输入是否已经结束,如下代码所示。

1
2
3
4
5
6
char ch;
cin.get(ch);
while(cin.fail() == false) {
cout << ch;
cin.get(ch);
}

除此之外,cin本身也可转为bool值作为判断输入是否结束的依据。

1
2
3
4
char ch;
while(cin.get(ch)) {
cout << ch;
}

这也是打acm或者刷题时经常使用的输入多组样例的方法,如下所示。

1
2
3
while(cin >> a >> b) {
cout << a + b << endl;
}

嵌套循环和二维数组

顺序、分支、循环这三种语句组成了复杂的逻辑,当然可以相互嵌套,所以循环可以嵌套也很容易理解。下面举一个嵌套二层循环以输出二维数组的例子。

1
2
3
4
5
6
7
8
9
int a[2][3] = {             // 二行三列
{1, 2, 3},
{4, 5, 6}
};
// 二层for循环输出
for(int i = 0; i < 2; ++i) {
for(int j = 0; j < 3; ++j) cout << a[i][j] << " ";
cout << endl;
}

输出结果如下。

1
2
1 2 3
4 5 6