5.语句和异常

语句

5.3条件语句
5.3.2switch语句
switch内部的变量定义

switch的执行流程有可能会跨过某些case标签。如果程序跳转到了某个特定的case,则switch结构中该case标签之前的部分会被忽略掉。因此,如果在某处一个带有初值的变量位于作用域之外,在另一处该变量位于作用域之内,则从前一处跳转到后一处是非法行为(保险起见,如果case的分支出现两条以上的语句,就要用大括号把各自的内容包括起来)

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

// 如果需要为某个case分支定义并初始化一个变量,应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外
case true: {    
    // 正确:声明语句位于语句块内部    
    string file_name = get_file_name();    
    // ...
}    
    break;
case false:    
    if (file_name.empty())  // 错误:file_name不在作用域之内


C++规定,不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。可以参考相应链接

5.4迭代语句
5.4.3范围for语句

C++11新标准引入了一种更简单的for语句,这种语句可以遍历容器或其他序列的所有元素,例如花括号括起来的初始值列表、数组或者vectorstring等类型的对象,这些类型的共同特点是拥有能返回迭代器的beginend成员。
范围for语句的定义来源于与之等价的传统for语句:

for (auto beg = v.begin(), end = v.end(); beg != end; ++beg) {
    auto &r = *beg;
    r *= 2;
}

由于在范围for语句中预存了end()的值,因此不能通过范围for语句增加vector(或者其他容器)的元素。一旦在序列中添加(删除)元素,end函数的值就可能变得无效。

5.6try语句块和异常处理

当程序的某部分检测到一个它无法处理的问题时,需要用到异常处理。此时,检测出问题的部分应该发出某种信号以表明程序遇到了故障,无法继续下去,而且信号的发出方无须知道故障将在何处得到解决。一旦发出异常信号,检测出问题的部分也就完成了任务。
异常处理机制为程序中异常检测和异常处理这两部分的写作提供支持。在c++语言中,异常处理包括:

  • throw表达式,异常检测部分使用throw表达式来表示它遇到了无法处理的问题。一般说throw引发了异常。
  • try语句块,异常处理部分使用try语句块处理异常,其中catch子句被称作异常处理代码

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

5.6.1throw表达式

程序的异常检测部分使用throw表达式引发一个异常,其中表达式的类型就是抛出的异常类型:

// 首先检查两条数据是否是关于同一种书籍的
if (item1.isbn() != item2.isbn()) {
    throw runtime_error("Data must refer to same ISBN");
}

// 如果程序执行到了这里,表示两个ISBN是相同的
cout << item1 + item2 << endl;
5.6.2try语句块

一如往常,try语句块内声明的变量在块外部无法访问,特别是在catch子句内也无法访问。

编写处理代码
while (cin >> item1 >> item2) {    
    try {        
        if (item1 != item2) {            
            throw runtime_error("Data must refer to same ISBN");        
        }    
    } catch (runtime_error err) {       
    // 提醒用户输入的两个值必须一致        
        cout << err.what()            
            << "\nTry Again? Enter y or n" << endl;        
        char c;        
        cin >> c;        
        
        if (!cin || c == 'n') {            
            break;        
        }    
    }
}

每个标准库异常类都定义了名为what的成员函数,这些函数没有参数,返回值是c风格字符串,即const char*。其中,runtime_errorwhat成员返回的是初始化一个具体对象时所用的string对象的副本。

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

在复杂系统中,程序在遇到抛出异常的代码前,其执行路径可能已经经过了多个try语句块。例如,一个try语句块可能调用了包含另一个try语句块的函数,新的try语句块可能调用了包含又一个try语句块的新函数,以此类推。
寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时,首先搜索抛出该异常的函数。如果没有找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的catch子句为止。
如果最终还是没能找到任何匹配的catch子句,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。对于那些没有任何try语句块定义的异常,也按照类似的方式处理

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

要好好理解这句话:异常中断了程序的正常流程。异常发生时,调用者请求的一部分计算可能已经完成了,另一部分则尚未完成。通常情况下,略过部分程序意味着某些对象处理到一半就戛然而止,从而导致对象处于无效或未完成的状态,或者资源没有正常释放,等等。那些在异常发生期间正确执行了清理工作的程序被称作异常安全的代码。然而经验表明,编写异常安全的代码非常困难。
对于一些程序来说,当异常发生时只是简单地终止程序。此时,不怎么需要担心异常安全的问题。
但是对于那些确实要处理异常并继续执行的程序,就要加倍注意。必须时刻清楚异常何时发生,异常发生后程序应如何确保对象有效、资源无泄漏、程序处于合理状态,等等。

5.6.3标准异常

C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在编写的程序中使用,分别定义在4个头文件中:

  • exception头文件定义了最通用的异常类exception。它只报告异常的发生,不提供额外的信息。
  • stdexcept头文件定义了几种常用的异常类。

在这里插入图片描述

  • new头文件定义了bad_alloc异常类型。
  • type_info头文件定义了bad_cast异常类型。

标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。
只能以默认初始化的方式初始化exceptionbad_allocbad_cast对象,不允许为这些对象提供初始值。
其他异常类型的行为则恰好相反:应该使用string对象或者c风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值