Part I: The Basics
Chapter 5. Statements
switch 语句和 goto 语句注意事项
switch 语句
// initialize counters for each vowel
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
// if ch is a vowel, increment the appropriate counter
switch (ch) {
case 'a':
++aCnt;
break;
case 'e':
++eCnt;
break;
case 'i':
++iCnt;
break;
case 'o':
++oCnt;
break;
case 'u':
++uCnt;
break;
default:
break;
}
}
// print results
cout << "Number of vowel a: \t" << aCnt << '\n'
<< "Number of vowel e: \t" << eCnt << '\n'
<< "Number of vowel i: \t" << iCnt << '\n'
<< "Number of vowel o: \t" << oCnt << '\n'
<< "Number of vowel u: \t" << uCnt << endl;
switch 内部的变量定义
如果被略过的代码中含有变量的定义怎么办?从作用域外的带有初始值的变量的地方,跳到作用域内该变量的地方,是非法的。
case true:
// this switch statement is illegal because these initializations might be bypassed
string file_name; // error: control bypasses an implicitly initialized variable
int ival = 0; // error: control bypasses an explicitly initialized variable
int jval; // ok: because jval is not initialized
break;
case false:
// ok: jval is in scope but is uninitialized
jval = next_num(); // ok: assign a value to jval
if (file_name.empty()) // file_name is in scope but wasn't initialized
// ...
如果需要为特定 case 定义和初始化变量,可以通过在块内定义变量来实现,从而确保该变量在任何后续标签处都在作用域之外。
case true:
{
// ok: declaration statement within a statement block
string file_name = get_file_name();
// ...
}
break;
case false:
if (file_name.empty()) // error: file_name is not in scope
goto 语句
end: return; // labeled statement; may be the target of a goto
goto 语句无法将控制权,从作用域外的初始化变量的地点,转移到作用域内该变量的地点:
// . . .
goto end;
int ix = 10; // error: goto bypasses an initialized variable definition
end:
// error: code here could use ix but the goto bypassed its declaration
ix = 42;
向后跳过已经执行的定义是可以的。
// backward jump over an initialized variable definition is okay
begin:
int sz = get_size();
if (sz <= 0) {
goto begin;
}
在上面代码中,goto 语句执行后将销毁 sz。
try 语句块和异常处理
throw 表达式
Sales_item item1, item2;
cin >> item1 >> item2;
// first check that the data are for the same item
if (item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISBN");
// if we're still here, the ISBNs are the same
cout << item1 + item2 << endl;
类型 runtime_error 是标准库异常类型之一,定义在 stdexcept 头文件中。必须初始化 runtime_error,可通过给它一个 string 对象或 C风格字符串来实现。该字符串提供有关该问题的辅助信息。
try 语句块
通用语法形式:
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
} // . . .
编写处理代码
while (cin >> item1 >> item2) {
try {
// execute code that will add the two Sales_items
// if the addition fails, the code throws a runtime_error exception
} catch (runtime_error err) {
// remind the user that the ISBNs must match and prompt for another pair
cout << err.what() << "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
break; // break out of the while loop
}
}
每个库异常类都定义一个名为 what 的成员函数。这些函数没有参数,返回 C风格字符串(即 const char*)。runtime_error 的 what 成员返回用于初始化特定对象的 string 对象副本。如果上一节中编写的代码抛出异常,那么 catch 子句输出:
Data must refer to same ISBN
Try Again? Enter y or n
函数在查找处理程序的过程中推出
在复杂的系统中,程序在遇到引发异常的代码之前,其执行路径可能经过多个 try 语句块。例如,try 语句块可能会调用包含 try 的函数,新的 try 语句块又调用另一个包含 try 语句块函数,依此类推。
查找处理程序与函数调用链相反。引发异常时,首先查找引发异常的函数。若找不到匹配的 catch 子句,则该函数终止。接下来查找调用引发异常的函数的函数。如果未找到处理程序,则该函数也会退出。依此类推,沿着执行路径倒退,直到找到适当类型的 catch 子句为止。
如果最终没有找到任何匹配的 catch 子句,程序转到名为 terminate 库函数。
如果一段程序没有 try 语句块且引发异常,系统会调用 terminate 函数,终止程序。
void throwExec()
{
try {
cout << "inner..." << endl;
throw runtime_error("error!");
}
catch(runtime_error e){
cout << "inner: " << e.what() << endl;
}
}
int main()
{
try {
cout << "outer..." << endl;
throwExec();
}
catch (exception e) {
cout << "outer: " << e.what() << endl;
}
}
运行结果如下:
outer...
inner...
inner: error!
——————————
void throwExec2()
{
try {
cout << "inner2..." << endl;
throw runtime_error("error!");
}
catch(overflow_error e){
cout << "inner2: " << e.what() << endl;
}
}
int main()
{
try {
cout << "outer..." << endl;
throwExec2();
}
catch (exception e) {
cout << "outer: " << e.what() << endl;
}
}
运行结果如下:
outer...
inner2...
outer: error!
注意:编写异常安全(exception safe)代码非常困难
异常中断了程序的正常流程。
通常,异常会导致处理对象处于无效或未完成的状态,或者资源没有正常释放等。
异常安全代码:在异常发生期间正确执行了“清理”工作的程序。
有些程序仅在异常情况发生时使用异常来终止程序。这样的程序通常不用担心异常安全性。
通常,处理异常并继续执行的程序,必须始终知道,是否可能发生异常,以及该程序必须执行哪些操作以确保对象有效、资源不泄漏与程序恢复到适当的状态。
标准异常
C ++库定义了几个类,用于报告标准库中的函数遇到的问题。这些异常类也可以在用户编写的程序中使用。这些类分别定义在四个头文件中:
- exception 头文件定义了最通用的异常类 exception。它仅传达异常发生,但不提供其他信息。
- stdexcept 头文件定义了几种通用异常类,这些异常类在表5.1中列出。
- new 头文件定义了 bad_alloc 异常类型。
- type_info 头文件定义了 bad_cast 异常类型。
表5.1 定义在 <stdexcept> 中的异常类
类 | 解释 |
---|---|
exception | 最普遍的问题 |
runtime_error | 只有在运行时才能检测出的问题 |
range_error | 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domain_error | 逻辑错误:参数对应的结果值不存在 |
invalid_argument | 逻辑错误:错误参数 |
length_error | 逻辑错误:试图创建一个超出该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |
库异常类只有几个操作。可以创建,复制和赋值任何异常类型的对象。
只能默认初始化 exception,bad_alloc 和 bad_cast 对象;无法为这些异常类型的对象提供初始值。
其他异常类型具有相反的行为:可以使用 string 对象或 C风格字符串初始化那些对象,但无法默认初始化它们。当创建任何这些其他异常类型的对象时,必须提供一个初始值。该初始值用于提供有关发生的错误的其他信息。
异常类型仅定义一个名为 what 的操作。该函数不带任何参数,并返回一个 const char*,它指向 C风格字符串。返回的 C风格字符串的内容取决于异常对象的类型。对于采用字符串初始化程序的类型,what函数返回该字符串。对于其他类型,返回的字符串的值因编译器而异。