第五章 语句
通常情况下,语句时顺序执行的。但除非是最简单的程序,否则仅有顺序执行远远不够。因此,C++语言提供了一组控制流(flow-of-control)语句以支持更复杂的执行路径。
5.1 简单语句
空语句
最简单的语句是空语句(null statement),空语句中只含有一个单独的分号:
; // 空语句
使用空语句时应该加上注释,从而令读这段代码的人知道该语句是有意省略的
5.2 语句作用域
可以在if、switch、while和for语句的控制结构内定义变量。定义在控制结构当中的变量旨在相应的语句内部可见,一旦语句结束了,变量也就超出其作用范围。
5.3 条件语句
5.3.1 if语句
if语句(if statement)的作用是:判断一个指定的条件是否为真,根据判断结果决定是否执行另外一条语句。if语句包括两种形式:一种含义else分支,另外一种没有。简单的if语句的语法形式是
if(condition)
statement
if else语句的形式是
if(condition)
statement
else
statement2
悬垂else
if分支多余else分支在那些既有if语句又有if else语句的编程语言中是个普遍存在的问题。不同语言解决该问题的思路也不同,就C++而言,它规定else与离它最近的尚未匹配的if匹配,从而消除了程序的二义性。
5.3.2 switch语句
switch语句搜先对括号里的表达式求值,该表达式紧跟在关键字switch的后面,可以是一个初始化的变量声明。表达式的值转换成整数类型,然后与每个case标签的值比较。
如果表达式和某个case标签的值匹配成功,程序从该标签之后的第一条语句开始执行,直到到达了switch的结尾或者遇到一条break语句为止。
switch内部的变量定义
switch的执行流程有可能会跨过某些case标签。如果程序跳到某个特定的case,则switch结构中该case标签之前的部分会被忽略掉。这种忽略掉一部分代码的行为引出一个有缺的问题,如果被忽略过的代码中含有变量的定义该怎么办?
答案是如果在某处一个带有初值的变量位于作用域之外,在另外一处该变量位于作用域之内,则从前一处跳转到后一处是非法行为。
case true:
string file_name; // 错误:控制流绕过一个隐式初始化的变量
int ival = 0; // 错误:控制流绕过一个显式初始化的变量
int jval; // 正确:因为jval没有被初始化
break;
case false:
jval = next_num(); // 正确
if(file_name.empty()) // file_name在作用域内,但没有被初始化
// ...
C++语言规定,不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。
如果需要为某个case分支定义并初始化一个变量,我们应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外。
case true:
{
string file_name = get_file_name(); // 正确,声明在语句块内部
// ...
}
break;
case false:
if(file_name.empty()) // 错误:不在作用域
5.4 迭代语句
5.4.1 while语句
while(condition)
statement
5.4.2 传统for语句
for(init-statement;condition;expression)
statement
5.4.3 范围for语句
C++11新标准引入了一种更简单的for语句,这种语句可以遍历容器或其他 序列的所有元素。范围for语句(range for statement)的语法形式是:
for(declaration: expression)
statement
expression表示的必须是一个序列,比如用花括号括起来的初始值列表、数组或者vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员。
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
for(auto &r : v) // 范围变量必须是引用类型,这样才能对元素执行写操作
r *= 2;
5.4.3 do while语句
do
statement
while(condition);
5.5 跳转语句
5.5.1 break语句
break语句负责终止离它最近的while、do while、for或switch语句,并从这些语句之后的第一条语句开始继续执行。
5.5.2 continue语句
continue语句负责终止离它最近的while、do while、for或switch语句,并开始下一次迭代。
5.5.3 goto语句
goto语句的作用是从goto语句无条件跳转到同一函数内的另一条语句。
不要在程序中使用goto语句,因为它使得程序既难理解又难修改。
5.6 try语句块和异常处理
在C++语言中,异常处理包括:
- throw表达式,异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发(throw)了异常。
- try语句块,异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch语句结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码。
- 一套异常类,用于在throw表达式和相关的catch子句之间传递异常的具体信息。
5.6.1 throw表达式
程序的异常检测部分使用throw表达式引发一个异常。throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。
if(item1.isbn() != item2.isbn())
throw runtime_error("Data must reder to same ISBN");
5.6.2 try 语句块
try语句块的语法形式是
try{
program-statement
}catch(exception-declaration){
handler-statement
}catch(exception-declaration){
handler-statement
} // ...
函数在寻找处理代码的过程中退出
寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的catch子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当的catch子句为止。
如果最终还是没能找到任何匹配的catch子句,程序转到名为terminate的标准函数库。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。
5.6.3 标准异常
C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在用户编写的程序中使用,它们分别定义在4个头文件中:
- exception头文件定义了最通用的异常类exception。它只报告异常的发生,不提供任何额外的信息。
- stdexcept头文件定义了几种常见的异常类。
- new头文件定义了bad_alloc异常类型。
- type_info头文件定义了bad_cast异常类型。
<stdexcept>定义的异常类
exception | 最常见的问题 |
---|---|
runtime_error | 只有运行时才能检测出的问题 |
range_error | 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domain_error | 逻辑错误:参数对应的结果值不存在 |
invalid_argument | 逻辑错误:无效参数 |
length_error | 逻辑错误:试图创建一个超出该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |