[C++]C++Primer Chapter 5

语句

5.1 简单语句

5.2 语句作用域

5.3 条件语句

5.3.1 if语句

5.3.2 switch语句

switch语句(switch statement)提供了一条便利的途径使得我们能够在若干固定选项中做出选择。

case关键字和它对应的值一起被称为case标签(case label)。case标签必须是整型常量表达式。

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

switch内部的变量定义

switch的执行流程有可能会跨过某些case标签。如果程序跳转到了某个特定的case,则switch结构中该case标签之前的部分会被忽略掉。这种忽略掉一部分代码的行为引出了一个有趣的问题:如果被略过的代码中含有变量的定义该怎么办?

答案是:如果在某处一个带有初值的变量位于作用域之外,在另一处该变量位于作用域之内,则从前一处跳转到后一处的行为是非法行为。

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

5.4 迭代语句

5.4.1 while语句

定义在while条件部分或者while循环体内的变量每次迭代都经历从创建到销毁的过程。

5.4.2 for语句

init-statement必须是以下三种形式中的一种:声明语句、表达式语句或者空语句

init-statement负责初始化一个值,这个值将随着循环的进行而改变。condition作为循环控制的条件,只要condition为真,就执行一次statement。如果condition第一次的求值结果就是false,则statement一次也不会执行。expression负责修改init-statement初始化的变量,这个变量正好就是condition检查的对象,修改发生在每次循环迭代之后。

5.4.3 范围for语句

expression表示的必须是一个序列,比如用花括号括起来的初始值列表(参见3.3.1节,第88页)、数组(参见3.5节,第101页)或者vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员(参见3.4节,第95页)。

在范围for语句中,预存了end()的值。一旦在序列中添加(删除)元素,end函数的值就可能变得无效了(参见3.4.1节,第98页)。

5.4.4 do while语句

5.5 跳转语句

跳转语句中断当前的执行过程。C++语言提供了4种跳转语句:break、continue、goto和return。

5.5.1 break语句

5.5.2 continue语句

5.5.3 goto语句

其中,label是用于标识一条语句的标示符。带标签语句(labeled statement)是一种特殊的语句,在它之前有一个标示符以及一个冒号:

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

5.2 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表达式

throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。

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

类型runtime_error是标准库异常类型的一种,定义在stdexcept头文件中。关于标准库异常类型更多的知识将在5.6.3节(第176页)介绍。我们必须初始化runtime_error的对象,方式是给它提供一个string对象或者一个C风格的字符串(参见3.5.4节,第109页),这个字符串中有一些关于异常的辅助信息。

5.6.2 try语句块

try语句块的一开始是关键字try,随后紧跟着一个块,这个块就像大多数时候那样是花括号括起来的语句序列。

跟在try块之后的是一个或多个catch子句。catch子句包括三部分:关键字catch、括号内一个(可能未命名的)对象的声明(称作异常声明,exception declaration)以及一个块。当选中了某个catch子句处理异常之后,执行与之对应的块。catch一旦完成,程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。

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

在复杂系统中,程序在遇到抛出异常的代码前,其执行路径可能已经经过了多个try语句块。例如,一个try语句块可能调用了包含另一个try语句块的函数,新的try语句块可能调用了包含又一个try语句块的新函数,以此类推。

寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的catch子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的catch子句为止。如果最终还是没能找到任何匹配的catch子句,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。

对于那些没有任何try语句块定义的异常,也按照类似的方式处理:毕竟,没有try语句块也就意味着没有匹配的catch子句。如果一段程序没有try语句块且发生了异常,系统会调用terminate函数并终止当前程序的执行。

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

异常中断了程序的正常流程。异常发生时,调用者请求的一部分计算可能已经完成了,另一部分则尚未完成。通常情况下,略过部分程序意味着某些对象处理到一半就戛然而止,从而导致对象处于无效或未完成的状态,或者资源没有正常释放,等等。

那些在异常发生期间正确执行了“清理”工作的程序被称作异常安全(exception safe)的代码。然而经验表明,编写异常安全的代码非常困难,这部分知识也(远远)超出了本书的范围。对于一些程序来说,当异常发生时只是简单地终止程序。此时,我们不怎么需要担心异常安全的问题。但是对于那些确实要处理异常并继续执行的程序,就要加倍注意了。我们必须时刻清楚异常何时发生,异常发生后程序应如何确保对象有效、资源无泄漏、程序处于合理状态,等等。

5.6.3 标准异常

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

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

stdexcept头文件定义了几种常用的异常类,详细信息在表5.1中列出。

new头文件定义了bad_alloc异常类型,这种类型将在12.1.2节(第407页)详细介绍。

type_info头文件定义了bad_cast异常类型,这种类型将在19.2节(第731页)详细介绍。

我们只能以默认初始化(参见2.2.1节,第40页)的方式初始化exception、bad_alloc和bad_cast对象,不允许为这些对象提供初始值。

其他异常类型的行为则恰好相反:应该使用string对象或者C风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。

异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串(参见3.5.4节,第109页)的const char*。该字符串的目的是提供关于异常的一些文本信息。


[C++]C++Primer Chapter 5
https://erlsrnby04.github.io/2024/09/22/C-C-Primer-Chapter-5/
作者
ErlsrnBy04
发布于
2024年9月22日
许可协议