施工提示

本博文尚在施工中,内容并未完成且可能变更。

控制流语句

C++ 的控制流语句包括:

  • 循环语句

  • 条件语句

  • 跳转语句

  • 异常处理语句

循环语句:for 与 while

C++ 循环语句可以分为 for 与 while 两大类:

  • for 循环:最常用的循环体,常用于已知循环次数(或循环迭代对象)的情形。

    • 常规 for 循环:控制循环变量的变化,以此来完成给定次数的循环。

    • 范围 for 循环 C++ 11 :允许指定迭代对象来进行循环,而不用显式地指明循环次数。

  • while 循环:循环次数未知,或要在循环体之后使用循环变量。

    • do..while 循环:与 while 类似,用于循环体无论如何也要执行一次的情形。

常规 for 循环

  • for 循环的循环头包含三个部分:初始化语句、循环条件,以及表达式。

  • 初始化语句中的对象 在离开 for 循环之后不可使用

下例将从 0 到 3 的整数累加:

 1#include <iostream>
 2using std::cout;
 3using std::endl;
 4
 5int main() {
 6    int s = 0;
 7    for (int k = 0; k < 4; ++k) {
 8        s += k;
 9    }
10    cout << "Sum = " << s << endl;
11    return 0;   
12}
13/*
14* 输出:6
15*/

C++ 中循环体如果只包含一条语句,可以不放在花括号内。例如:

for (int k = 0; k < 4; ++k) s +=k;
for (int j = 0; j < 10; ++j) ;  // 空语句

范围 for 循环

范围 for 循环 C++ 11 使用冒号 : 来表示对迭代对象的元素进行遍历。

下例实现了对指定向量中的所有数字累加求和。关于向量,请参考 向量(std::vector) 一节。

 1#include <iostream>
 2#include <vector>
 3using std::cout;
 4using std::endl;
 5using std::vector;
 6
 7int main() {
 8    vector<int> vec = {1, 3, 5, 7};
 9    int s = 0;
10    for (int k: vec) {  // 遍历向量 vec 的每个元素
11        s += k;
12    }
13    cout << "Sum = " << s << endl;
14    return 0;
15}
16/*
17* 输出:16
18*/

范围 for 循环的另一个不同之处在于可以方便地使用引用,避免额外开销:

for (const int &k: vec) {  // 使用引用
    s += k;
}

while 循环

用 while 循环来处理未知循环次数的任务。下例从用户输入中读取若干个数字,并将它们相加:

 1#include <iostream>
 2using std::cout;
 3using std::cin;
 4using std::endl;
 5
 6int main() {
 7    int s = 0, k = 0;
 8    // 输入 Ctrl + Z 或 Ctrl + D 来标识输入流结束
 9    while (cin >> k) {
10        s += k;
11    }
12    cout << "Sum = " << s << endl;
13    return 0;   
14}
15/*
16* 输入:1 3 7 5 9^D
17* 输出:Sum = 25
18*/

cin 的读取何时会终止?

while(cin >> k) 表示从输入流依次地读取内容,并赋值给变量 k。这种 cin 的读入行为在以下情况终止:

  • 读取到输入流的末尾(EoF)。对输入文件来说,EoF 就是它的最后内容;对控制台的用户输入来说,EoF 需要手动地输入:在 Windows 上,使用 Ctrl + D;在类 Unix 上,使用 Ctrl + Z。

  • 读取到异常内容。上例中,限于变量 k 的类型,用户输入的内容应当是 int 类型的数字;如果用户输入其他内容(例如字母、符号),cin 读取将会终止。

在上例中,如果输入 1 2 xyz 4,程序将输出 Sum = 3

do..while 循环*

