文章目录
语句
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
语句,这种语句可以遍历容器或其他序列的所有元素,例如花括号括起来的初始值列表、数组或者vector
或string
等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin
和end
成员。
范围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_error
的what
成员返回的是初始化一个具体对象时所用的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
异常类型。标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。
只能以默认初始化的方式初始化exception
、bad_alloc
和bad_cast
对象,不允许为这些对象提供初始值。
其他异常类型的行为则恰好相反:应该使用string
对象或者c风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。