天天看点

《C++ Primer(第5版)》学习笔记(第5章)第5章 语句

第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*,该字符串的目的是提供关于异常的一些文本信息。

继续阅读