语句和流程控制

一个简单的 C++ 语句是程序中的每个独立指令,例如前几节中看到的变量声明和表达式。它们总是以分号 (;) 结尾,并按照在程序中出现的顺序执行。

但程序并不局限于线性的语句序列。在处理过程中,程序可能会重复代码段,或者做出决策并分支。为此,C++ 提供了流程控制语句,用于指定程序在何时、何种情况下应该做什么。

本节中解释的许多流程控制语句都需要一个通用的(子)语句作为其语法的一部分。此语句可以是简单的 C++ 语句,如以分号 (;) 结尾的单个指令,或者是复合语句。复合语句是由一组语句(每个语句都以自己的分号结尾)组成的,但它们都被包含在一个用花括号 {} 括起来的代码块中。

{ statement1; statement2; statement3; }

整个代码块被视为一个单一的语句(它本身由多个子语句组成)。无论何时,只要一个通用语句是流程控制语句语法的一部分,它都可以是简单语句或复合语句。

选择语句:if 和 else

if 关键字用于在条件满足时执行语句或代码块,仅在此情况下执行。其语法是:

if (condition) statement

这里,condition 是正在被评估的表达式。如果此 condition 为真,则执行 statement。如果为假,则不执行 statement(仅忽略),程序将继续执行整个选择语句之后的代码。
例如,以下代码片段仅在 x 变量的值确实是 100 时才打印消息 (x is 100)

1
2
if (x == 100)
  cout << "x is 100";

如果 x 不等于 100,则忽略此语句,不打印任何内容。

如果您想在满足条件时包含多个语句,这些语句应包含在花括号 ({}) 中,形成一个代码块。

1
2
3
4
5
if (x == 100)
{
   cout << "x is ";
   cout << x;
}

与往常一样,代码中的缩进和换行没有影响,因此上述代码等同于:

1
if (x == 100) { cout << "x is "; cout << x; }

带有 if 的选择语句也可以通过使用 else 关键字引入替代语句来指定在条件不满足时会发生什么。其语法是:

if (condition) statement1 else statement2

其中,如果 condition 为真,则执行 statement1;否则,执行 statement2

例如:

1
2
3
4
if (x == 100)
  cout << "x is 100";
else
  cout << "x is not 100";

如果 x 的值确实是 100,则打印 x is 100;如果不是,则打印 x is not 100
可以串联多个 if + else 结构,以检查值范围。例如:

1
2
3
4
5
6
if (x > 0)
  cout << "x is positive";
else if (x < 0)
  cout << "x is negative";
else
  cout << "x is 0";

通过串联两个 if-else 结构,此代码可以判断 x 是正数、负数还是零。同样,也可以通过将多个语句分组到用花括号 {} 括起来的代码块中来执行每个情况下的多个语句。

迭代语句(循环)

循环会重复执行一条语句,重复一定的次数,或者只要某个条件为真。它们由关键字 whiledofor 引入。

while 循环

最简单的循环是 while 循环。其语法是:

while (expression) statement

while 循环会在 expression 为真时重复执行 statement。如果在执行 statement 之后,expression 不再为真,则循环结束,程序将继续执行循环之后的代码。例如,让我们来看一个使用 while 循环的倒计时:

// custom countdown using while
#include <iostream>
using namespace std;

int main ()
{
  int n = 10;

  while (n>0) {
    cout << n << ", ";
    --n;
  }

  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, liftoff!

main 中的第一条语句将 n 设置为 10。这是倒计时中的第一个数字。然后 while 循环开始:如果此值满足条件 n>0(即 n 大于零),则执行条件后的代码块,并只要条件 (n>0) 保持为真,就重复执行。

上一个程序可以根据以下脚本(从 main 开始)来解释:

  1. n 被赋值
  2. 检查 while 条件 (n>0)。此时有两种可能性:
    • 条件为真:执行语句(到步骤 3)
    • 条件为假:忽略语句,继续执行其后的代码(到步骤 5)
  3. 执行语句
    cout << n << ", ";
    --n;
    (打印 n 的值并将 n 减 1)
  4. 代码块结束。自动返回到步骤 2。
  5. 继续执行代码块之后的程序
    打印 liftoff! 并结束程序。

关于 while 循环需要注意的一点是,循环应该在某个时候结束,因此语句应该以某种方式改变条件中检查的值,以迫使它最终变为假。否则,循环将永远继续下去。在本例中,循环包含 --n,它将条件中被评估的变量 (n) 的值减一——这将在一定次数的循环迭代后,最终使条件 (n>0) 变为假。更具体地说,在 10 次迭代后,n 变为 0,使条件不再为真,while 循环也随之结束。

请注意,对于计算机来说,此循环的复杂性非常简单,因此整个倒计时是即时完成的,计数元素之间没有实际延迟(如果您有兴趣,请参阅 sleep_for 以了解带有延迟的倒计时示例)。

do-while 循环

一个非常相似的循环是 do-while 循环,其语法是:

do statement while (condition);

它的行为与 while 循环相似,但不同之处在于 condition 是在 statement 执行之后而不是之前评估的,这保证了 statement 至少执行一次,即使 condition 从未满足。例如,以下示例程序会回显用户输入的任何文本,直到用户输入 goodbye 为止。

// echo machine
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string str;
  do {
    cout << "Enter text: ";
    getline (cin,str);
    cout << "You entered: " << str << '\n';
  } while (str != "goodbye");
}
Enter text: hello
You entered: hello
Enter text: who's there?
You entered: who's there?
Enter text: goodbye
You entered: goodbye

