第5章 语句

  • 第5章 语句
    • 5.1 简单语句
      • 空语句(null statement)
      • 复合语句(块)
    • 5.2 语句作用域
    • 5.3 条件语句
      • 5.3.1 if语句
        • 使用if else语句
        • 嵌套if语句
        • 注意使用花括号
        • 悬垂else (dangling else)
        • 使用花括号控制执行路径
      • 5.3.2 switch语句
        • switch 内部的控制流
        • 漏写break容易引发缺陷
        • default标签
        • switch内部的变量定义
    • 5.4 迭代语句
      • 5.4.1 while语句
        • 使用while循环
      • 5.4.2 传统的for语句
        • for语句头中的多重定义
        • 省略for语句头的某些部分
          • 省略init-statement
          • 省略condition
          • 省略expression
      • 5.4.3 范围for语句 (range for statement)
      • 5.4.4 do while语句
    • 5.5 跳转语句
      • 5.5.1 break语句
      • 5.5.2 continue语句
      • 5.5.3 goto语句
    • 5.6 try语句块和异常处理
      • 5.6.1 throw表达式
      • 5.6.2 try语句块
        • 编写处理代码
        • 函数在寻找处理代码的过程中退出
      • 5.6.3 标准异常

第5章 语句

5.1 简单语句

末尾加上分号就变成了表达式语句(expression statement)

空语句(null statement)

如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。

例如,我们想读取输入流的内容直到遇到一个特定的值为止,除此之外什么事情也不做:

// 重复读入数据直至到达文件末尾或某次输入的值等于 sought
while (cin >> s && s != sought)
    ;   // 空语句

复合语句(块)

复合语句(compound statement)是指用花括号括起来的语句和声明的序列,复合语句也被称作(block)。一个块就是一个作用域。在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。

所谓空块,是指内部没有任何语句的一对花括号。空块的作用等价于空语句:

while (cin >> s && s != sought)
    {
      } // 空块

5.2 语句作用域

可以在ifswitchwhile、和for语句的控制结构内定义变量。定义在控制结构当中的变量只在相应语句的内部可见,一旦语句结束,变量也就超出其作用范围了。

练习5.4:说明下列例子的含义,如果存在问题,试着修改它。

(a) while (string::iterator iter != s.end()) {
      /* ...*/ }

(a)是非法的,它的原意是希望在while语句的控制结构当中定义一个string::iterator类型的变量iter,然后判断iter是否达到了s的末尾,只要还没有到达末尾就执行循环体的内容。
但是该式把变量的定义和关系判断混合在了一起,如果要使用iter与其他值比较,必须首先为iter赋初值。

// 修改后的程序应该是:
string::iterator iter = s.begin();
whilie (iter != s.end())
{
     
    ++iter;
    /*...*/
}
(b) while (bool status = find(word)) {
      /* ...*/ }
    if (!status) {
      /* ...*/ }

(b)时非法的,变量status定义在while循环控制结构的内部,其作用域仅限于while循环。
if语句已经位于while循环的作用域之外,statusif语句内是一个未命名的无效变量。
要想在if语句中继续使用status,需要把它定义在while循环之前。

// 修改后的程序应该是:
bool status;
while (status = find(word)) {
      /* ...*/}
if (!status) {
     /* ...*/}

5.3 条件语句

5.3.1 if语句

使用if else语句

例子:假设数字成绩的范围是从0100(包括100在内),其中100分对应的字母形式是“A++”,低于60分的成绩对应的字母形式是“F”。
其他成绩每 10 个划分成一组:6069(包括69在内)对应字母“D”、7079对应字母“C”,以此类推。

const vector<string> scores = {
     "F", "D", "C", "B", "A", "A++"};

string lettergrade;
if (grade < 60)
    lettergrade = scores[0];
else
    lettergrade = scores[(grade-50)/10];

嵌套if语句

例子:试着给那些合格的成绩后面添加一个加号或减号。
如果成绩的末位是8或者9,添加一个加号;如果末位是012,添加一个减号:

if (grade % 10 > 7)
    lettergrade += '+'; // 末尾是8或者9的成绩添加一个加号
else if (grade % 10 < 3)
    lettergrade += '-'; // 末尾是0、1或者2的成绩添加一个减号

注意使用花括号

悬垂else (dangling else)