do..while 循环与 while 区别很小——除了它会先执行一次循环体后,再检查循环条件。也就是说,do..while 循环的循环体最少也会被执行一次。

  • 请注意 do..while 的结尾需要有分号。

 1#include <iostream>
 2using std::cout;
 3using std::cin;
 4using std::endl;
 5
 6int main() {
 7    int s = 0, k = 0;
 8    do {
 9        s += k;
10    } while (cin >> k);  // 请勿忘记这个分号
11    cout << "Sum = " << s << endl;
12    return 0;   
13}
14/*
15* 输入:1 4 7 2^D
16* 输出:Sum = 14
17*/

跳转语句:break 与 continue

警告

由于脆弱且可读性差,不建议在代码中使用 goto 语句。

跳转语句报考 break, continue 与 goto 三种。这里只介绍 break 与 continue 这两种跳转语句。

  • break:结束并跳出当前循环体,执行循环体之后的代码。

  • continue:结束当前迭代,执行循环的下一个迭代。

下例统计了字符串中第一个单词(以空格分割单词)的长度及其包含的字母 o 的数量。

 1#include <iostream>
 2#include <string>
 3using std::cout;
 4using std::endl;
 5using std::string;
 6
 7int main() {
 8    int count_o = 0, count_firstword = 0;
 9    string s = "Hello world";
10    for (char c: s) {
11        if (c == 'o') {
12            count_o += 1;
13            count_firstword += 1;
14            continue;
15        } else if (c == ' ') {
16            break;
17        } else {
18            count_firstword += 1;
19        }
20    }
21    cout << "Count of 'o' = " << count_o << endl;
22    cout << "Length of first word = " << count_firstword << endl;
23    return 0;   
24}
25/*
26  输出:
27  Count of 'o' = 1
28  Length of first word = 5
29*/

条件语句:if 与 switch

  • if 语句:根据条件判断是否执行条件体。它支持 if..else 形式以及嵌套的 if..else if 形式。

  • switch 语句:根据给定选项判断条件符合哪一种,然后执行对应的条件体。

if 语句

下例的 if..else 嵌套条件语句能够根据用户输入的整数,判断其正负号:

  • C++ 支持 else if 语法块。

 1#include <iostream>
 2using std::cout;
 3using std::cin;
 4using std::endl;
 5
 6int main() {
 7    int val;
 8    cin >> val;  // 读取用户输入
 9    char sign;
10    if (val > 0) {
11        sign = '+';
12    } else if (val < 0) {
13        sign = '-';
14    } else {
15        sign = '0';
16    }
17    cout << "Sign of " << val << " is " << sign << endl;
18    return 0;
19}
20/*
21* 输入:100
22* 输出:Sign of 100 is +
23*/

switch 语句

  • switch 语句将执行符合 case 标签的每一个语句直到 switch 的结尾,或者到发现 break 为止。

    • 因此,一般在每个 case 标签的末尾都会写有 break; 语句。

    • case 标签必须是整型常量表达式

      int val = 7;
      // 错误:以下 case 标签不合法,因为它们不是整型常量表达式
      case 1.414:
      case val:
      
    • 在同一 switch 语句内部,不能有相同的 case 标签。

    • switch 语句允许特殊的 default 关键字,表示在没有 case 匹配时需执行的操作。

下例分别计数了字符串中字符 a, b, c 以及它们以外的字符出现的次数:

 1#include <iostream>
 2#include <string>
 3#include <vector>
 4using std::cout;
 5using std::endl;
 6using std::string;
 7using std::vector;
 8
 9int main() {
10    string s = "abaccbba13caabbbcc539207aacb";
11    vector<string> targets {"other", "a", "b", "c"};
12    vector<int> counts(targets.size() + 1, 0);
13    int total = 0;
14    for (char c: s) {
15        switch (c) {
16            case 'a':
17                ++counts[1];
18                break;
19            case 'b':
20                ++counts[2];
21                break;
22            case 'c':
23                ++counts[3];
24                break;
25            default:  // 其他情形
26                ++counts[0];
27                break;
28        }
29    }
30    for (int i = 0; i < targets.size(); ++i) {
31        cout << "Count of '" << targets[i] << "': "
32             << counts[i] << endl;
33    }
34    return 0;
35}
36/*
37    输出:
38    Count of 'other': 8
39    Count of 'a': 7
40    Count of 'b': 7
41    Count of 'c': 6
42*/