statement 需要至少执行一次时,通常首选 do-while 循环,例如当用于结束循环的条件在循环语句本身内部确定时。在前面的示例中,代码块内的用户输入将决定循环是否结束。因此,即使用户希望尽快结束循环(通过输入 goodbye),循环中的代码块也至少需要执行一次来提示输入,并且实际上,只有在执行后才能确定条件。

for 循环

for 循环旨在迭代一定次数。其语法是:

for (initialization; condition; increase) statement;

与 while 循环一样,此循环在 condition 为真时重复执行 statement。但是,for 循环还提供了特定的位置来包含 initializationincrease 表达式,它们分别在循环第一次开始之前和每次迭代之后执行。因此,使用计数器变量作为 condition 特别有用。

其工作方式如下:

  1. 执行 initialization。通常,这会声明一个计数器变量,并将其设置为某个初始值。这只在循环开始时执行一次。
  2. 检查 condition。如果为真,循环继续;否则,循环结束,并跳过 statement,直接转到步骤 5。
  3. 执行 statement。与往常一样,它可以是单个语句,也可以是包含在花括号 { } 中的代码块。
  4. 执行 increase,然后循环回到步骤 2。
  5. 循环结束:执行继续到循环之后的下一条语句。

这是使用 for 循环的倒计时示例:

// countdown using a for loop
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--) {
    cout << n << ", ";
  }
  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, liftoff!

for 循环的三个字段是可选的。它们可以留空,但在所有情况下,它们之间的分号是必需的。例如,for (;n<10;) 是一个没有 *initialization* 或 *increase* 的循环(相当于 while 循环);for (;n<10;++n) 是一个有 *increase* 但没有 *initialization* 的循环(可能因为变量在循环之前已被初始化)。没有 *condition* 的循环等同于以 true 作为条件的循环(即无限循环)。

由于每个字段都在循环生命周期的特定时间执行,因此将多个表达式作为 *initialization*、*condition* 或 *statement* 执行可能很有用。不幸的是,它们不是语句,而是简单的表达式,因此不能被代码块替换。作为表达式,它们可以使用逗号运算符 (,):此运算符是表达式分隔符,可以在通常只期望一个表达式的地方分隔多个表达式。例如,使用它,for 循环可以处理两个计数器变量,同时初始化和增加它们:

1
2
3
4
for ( n=0, i=100 ; n!=i ; ++n, --i )
{
   // whatever here...
}

如果 ni 在循环中未被修改,此循环将执行 50 次。



n 从 0 开始,i 从 100 开始,条件是 n!=i(即 n 不等于 i)。由于每次迭代 n 增加一,i 减少一,因此在第 50 次迭代后,当 ni 都等于 50 时,循环的条件将变为假。

基于范围的 for 循环

for 循环有另一种语法,专门用于范围:

for ( declaration : range ) statement;

这种 for 循环遍历 range 中的所有元素,其中 declaration 声明一个变量,该变量可以存储此范围中某个元素的值。范围是元素的序列,包括数组、容器以及任何支持 beginend 函数的类型;本教程中尚未介绍这些类型中的大多数,但我们已经熟悉了至少一种范围:字符串,它们是字符序列。

使用字符串的基于范围的 for 循环示例:

// range-based for loop
#include <iostream>
#include <string>
using namespace std;

int main ()
{
  string str {"Hello!"};
  for (char c : str)
  {
    cout << "[" << c << "]";
  }
  cout << '\n';
}
[H][e][l][l][o][!]

请注意,在 for 循环中,冒号 (:) 前面是 char 变量的声明(字符串中的元素是 char 类型)。然后,我们在语句块中使用该变量 c 来表示范围中每个元素的值。

此循环是自动的,不需要显式声明任何计数器变量。

基于范围的循环通常也使用 auto 进行类型推导来确定元素的类型。通常,上面的基于范围的循环也可以这样写:

1
2
for (auto c : str)
  cout << "[" << c << "]";

在这里,c 的类型是从 str 的元素类型自动推导出来的。

跳转语句

跳转语句允许通过跳转到特定位置来改变程序的流程。

break 语句

break 语句会退出循环,即使循环的结束条件尚未满足。它可用于结束无限循环,或强制循环在其自然结束之前终止。例如,让我们在自然结束之前停止倒计时:

// break loop example
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--)
  {
    cout << n << ", ";
    if (n==3)
    {
      cout << "countdown aborted!";
      break;
    }
  }
}
10, 9, 8, 7, 6, 5, 4, 3, countdown aborted!

continue 语句

continue 语句会导致程序跳过当前迭代中循环的其余部分,就好像已到达语句块的末尾一样,使其跳转到下一次迭代的开头。例如,让我们在倒计时中跳过数字 5:

// continue loop example
#include <iostream>
using namespace std;

int main ()
{
  for (int n=10; n>0; n--) {
    if (n==5) continue;
    cout << n << ", ";
  }
  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 4, 3, 2, 1, liftoff!

goto 语句

goto 允许程序跳转到程序的另一个绝对点。这种无条件跳转会忽略嵌套级别,也不会导致任何自动堆栈展开。因此,这是一项需要谨慎使用的功能,最好在同一语句块内使用,尤其是在存在局部变量的情况下。

目标点由一个 *标签* 标识,该标签随后用作 goto 语句的参数。*标签* 由一个有效的标识符后跟一个冒号 (:) 组成。

goto 通常被认为是一种低级功能,在现代 C++ 使用的高级编程范例中没有特别的用例。但是,仅作为示例,这里有一个使用 goto 的倒计时循环版本:

// goto loop example
#include <iostream>
using namespace std;

int main ()
{
  int n=10;
mylabel:
  cout << n << ", ";
  n--;
  if (n>0) goto mylabel;
  cout << "liftoff!\n";
}
10, 9, 8, 7, 6, 5, 4, 3, 2, 1, liftoff!

另一个选择语句:switch。

switch 语句的语法有点特殊。它的目的是检查多个可能的常量表达式中的一个值。它类似于串联 if-else 语句,但仅限于常量表达式。其最典型的语法是:

code>switch (expression)
{
case constant1:
group-of-statements-1;
break;
case constant2:
group-of-statements-2;
break;
.
.
.
default:
default-group-of-statements
} /code>

它的工作方式是:switch 评估 expression 并检查它是否等于 constant1;如果等于,则执行 group-of-statements-1,直到找到 break 语句。当找到该 break 语句时,程序将跳转到整个 switch 语句的末尾(结束花括号)。

如果 expression 不等于 constant1,则会将其与 constant2 进行比较。如果相等,则执行 group-of-statements-2,直到找到 break,此时将跳转到 switch 的末尾。

最后,如果 expression 的值与之前指定的任何常量都不匹配(这些常量可能数量不定),则程序将执行 default: 标签之后的语句(如果存在,因为它是可选的)。

以下两个代码片段具有相同的行为,展示了 switch 语句的 if-else 等效项:

switch 示例if-else 等效项
code>switch (x) {
case 1:
cout "x is 1";
break;
case 2:
cout "x is 2";
break;
default:
cout "value of x unknown";
} /code>
code>if (x == 1) {
cout "x is 1";
}
else if (x == 2) {
cout "x is 2";
}
else {
cout "value of x unknown";
}
/code>

switch 语句的语法有些特殊,这是从早期 C 编译器继承下来的,因为它使用标签而不是代码块。在最典型的用法(如上所示)中,这意味着需要为每个标签后面的语句组添加 break 语句。如果省略 break,则会继续执行 case 后面的所有语句(包括其他标签下的语句),直到 switch 代码块结束或遇到跳转语句(如 break)。

如果上面的示例在 case one 的第一个组之后缺少 break 语句,程序不会自动跳转到 switch 代码块的末尾,而是会继续执行 case two 中的语句(因此也会打印 x is 2)。它将继续这样做,直到遇到 break 语句或 switch 代码块的末尾。这使得为每个 case 的语句包含在花括号 {} 中成为不必要的,并且也可以用于为不同的可能值执行相同的语句组。例如:

1
2
3
4
5
6
7
8
9
switch (x) {
  case 1:
  case 2:
  case 3:
    cout << "x is 1, 2 or 3";
    break;
  default:
    cout << "x is not 1, 2 nor 3";
  }

请注意,switch 仅限于将其评估的表达式与常量表达式的标签进行比较。不能使用变量作为标签或范围,因为它们不是有效的 C++ 常量表达式。

要检查非常量范围或值,最好使用 ifelse if 语句的串联。
Index
目录