C++规定else与离它最近的尚未匹配的if匹配,从而消除程序的二义性。

// 错误:实际的执行过程并非像缩进格式显示的那样;else分支匹配的是内层if语句
if (grade % 10 >= 3)
    if (grade % 10 > 7)
        lettergrade += '+'; // 末尾是8或者9的成绩添加一个加号
else
    lettergrade += '--'; // 末尾是3、4、5、6或者7的成绩添加一个减号!
/*虽然我们希望当grade的末位小于3时执行else分支,然而不管我们是什么意图也不管程序如何锁紧,这里的else分支其实是内层if语句的一部分*/

执行过程实际上等价于如下形式:

// 缩进格式与执行过程相符,但不是程序员的意图
if (grade % 10 >= 3)
    if (grade % 10 > 7)
        lettergrade += '+'; // 末尾是8或者9的成绩添加一个加号
    else
        lettergrade += '--'; // 末尾是3、4、5、6或者7的成绩添加一个减号!

使用花括号控制执行路径

要想使else分支和外层的if语句匹配起来,可以加个花括号:

// 末尾是8或者9的成绩添加一个加号,末尾是0、1或者2的成绩添加一个减号
if (grade % 10 >= 3) {
     
    if (grade % 10 > 7)
        lettergrade += '+'; // 末尾是8或者9的成绩添加一个加号
} else
    lettergrade += '-';     // 末尾是0、1或者2的成绩添加一个减号

5.3.2 switch语句

假如我们想要统计五个元音字母在文本中出现的次数:

// 为每个元音字母初始化其计数值
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
     
    // 如果 ch 是元音字母,将其对应的计数值加 1
    switch (ch) {
     
        case 'a':
            ++aCnt;
            break;
        case 'e':
            ++eCnt;
            break;
        case 'i':
            ++iCnt;
            break;
        case 'o':
            ++oCnt;
            break;
        case 'u':
            ++uCnt;
            break;
    }
}

此例中,break语句将控制权转移到switch语句外面。因为switchwhile循环体内唯一的语句,所以从switch语句中断出来以后,程序的控制权将移到while语句的右花括号处。此时while语句内部没有其他语句要执行,所以while会返回去再一次判断条件是否满足。

case关键字和它对应的值一起被称为case标签(case label)。

switch 内部的控制流

每个case标签只能对应一个值,但是有时候我们希望两个或更多个值共享同一组操作。此时,我们就故意省略掉break语句,使得程序能够连续执行若干个case标签。

// 统计所有元音字母出现的总次数
unsigned vowelCnt = 0;
switch (ch)
{
     
    // 出现了a、e、i、o 或 u 中的任意一个都会将 vowelCnt 的值加 1
    case 'a';
    case 'e';
    case 'i';
    case 'o';
    case 'u';
        ++vowelCnt;
        break;
}
// 在上面的代码中,几个 case 标签连写在一起,中间没有 break 语句。因此只要 ch 是元音字母,不管到底是五个钟的哪一个都执行相同的代码。

也可以把几个case标签写在一行里,强调这些case代表的是某个范围内的值:

switch (ch)
{
     
    // 另一种合法的书写形式
    case 'a'; case 'e'; case 'i'; case 'o'; case 'u';
        ++vowelCnt;
        break;
}

练习5.12:修改统计元音字母的程序,使其能统计以下含有两个字符的字符序列的数量:ff、fl 和 fi。

/* 练习5.12:修改统计元音字母的程序,使其能统计以下含有两个字符的字符序列的数量:ff、fl 和 fi。

【解答】
我们的设定是一个字符只会被统计一次。
如果用户输入的序列是 xxxxxfflxxx,则统计结果是ff: 1次、fl: 0次、fi: 0次。
如果用户输入的序列是 xxxxxfiffffflxxx,则统计结果是 ff:2次、fl:1次、fi:1次。
*/

#include 
using namespace std;

int main()
{
     
    unsigned int ffCnt = 0, flCnt = 0, fiCnt = 0;
    char ch, prech = '\0';
    cout << "Please enter a text: " << endl;
    while (cin >> ch)
    {
     
        bool bl = true;
        if (prech == 'f')
        {
     
            switch(ch)
            {
     
            case 'f':
                ++ffCnt;
                bl = false;
                break;
            case 'l':
                ++flCnt;
                break;
            case 'i':
                ++fiCnt;
                break;
            }
        }
        if (!bl)
            prech = '\0';
        else
            prech = ch;
    }
    cout << "The number of ff is: " << ffCnt << endl;
    cout << "The number of fl is: " << flCnt << endl;
    cout << "The number of fi is: " << fiCnt << endl;
    return 0;
}

