第5章 语句
C++提供了条件执行语句、循环语句和用于中断当前控制流的跳转语句,本章将具体介绍这些语句。
5.1 简单语句
空语句
最简单的语句是空语句,只有一个单独的分号。如在程序的某个地方语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。
// 读取输入流的内容直到遇到一个特定值
while (cin >> s && s != sought)
; // 空语句,循环的全部工作在条件部分就可以完成
复合语句
用花括号括起来的语句和声明的序列,也称作块,一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。
5.2 语句作用域
可在if、switch、while和for语句的控制结构内定义变量,定义在控制结构中的变量只在语句相应的块内部可见,一旦语句结束,变量就超出其作用范围了。
for (int i = 0; i <= n; ++i) // 在for语句内部定义i,只能在for对应的块中使用
cout << i << endl;
i = 0; // 错误:在循环外部不能使用
5.3 条件语句
5.3.1 if 语句
if (condition1) // condition1类型必须能转换成布尔类型
statement1;
// condition1为真:执行statement1,执行完后执行后面的其他语句,跳过else if和else语句
else if (condition2)
statement2;
// condition1为假,condition2为真:跳过statement1,执行statement2,执行完后跳过else语句
else
statement3;
// condition1和condition2为假:跳过statement1和statement2,执行statement3
当statement中有多条语句想要一起执行时,要使用花括号{}将其括住。
当一个if语句嵌套在另一个if语句内部时,很可能if分支会多于else分支,C++规定else与离它最近的尚未匹配的if匹配。
5.3.2 switch 语句
switch (expression) { // 对表达式求值,然后与每个case标签比较
case val1: // 匹配成功则执行statement1,不成功则匹配val2
statement1;
break; // 遇到break后跳出switch语句
case val2:
statement2;
break;
case val3:
statement3;
break;
default: // 该语句可省略
statement4;
break;
// 若所有case标签都未匹配成功则执行紧跟在default标签后的语句
}
如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case、default分支(不管后面的case标签有没有匹配上),除非中断这一过程(如break)。
switch的执行过程可能会跨过某些case标签,如果程序跳转到了某个特定的case,则switch结构中该case标签之前的部分会被忽略掉。如果在某处一个带有初值的变量位于作用域之外,在另一处该变量位于作用域之内,则从前一处跳转到后一处是非法行为。
5.4 迭代语句
5.4.1 while 语句
当不确定到底要迭代多少次,或者在循环结束后想访问循环控制变量的可以用while循环。
while (condition)
statement
while语句的执行过程是交替地检测condition和执行statement,直至condition为假为止。condition可以是一个表达式或者一个带初始化的变量声明。应该由条件本身或者是循环体设法改变condition的值,否则循环可能无法终止。
定义在while条件部分或者while循环体内的变量每次迭代都经历从创建到销毁的过程。
5.4.2 传统的 for 语句
for (init-statement; condition; expression)
statement
for循环的执行过程如下:
init-statement必须是以下三种形式中的一种:声明语句、表达式语句或者空语句,可以定义多个对象,但是只能有一条声明语句,因此所有变量的基础类型必须相同。
for 语句头能省略掉init-statement、condition和expression中的任何一个(或者全部)
5.4.3 范围 for 语句
for (declaration: expression)
statement
expression表示的必须是一个序列,如用花括号括起来的初始值列表、数组或者vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员。
declaration定义一个变量,序列中每个元素都能转换成该变量的类型。为确保类型相容,可使用auto类型说明符。如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型。
每次迭代都会重新定义循环控制变量,并将其初始化成序列中的下一个值,之后才会执行statement。
vector<int> v = {0, 1, 2, 3, 4, 5};
// 范围变量必须是引用类型,这样才能执行写操作
for (auto &r : v)
r *= 2;
以上范围for语句等价于:
for (auto beg = v. begin(), end = v.end(); beg != end; ++beg) {
auto &r = *beg;
r *= 2;
}
5.4.4 do while 语句
do
statement
while (condition);
do while语句与while语句的区别是:do while语句先执行循环体statement,再检查条件condition,如果condition为假,循环终止,否则,重复循环过程。
condition使用的变量必须定义在循环体之外。
5.5 跳转语句
5.5.1 break 语句
break语句负责终止离它最近的while、do while、for或switch语句,并从这些语句之后的第一条语句开始继续执行。break语句只能出现在迭代语句或者switch语句内部(包括嵌套在此类循环里的语句或块的内部)。
5.5.2 continue 语句
continue语句负责终止离它最近的while、do while、for语句的当前迭代,并立即开始下一次迭代。continue语句只能出现在迭代语句while、do while、for内部(包括嵌套在此类循环里的语句或块的内部)。
对while或do while语句:继续判断条件的值;对于传统for循环:继续执行for语句头的expression;对于范围for语句:用序列中的下一个元素初始化循环控制变量。
5.5.3 goto 语句
goto 语句的作用是从goto语句无条件跳转到同一函数内的另一条语句。其形式为:
用标识符+冒号的形式写带标签的语句:
goto语句和控制权转向的那条带标签的语句必须位于同一个函数内,goto语句不能将程序的控制权从变量的作用域之外转移到作用域之内。
不要使用goto语句,因为它令程序很难理解且难修改。
5.6 try 语句块和异常处理
5.6.1 throw 表达式
int x, y;
cin >> x >> y;
if (y != 0) {
cout << x / y << endl;
return 0;
}
else {
cerr << "y must be not equal to zero" << endl;
return -1;
}
上述程序计算两个整数的除法运算,但在真实程序中,应把对象相除的代码和用户交互的代码分离开来。因此改写程序使得检查完成后抛出一个异常:
if (y == 0)
throw runtime_error("y must be not equal to zero")
cout << x / y << endl;
如果y等于0,就抛出一个异常,该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。
5.6.2 try 语句块
try {
program-statement
// 包括声明在内的任意语句,在语句块内部声明的变量无法在外部访问
}
catch (expression-declaration) {
handler-statements
}
catch (expression-declaration) {
handler-statements
} // ...
5.6.1中的代码用try语句块编写:
while (cin >> x >> y) {
try { // try语句块是程序本来要执行的任务
if (y == 0)
throw runtime_error("y must be not equal to zero")
cout << x / y << endl;
}
catch (runtime_error err) { // catch子句负责处理类型为runtime_error的异常
cout << err.what() // what是runtime_error类的一个成员函数
<< "\n Try Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
break;
}
}
在复杂系统中,程序遇到抛出异常代码前,其执行路径已经经过了多个try语句块,如当前try语句块被另一个try语句块调用,而当异常被抛出时,首先搜索抛出该异常的函数,若没找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找,以此类推。如一段程序没有try语句块且发生了异常,系统会调用terminate函数并终止当前程序的执行。
5.6.3 标准异常
C++标准库定义了一组类(分别在4个头文件中)用于报告标准库函数遇到的问题:
- exception:最通用的异常类exception,只报告异常的发生,不提供任何额外的信息。
- stdexcept:几种常用的异常类。包括之前提到的runtime_error等。
- new:bad_alloc异常类型。
- type_info:bad_cast异常类型。
标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。只能以默认初始化的方式初始化exception、bad_alloc和bad_cast对象,不允许为这些对象提供初始值。相反:应当使用string对象或者C风格字符串初始化这些类型的对象,但不允许使用默认初始化的方式。
异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串的const char*,该字符串的目的是提供关于异常的一些文本信息。