跳转至

第五章 语句


简单语句

C++语言中的大多数语句都以分号结束,一个表达式,比如ival+5,末尾加上分号就变成了表达式语句(expression statement),表达式语句的作用是执行表达式并丢弃掉求值结果:

ival + 5;    // 无意义的表达式语句
cout << ival;// 有意义的表达式语句

空语句

最简单的语句是空语句(null statement),它只有一个分号:

; // 空语句

如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。

复合语句(块)

复合语句(compound statement)是指用花括号括起来的语句和声明的序列,复合语句也被称作(block)。一个块就是一个作用域。

如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,则应该使用复合语句。

所谓空块,是指内部没有任何语句的一对花括号。空块的作用等价于空语句:

while (cin >> s && s != sought)
{}  // 空块

语句作用域

可以在if、switch、while和for语句的控制结构内定义变量。定义在控制结构当中的变量只在相应语句的内部可见,一旦语句结束,变量也就超出其作用范围了:

while (int i = get_num()) // 每次迭代时创建并初始化
    cout << i << endl;
i = 0;    // 错误,在循环外部无法访问

my note: 如上述例子的写法,似乎没有什么实际意义。

条件语句

C++语言提供了两种按条件执行的语句。一种是if语句,它根据条件决定控制流;另一种是switch语句,它计算一个整型表达式的值,然后根据这个值从几条执行路径中选择一条。

if语句

if语句的作用是:判断一个指定的条件是否为真,根据判断结果决定是否执行另外一条语句。

my note: 这里讲解的语法自己已经很熟悉了,故不做更多笔记。

switch语句

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


my note

switch语句我已经很熟悉了,故不做更多笔记。

在switch内部定义变量,一个好的办法是:在case分支下,通过使用块把变量定义在块内,从而确保后面所有case标签都在变量的作用域之外。具体的原因见p163。

case true:
    {
        // 正确:声明语句位于语句块内部
        string file_name = get_file_name();
    }
    break;

my note: 要记住的是,C++语言规定,不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。基于此,在case分支后面定义并初始化一个变量很可能是不合法的,因为此分支可能被跳过,而另一个分支却访问了此变量。


迭代语句

迭代语句通常称之为循环,它重复执行操作直到满足某个条件才停下来。while和for语句在执行循环体之前检查条件,do while语句先执行循环体,然后再检查条件。

while语句

语法格式是:

while (condition)     statement

只要condition的求值结果为真就一直执行statement。如果condition第一次求值就是false,statement一次都不执行。

while的条件部分可以是一个表达式或者是一个带初始化的变量声明。

使用while循环

当不确定到底要迭代多少次时,使用while循环比较合适。还有一种情况也应该使用while循环,这就是我们想在循环结束后访问循环控制变量。

传统的for语句

for语句的语法形式是

for (init-statement: condition; expression)     statement

my note: 传统for语句我已经比较熟悉了,故不做更多笔记。

范围for语句

C++11新标准引入了一种更简单的for语句,这种语句可以遍历容器或其他序列的所有元素。范围for语句(range for statement)的语法形式是:

for (declaration : expression)     statement

expression必须是一个序列,比如用花括号括起来的初始值列表、数组、或者vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员。

declaration定义一个变量,序列中的每个元素都能转换成该变量的类型。

每次迭代都会重新定义循环控制变量,并将其初始化成序列中的下一个值,之后才会执行statement。

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

do while语句

do while语句和while语句非常相似,唯一的区别是,do while语句先执行循环体后检查条件。不管条件的值如何,我们都至少会执行一次循环。do while语句的语法形式如下:

do
    statement
while (condition);

condition使用的变量必须定义在循环体之外。

my note: 如果在condition中定义变量,是没有意义的,因为statement无法访问它,它在do while循环的外面也无法访问。

跳转语句

跳转语句中断当前的执行过程。C++语言提供了4种跳转语句:break, continue, goto和return。本章介绍前三种,return在第六章介绍(p199页)。

break语句

break语句负责终止离它最近的while, do while, for或switch语句,并从这些语句之后的第一条语句开始执行。

continue语句

continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。continue语句只能出现在for, while和do while循环的内部。

goto语句

goto语句(goto statement)的作用是从goto语句无条件跳转到同一函数内的另一条语句。

my note: 书上建议不要使用goto语句。我认为它应该有其合适的应用场景,只不过对于初学者并不需要。

goto语句的语法形式是:

goto lable:

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

end: return; // 带标签的语句,可以作为goto的目标

标签标示符独立于变量或其他标示符的名字,因此,标签标示符可以和程序中其他实体的标示符使用同一个名字而不会相互干扰。

和switch语句类似,goto语句也不能将程序的控制权从变量的作用域之外转移到作用域之内。

// ...
goto end:
    int ix = 10; // 错误,goto语句绕过了一个带初始化的变量定义

end:
    // 错误,goto绕过了ix的声明
    ix = 12;

跳回到变量定义之前意味着系统将销毁该变量,然后重新创建它:

// 向后跳过一个带初始化的变量定义是合法的
begin:
    int sz = get_size();
    if (sz <= 0)
        goto begin;

try语句块和异常处理

异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。

当程序的某部分检测到一个它无法处理的问题时,需要用到异常处理。此时,检测出问题的部分应该发出某种信号以表明程序遇到了故障,无法继续下去了,而信号的发出方无须知道故障将在何处得到解决。

如果程序中含有可能引发异常的代码,那么通常也会有专门的代码处理问题。

异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持。在C++语言中,异常处理包括:

  • throw 表达式(throw expression),异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发(raise)了异常。

  • try语句块(try block),异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句(catch clause)结束。try语句块中代码抛出的异常通常会被某个catch子句处理。

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

my note: 编写异常安全的代码非常困难,关于异常安全的概念见书本p175。另外,本书中会介绍一些比较常规的提升异常安全性的技术,但不会详细介绍编写异常处理的代码。

throw表达式

抛出异常的一个例子是:

throw runtime_error("Data must refer to same ISBN");

该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。

my note: 书本上有例子的详细解读,见p173。

try语句块

try语句块的通用语法形式是:

try {
    program-statements
} catch (exception-declaration) {
    handler-statements
} // ...

当选中了某个catch子句处理异常之后,执行与之对应的块。catch一旦完成,程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。

try语句块内声明的变量在catch子句内无法访问。

一个简要的例子(摘自书本):

while (cin >> item1 >> item2) {
    try {
        // ... 可能抛出一个异常的代码
    } catch (runtime_error err) {
        cout << err.what() << "\nTry Again? Enter y or n" << endl;
        char c;
        cin >> c;
        if (!cin || c == 'n')
            break; // 跳出while循环
    }
}

my note: 见书本更详细的解读。

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

当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没找到匹配的catch子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,直到找到适当类型的catch子句为止。

如果最终还是没能找到,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。

标准异常

C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以用在用户编写的程序中。这些异常类见书本p176。

异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串的异常说明。

如果异常类型有一个字符串初始值,则what返回该字符串,否则返回的内容由编译器决定。