漏写break容易引发缺陷

break语句的作用是中断当前的控制流。

注意:尽管switch语句不是非得在最后一个标签后面写上break,但是为了安全起见,最好这么做。因为这样的话,即便以后再增加新的case分支,也不用再在前面补充break语句了。

default标签

例子:增加一个计数值来统计非元音字母的数量,只要在default分支内不断递增名为otherCnt的变量就可以了:

// 如果 ch 是一个元音字母,将相应的计数值加 1
switch (ch) {
     
    case 'a'; case 'e'; case 'i'; case 'o'; case 'u';
        ++vowelCnt;
        break;
    default:
    ++otherCnt;
    break;
}

建议:即使不准备在default标签下做任何工作,定义一个default标签也是有用的。其目的在于告诉程序的读者,我们已经考虑到了默认的情况,只是目前什么也没做。

如果switch结果以一个空的default标签作为结束,则该default标签后面必须跟上一条空语句或一个空块。

switch内部的变量定义

case true:
    // 因为程序的执行流程可能绕开下面的初始化语句,所以该 switch 语句不合法
    string file_name;   // 错误:控制流绕过一个隐式初始化的变量
    int ival = 0;       // 错误:控制流绕过一个显式初始化的变量
    int jval;           // 正确:因为 jval 没有初始化
    break;
case false:
    // 正确:jval 虽然在作用域内,但是它没有被初始化
    jval = next_num();     // 正确:给jval 赋一个值
    if (file_name.empty())  //错误:file_name 在作用域内,但是没有被初始化
        // ...

假设上述代码合法,则一旦控制流直接跳到false分支,也就同时略过了变量file_nameival的初始化过程。此时这两个变量位于作用域之内,跟在false之后的代码试图在尚未初始化的情况下使用它们,这显然是行不通的。
因此C++语言规定,不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。

如果需要为某个case分支定义并初始化一个变量,我们应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外。

case true:
    {
     
        // 正确:声明语句位于语句块内部
        string file_name = get_file_name();
        // ...
    }
    break;
case false:
    if (file_name.empty())  // 错误:file_name 不在作用域之内

练习5.13:下面显示的每个程序都含有一个常见的编程错误,指出错误在哪里,然后修改它们(P164)。

【出题思路】
switch 语句有几个语法要点:

  • 必须在必要的地方使用break;语句,
  • 应该把变量定义在块作用域内,
  • case 标签职能有一个值且不能是变量。

(a) 的错误是在每个 case 分支中都缺少了 break; 语句,造成的后果是一旦执行了前面的 case 分支,必定还会继续执行接下来的其他 case 分支。

// 修改后的程序如下所示:
unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
char ch = next_text();
switch (ch) {
     
    case 'a':
        aCnt++;
        break;
    case 'e':
        eCnt++;
        break;
    default:
        iouCnt++;
        break;
}

(b) 的错误是在 case 分支中定义并初始化了变量 ix,同时在default分支中使用了该变量,此使如果控制流跳过case分支而直接到达default分支,则会试图使用未经初始化的变量,因而该程序无法通过编译。
解决办法是,把ix的帝国一放置在switch语句之前。

// 修改后的程序如下所示:
unsigned index = some_value();
int ix;
switch (index) {
     
    case 1:
        ix = get_value();
        ivec[ ix ] = index;
        break;
    default:
        ix = ivec.size() - 1;
        ivec[ ix ] = index;
}

© 的错误是在同一个case标签中放置了多个值,而C++规定一个case标签只能对应一个值。

// 修改后的程序如下所示:
unsigned evenCnt = 0, oddCnt = 0;
int digit = get_num() % 10;
switch (digit) {
     
    case 1:
    case 3:
    case 5:
    case 7:
    case 9:
        oddCnt++;
        break;
    case 2:
    case 4:
    case 6:
    case 8:
    case 10:
        evenCnt++;
        break;
}

