参考资料:
- 《C++ Primer》第5版
- 《C++ Primer 习题集》第5版
5.1 简单语句(P154)
在一个表达式的末尾加上 ;
就构成了表达式语句,其作用是执行表达式并丢弃结果。
空语句
由单独的 ;
构成的语句为空语句。空语句常用于语法上需要一条语句但逻辑上不需要的场景:
while(cin>>a)
; // 空语句
使用空语句时应加上注释
别漏写分号,也别多写分号
while(iter != svec.end()); // 有害空语句
++iter;
复合语句
复合语句指用 {}
括起来的语句序列,也称作块。一个块就是一个作用域。
块不以分号结束
如果语法上需要一条语句,而逻辑上需要多条语句,就需要使用复合语句。例如,while
和 for
的循环体必须是一条语句,所以我们常常使用复合语句。
空块是没有任何语句的 {}
,其作用等价于空语句。
5.2 语句作用域(P155)
可以在 if
、switch
、while
、for
语句的控制结构内部定义变量,作用域仅限于该条语句。
while(int i = get_num()){
cout<<i; // 合法,i在while语句内部有效
}
cout<<i; // 错误
奇怪了,上述语句的控制结构里也不是语句啊?
5.3 条件语句(P156)
5.3.1 if
语句(P156)
if
语句包括两种格式:简单 if
语句和 if else
语句:
// 简单if语句
if(condition)
statement
// if else语句
if(condition)
statement1
else
statement2
其中,condition
可以是条件表达式,也可以是初始化了的变量声明。
嵌套if
语句
悬垂else
如何知道某个 else
和哪个 if
匹配,这个问题称作悬垂 else
。C++ 规定,else
与离它最近的尚未匹配的 if
语句。
5.3.2 switch
语句(P159)
例子:
switch(ch){
case 'a':
++aCnt;
break;
case 'e':
++eCnt;
break;
default:
++cnt;
}
switch
语句首先对括号里的表达式求值(也可以是初始化的变量声明),表达式的值转化为整数类型,然后与 case
中的每个标签比较,如果和某个 case
匹配成功,则从该标签后的,直到 switch
语句结尾或者遇到 break
语句。如果没有匹配成功,则会执行 switch
语句后的第一条语句。
case
标签必须是整型的常量表达式。
switch
内部的控制流
switch(ch){
case 'a': case 'e': case 'i': case 'o': case 'u':
++cnt;
}
default
标签
如果 switch
想以一个空的 default
标签作为结束,则必须在 default
标签后跟上空语句或空块。
switch(ch){
case 'a':
cout<<'a';
default: // default标签不是必须也在最后
cout<<'b';
case 'c':
cout<<'c';
}
switch
内部的变量定义
如果某处一个带有初值的变量位于作用域之外,在另一处该变量位于作用于之内,则从前一处条跳转到后一处是非法行为,因为 C++ 不允许跨过变量的初始化语句直接跳转到该变量的作用域内。
case true:
int i;
int j = 0; // 错误
string str; // 错误,str被隐式初始化了
case false:
;
需要注意的是,即使后面并没有用到前面初始化的变量,这种跳转也是不合法的。
5.4 迭代语句(P165)
5.4.1 while
语句(P165)
while(condition)
statement
condition
不能为空,只要 condition
的求值结果为真,就重复执行循环体。
5.4.2 传统for
语句(P166)
for(init-statement;condition;expression)
statement
init-statement
必须是声明语句、表达式语句或空语句。
for
语句头中的多重定义
init-statement
中可以声明多个对象,但只能有一条声明语句,所以所有对象的类型必须相同
省略for
语句头中的某些部分
省略 condition
等价于在条件部分写了一个 true
。
5.4.3 范围for
语句(P168)
for (declaration : expression)
statement
expression
必须是一个序列,如用花括号括起来的初始值列表、数组、vector
对象,这些类型的共同特点是有能返回迭代器的 begin
或 end
成员。
declaration
定义一个变量,且每次循环都会重新定义循环控制变量,并将其初始化为序列中的下一个值。
范围 for
语句中预存了序列 end()
的值,如果在循环中添加或删除序列元素,可能会导致 end
函数的值失效。
5.4.4 do while
语句(P169)
do
statement
while(condition);
do while
语句允许在 condition
内定义变量,conditon
使用的变量必须定义在循环体之外。
5.5 跳转语句(P170)
5.5.1 break
语句(P170)
break
负责终止离它最近的迭代语句和 switch
语句。
5.5.2 continue
语句(P171)
continue
语句可以在迭代语句中使用,负责终止当前迭代并立即开始下一次迭代。
5.5.3 goto
语句(P172)
goto label;
label: statement
label
是标识一条语句的标识符,可以与程序中的其他实体重名。goto
语句和目标标签的语句必须位于同一个函数之内。
5.6 try
语句块和异常处理(P172)
当程序某部分检测到一个无法处理的问题时,应该发出某种信号表明程序遇到了故障,然后交由异常处理部分处理。
5.6.1 throw
表达式(P173)
throw
表达式能引发一个异常,如:
throw runtime_error("Date must refer to same ISBN");
runtime_error
时标准库异常类型的一种,定义在 stdexcept
头文件中,runtime_error
对象必须初始化,方式是提供 string
对象或者 C 风格字符串。
5.6.2 try
语句块
try{
program-statements
} catch(exception-declaration){
handler-statements
} catch(exception-declaration){
handler-statements
}
catch
子句包括三部分:关键字 catch
、括号一个对象的声明、一个块。当某个 catch
子句处理异常后,执行其块,完成后跳转到最后一个 catch
子句的之后的语句执行。
try
语句块组成程序的正常逻辑,其中定义的变量无法在 catch
子句中访问。
int i = 0;
try{
cin >> i;
if(i <= 0) throw runtime_error("i must be positive");
} catch(runtime_error err){
cout << err.what() << endl;
}
what()
时 runtime_error
类的一个成员函数,每个标准库异常类都定义了 what()
成员,这些函数都没有参数,返回值为 C 风格字符串。
函数在寻找处理代码的过程中退出
当异常抛出时,首先检查抛出该异常的函数,如果没找到匹配的 catch
子句,则终止该函数,然后在调用该函数的函数中继续寻找,以此类推。特别地,那些没有包含在 try
语句块中的异常认为在当前函数没有 catch
子句与之匹配。如果最终也没能找到匹配的 catch
,程序将转到名为 terminate
的标准库函数,执行该函数通常会导致程序非正常退出。
5.6.3 标准异常(P176)
C++ 定义了一组类,用于报告程序遇到的问题。
exception
、bad_alloc
和 bad_cast
对象只能默认初始化,其他对象则必须提供初始值。
异常类型只定义了一个 what()
成员函数,对于没有初始值的对象,what()
函数的返回值由编译器决定。