一个常用技巧是,在 switch 的多个 case 需要执行的操作相同时,合并它们。下例统计了字母 a, b, c 的总数:

int count_abc = 0;
// ...
switch(c) {
    // 将多个 case 写在同一行内或拆成多行均可
    case 'a': case 'b': case 'c':
        count_abc += 1;
        break;
}
// ...

异常语句:try 与 throw

重要

try..catch 与 throw 语句是异常处理部分的内容。本节只对语句语法作介绍,而不涉及异常处理的详细知识。

try..catch 语句

try..catch 语句捕获异常并允发出有关该异常的信息。

  • try 语句块后至少需要一个 catch 语句块,也可以是多个。

  • 通常需要包含 <stdexcept> 库。常用的异常是 std::runtime_error

下例尝试将 string 转为 double,并在无法转换时捕获 std::invalid_argument 异常:

  • error 对象的 what() 方法将返回一个 C 风格字符串,用于提示错误信息。

  • 本例中将错误信息整理后输出到标准错误 std::cerr

 1#include <iostream>
 2#include <string>
 3#include <stdexcept>
 4using std::cout;
 5using std::cerr;
 6using std::endl;
 7using std::string;
 8
 9int main() {
10    string s = "exp=2.718";
11    double x = 0;
12    try {
13        x = std::stod(s);  // string 到 double 转换函数
14    } catch (std::invalid_argument err) {
15        cerr << "Invalid arg: " << err.what()
16             << "(\"" << s <<"\")" << endl;
17    }
18    cout << "x = " << x << endl;
19    return 0;
20}
21/*
22    输出:
23    Invalid arg: stod("exp=2.718")
24    x = 0
25*/

throw 语句

throw 语句用于直接抛出异常。

下面是一个简单的例子。在 int 除法时,若检查到除数为 0 就会抛出异常:

 1#include <iostream>
 2#include <stdexcept>
 3using std::cout;
 4using std::cerr;
 5using std::endl;
 6
 7int main() {
 8    int x = 7, y = 0;
 9    int result = 0;
10    if (y == 0) {
11        throw std::runtime_error("Division by zero.");
12    }
13    // 如果抛出了异常,以下内容将不会被执行
14    cout << "x // y = " << x / y << endl;
15    return 0;
16}
17/*
18    输出:
19    terminate called after throwing an instance of 'std::runtime_error'
20      what():  Division by zero.
21*/

请注意,上例中的异常被抛出,但并没有 try..catch 来捕获它,因此程序在抛出异常后就终止了。通常我们在抛出异常时也会捕获它,请参考下一节的例子。

完整的异常处理例子

一般地,我们将 throw 写在被调函数内部,并在外部调用函数时用 try..catch 捕获可能的异常。

下面是一个更完整的异常抛出与捕获的例子,仍然是处理整数除法中除数为 0 的问题。本例中结合使用了 throw 与 try..catch 语句。

 1#include <iostream>
 2#include <stdexcept>
 3using std::cout;
 4using std::cerr;
 5using std::endl;
 6
 7int int_div(int numerator, int denominator) {
 8    if (denominator == 0) {
 9        throw std::runtime_error("Error: Division by zero.");
10    }
11    return numerator / denominator;
12}
13
14int main() {
15    int x = 7, y = 0;
16    int result = 0;
17    try {
18        result = int_div(x, y);
19    } catch (std::runtime_error err) {
20        cerr << err.what() << endl;
21        cout << "result = N/A" << endl;
22    }
23    // 如果异常被成功捕获,那么以下内容会被正常执行
24    cout << "~ A message after division." << endl;
25    return 0;
26}
27/*
28    输出:
29    Error: Division by zero.
30    result = N/A
31    ~ A message after division.
32*/