(d) 的错误是使用变量作为case标签的内容,C++规定,case标签的内容只能是整型常量表达式。

// 修改后的程序如下所示:
const unsigned ival = 512, jval = 1024, kval = 4096;
unsigned bufsize;
unsigned swt = get_bufCnt();
switch (swt) {
     
    case ival:
        bufsize = ival * sizeof(int);
        break;
    case jval:
        bufsize = jval * sizeof(int);
        break;
    case kval:
        bufsize = kval * sizeof(int);
        break;
}

5.4 迭代语句

5.4.1 while语句

使用while循环

  • 当不确定到底要迭代多少次,使用while循环比较合适,比如读取输入的内容就是如此。
  • 还有一种情况也应该使用while循环,这就是我们想在循环结束后访问循环控制变量。
/* 练习5.14:编写段程序,从标准输入中读取若干 string 对象并查找连续重复出现的单词。
所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。
如果这样的单词存在,输出重复出现的最大次数;
如果不存在,输出一条信息说明任何单词都没有连续出现过。
例如,如果输入是
    how now now now brown cow cow
那么输出应该表明单词 now 连续出现了 3 次。*/

#include 
#include 
using namespace std;

int main()
{
     
    string currString, preString = "", maxString;
    int currCnt = 1, maxCnt = 0;
    while (cin >>currString)
    {
     
        if (currString == preString)
        {
     
            ++currCnt;
            if (currCnt > maxCnt)
            {
     
                maxCnt = currCnt;
                maxString = currString;
            }
        }
        else // 如果当前字符串与前一个字符串不一致,重置 currCnt
        {
     
            currCnt = 1;
        } // 更新 preString 以便于下一次循环使用
        preString = currString;
    }

    if (maxCnt > 1)
        cout << "The most frequently occured string is: " << maxString
             << ", and the number of occurence is: " << maxCnt << endl;
    else
        cout << "Every string occurrs only once." << endl;
    return 0;
}

5.4.2 传统的for语句

for 语句的语法形式是:

for (init-statement; condition; expression)
    statement

Note: 牢记for语句头中定义的对象只在for循环体内可见。因此在上面的例子中,for循环结束后index就不可用了。

for语句头中的多重定义

// 用下面的循环把vector的元素拷贝一份添加到原来的元素后面
// 记录下 v 的大小,当到达原来的最后一个元素后结束循环
for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i)
    v.push_back(v[i]);

// 这个循环中,我们同时定义了索引 i 和循环控制变量 sz

省略for语句头的某些部分

for语句头能省略掉init-statementconditionexpression中的任何一个(或者全部)。
如果无须初始化,则我们可以使用一条空语句作为 init-statement

省略init-statement

例如,对于vector对象中寻找第一个负数的程序,完全能用for循环改写:

auto beg = v.begin();
for (/* 空语句 */; beg != v.end() && *beg >= 0; ++beg)
    ;   // 什么也不做

注意,分号必须保留以表明我们省略掉了init-statement。说得更准确一点,分号表示的是一个空的init-statement
要做的工作都在for语句头的条件和表达式部分完成了,所以for循环体也是空的。其中,条件部分决定何时停止查找,表达式部分递增迭代器。

省略condition

省略condition的效果等价于在条件部分写了一个true。因为条件的值永远是true,所以在循环体内必须有语句负责退出循环,否则循环就会无休止地执行下去:

for (int i = 0; /* 条件为空 */; ++i) {
     
    // 对 i 进行处理,循环内部的代码必须负责终止迭代过程!
}
省略expression

举例:之前有一个将整数读入vectorwhile循环,我们使用for语句改写它:

vector<int> v;
for (int i; cin >> i; /* 表达式为空 */)
    v.push_back(i);

因为条件部分能改变i的值,所以这个循环无须表达式部分。
其中,条件部分不断检查输入流的内容,只要读取完所有的输入或者遇到一个输入错误就终止循环。

5.4.3 范围for语句 (range for statement)

for (declaration : expression)
    statement

expression必须是一个序列,比如用花括号括起来的初始值列表、数组、vector、string,这些类型的共同特点是拥有能返回迭代器的beginend成员。

declaration中确保类型相容最简单的办法是使用auto类型说明符。如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型。

举例:把 vector 对象中的每个元素都翻倍

vector<int> v = {
     0,1,2,3,4,5,6,7,8,9};
// 范围变量必须是引用类型,这样才能对元素执行写操作
for (auto &r : v)
    r *= 2;     // 将 v 中每个元素的值翻倍

5.4.4 do while语句

do
    statement
while (condition);
// 使用 do while 循环(不断地)执行加法运算
string rsp; // 作为循环的条件,不能定义在 do 的内部 <-重要!
do {
     
    cout << "please enter two values: ";
    int val1 = 0, val2 = 0;
    cin >> val1 >> val2;
    cout << "The sum of " << val1 << " and " << val2 
         << " = " << val1 + val2 << "\n\n"
         << "More? Enter yes or no: ";
    cin >> rsp;
} while (!rsp.empty() && rsp[0] != 'n');

5.5 跳转语句

5.5.1 break语句

break 语句负责终止离它最近的 whiledo whileforswitch语句,并从这些语句之后的第一条语句开始继续执行。

break语句只能出现在迭代语句或者switch语句内部(包括嵌套在此类循环里的语句或块的内部)。break语句的作用范围仅限于最近的循环或者switch

/* 练习5.20:编写一段程序从标准输入中读取 string 对象的序列
直到连续出现两个相同的单词或者所有单词都读完为止。
使用 while 循环次读取一个单词,当一个单词连续出现两次时使用 break 语句终止循环。
输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。*/

#include 
#include 

using namespace std;

int main()
{
     
    string currString, preString;
    bool bl = true;
    cout << "Please enter a group of strings: " << endl;
    while (cin >> currString)
    {
     
        if (currString == preString)
        {
     
            bl = false;
            cout << "连续出现的字符串是: " << currString << endl;
            break;
        }
        preString = currString;
    }
    if (bl)
        cout << "没有连续出现的字符串" << endl;
    return 0;
}
/* 本题用到了一个起标识作用的布尔值 bl,当 bl 为真时表示没有连续出现的字符串,
一旦我们发现了存在连续出现的字符串,就立即把 bl 的值置为 false。 */

5.5.2 continue语句

continue 语句终止最近的循环中的当前迭代并立即开始下一次迭代。

  • break 语句类似的是,出现在嵌套循环中的 continue 语句也仅作用于离它最近的循环。
  • break 语句不同的是,continue 虽然终止了当前迭代,但是并不终止循环;而 break 语句则直接跳出循环。

continue 语句中断当前的迭代,但是仍然继续执行循环。

  • 对于 while 或者 do while 语句来说,继续判断条件的值;
  • 对于传统的 for 循环来说,继续执行 for 语句头的 expression;
  • 而对于范围 for 语句来说,则是用序列中的下一个元素初始化循环控制变量。

例子:下面的程序每次从标准输入中读取一个单词。循环只对那些以下划线开头的单词感兴趣,其他情况下,我们直接终止当前的迭代并获取下一个单词:

string buf;
while (cin >> buf && !buf.empty()) {
     
    if (buf[0] != '_')
        continue;   // 接着读取下一个输入
    // 程序执行过程到了这里?说明当前的输入是以下划线开始的;接着处理 buf……
}

5.5.3 goto语句

goto语句的作用是从goto语句无条件跳转到同一函数内的另一条语句。

注意:不要再程序中使用goto语句,因为它使得程序既难理解又难修改。

5.6 try语句块和异常处理

典型的异常包括失去数据库连接以及遇到意外输入等。

异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持。在C++语言中,异常处理包括:

  • throw表达式(throw expression),异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发(raise)了异常。

  • try语句块(try block),异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句(catch clause)结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码(exception handler)。

  • 一套异常类(exception class),用于在throw表达式和相关的catch子句之间传递异常的具体信息。

5.6.1 throw表达式

举例:之前把两个Sales_item对象相加的程序,检查它读入的记录是否是关于同一种书籍的;如果不是,输出一条信息然后退出。

Sales_item item1, item2;
cin >> item1 >> item2;
// 首先检查 item1 和 item2 是否表示同一种书籍
if (item1.isbn() == item2.isbn()) {
     
    cout << item1 + item2 << endl;
    return 0;   // 表示成功
} else {
     
    cerr << "Data must refer to same ISBN" << endl;
    return -1;  // 表示失败
}

在真实的程序中,应该把对象相加的代码和用户交互的代码分离开来。
我们改写程序使得检查完成后不再直接输出一条信息,而是抛出一个异常:

// 首先检查两条数据是否是关于同一种书籍的
if (item1.isbn() != item2.isbn())
    throw runtime_error("Data must refer to same ISBN");
// 如果程序执行到了这里,表示两个ISBN是相同的
cout << item1 + item2 << endl;

在这段代码中,如果ISBN不一样就抛出一个异常,该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。

类型runtime_error是标准库异常类型的一种,定义在stdexcept头文件中。

练习5.24:修改你的程序,使得当第二个数是 0 时抛出异常。

/* 练习5.24:修改你的程序,使得当第二个数是 0 时抛出异常。
先不要设定 catch 子句,运行程序并真的为除数输入 0,看看会发生什么?*/

#include 
#include 
using namespace std;

int main()
{
     
    cout << "Please enter the dividend and the divisor: " << endl;
    int ival1, ival2;
    cin >> ival1 >> ival2;
    if (ival2 == 0)
    {
     
        // cout << "除数不能为0" << endl;
        // return -1;
        throw runtime_error("The divisor can't be 0.");
    }
    cout << "The result is: " << ival1 / ival2 << endl;
    return 0;
}

/*
在本题中,我们设定当检测到除数为 0 时抛出一个 runtime_error 异常,因为没有 catch 语句,所以系统只报告异常而并不处理它。

3 0
terminate called after throwing an instance of 'std::runtime_error'
  what():  The divisor can't be 0.
*/

5.6.2 try语句块

try语句块的通用语法形式是:

try {
     
    program-statements
} catch (exception-declaration) {
     
    handler-statements
} catch (exception-declaration) {
     
    handler-statements
} // ...

编写处理代码

while (cin >> item1 >> item2)
{
     
    try {
     
        // 执行添加两个 Sales_item 对象的代码
        // 如果添加失败,代码抛出一个 runtime_error 异常
    } catch (runtime_error err) {
     
        // 提醒用户两个 ISBN 必须一致,询问是否重新输入
        cout << err.what() << "\nTry Again? Enter y or n" << endl;
        char c;
        cin >> c;
        if (!cin || c == 'n')
            break;  // 跳出 while 循环
    }
}

runtime_errorwhat成员返回的是初始化一个具体对象时所用的string对象的副本。
如果上一节编写的代码抛出异常,则本节的catch子句输出:

Data must refer to same ISBN
Try Again? Enter y or n

练习5.25:修改上一题的程序,使用 try 语句块去捕获异常。使用 try-catch 结构实现完整的异常处理机制。

/* 练习5.25:修改上一题的程序,使用 try 语句块去捕获异常。
catch 子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行 try 语句块的内容。

【出题思路】使用 try-catch 结构实现完整的异常处理机制。
*/

#include 
#include 
using namespace std;

int main()
{
     
    cout << "Please enter the dividend and the divisor: " << endl;
    int ival1, ival2;
    while (cin >> ival1 >> ival2)
    {
     
        try
        {
     
            if (ival2 == 0)
            {
     
                throw runtime_error("The divisor can't be 0.");
            }
            cout << "The result is: " << ival1 / ival2 << endl;
        }
        catch(runtime_error err)
        {
     
            cout << err.what() << endl;
            cout << "Do you want to continue (y or n)? ";
            char ch;
            cin >> ch;
            if (ch != 'y' && ch != 'Y')
                break;
        }
    }
    return 0;
}

函数在寻找处理代码的过程中退出

提示:编写异常安全的代码非常困难

在异常发生期间正确执行“清理”工作的程序被称作异常安全(exception safe)的代码。

5.6.3 标准异常

  • exception头文件定义了最通用的异常类exception。它只报告异常的发生,不提供任何额外信息。
  • stdexcept头文件定义了几种常用的异常类。
  • new头文件定义了bad_alloc异常类型
  • type_info头文件定义了bad_cast异常类型
表5.1: 定义的异常类
exception 最常见的问题
runtime_error 只有在运行时才能检测出的问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行错误:计算下溢
logic_error 程序逻辑错误
domain_error 逻辑错误:参数对应的结果值不存在
invalid_argument 逻辑错误:无效参数
length_error 逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值

你可能感兴趣的:(C++,Primer,c++)