# 语句与语句作用域
# 简单语句
表达式语句(expression statement):执行表达式并丢弃掉求值结果
空语句(null statement):空语句中只含有一个单独的分号
例如:
// 重复读入数据直至到达文件末尾或某次输入的值等于 sought | |
while (cin >> s && s != sought) | |
; // 空语句 |
使用空语句时应该加上注释,从而令读这段代码的人知道该语句是有意省略的
别漏写分号,也别多写分号:
ival = v1 + v2;; // 正确:第二个分号表示一条多余的空语句 | |
while (iter != svec.end()) ; //while 循环体是那条空语句(无休止循环) | |
++iter; // 递增运算不输于循环的一部分 |
复合语句(compound statement)是指用花括号括起来的(可能为空的)语句和声明的序列,复合语句也被称作 块(block)
一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问
块不以分号作为结束
while (val <= 10) { | |
sum += val; | |
++val; | |
} |
空块:是指内部没有任何语句的一对花括号。空块的作用等价于空语句
while (cin >> s && s != sought) | |
{ } // 空块 |
# 语句作用域
可以在 if 、switch 、while 和 for 语句的控制结构内定义变量。但是需要注意,定义在控制结构中的变量只在相应语句的内部可见,一旦语句结束,变量也就超出其作用范围了
while (int i = get_num()) // 每次迭代时创建并初始化 i | |
cout << i << endl; | |
i = 0; // 错误:在循环外部无法访问 i |
如果其他代码也需要访问控制变量,则变量必须定义在语句的外部
// 寻找第一个负值元素 | |
auto beg = v.begin(); // 注意要初始化 | |
while (beg != v.end() && *beg >= 0) | |
++beg; | |
if (beg == v.end()) | |
// 此时我们知道 v 中的所有元素都大于等于 0 |
# 条件语句
C++ 语言提供了两种按条件执行的语句
- if 语句:根据条件决定控制流
- switch 语句:计算一个整型表达式的值,然后根据这个值从几条执行路径中选择一条
# if 语句
简单 if 语句的语法形式:
if (condition)
statement
if else 语句的形式:
if (condition)
statement
else
statement2
如果 condition 为真,执行 statement 。当 statement 执行完成后,程序继续执行 if 语句后面的其他语句
如果 condition 为假,跳过 statement 。对于简单 if 语句来说,程序继续执行 if 语句后面的其他语句;对于 if else 语句来说,执行 statement2
注意:在这两个版本的 if 语句中,condition 都必须用圆括号包围起来
condition 可以是一个表达式,也可以是一个初始化了的变量声明。不管是表达式还是变量,其类型都必须能转换成布尔类型
# 嵌套 if 语句
例如:把数字形式表示的成绩转换成字母形式,并在合格的成绩后面添加一个加号或减号(如果成绩的末位是 8 或者 9 ,添加一个加号;如果末位是 0 、1 或 2 ,添加一个减号)
vector<string> scores = {"F", "D", "C", "B", "A", "A++"}; | |
string lettergrade; | |
if (grade < 60) // 成绩不合格,对应的字母是 F | |
lettergrade = scores[0]; | |
else { | |
lettergrade = scores[(grade - 50)/10]; // 获得字母形式的成绩 | |
if (grade != 100) // 只要不是 A++(100 分),就考虑添加加号或者减号 | |
if (grade % 10 > 7) | |
lettergrade += '+'; // 末尾是 8 或者 9 的成绩添加一个加号 | |
else if (grade % 10 < 3) | |
lettergrade += '-'; // 末尾是 0 、1 或者 2 的成绩添加一个减号 | |
} |
注意使用花括号
# 悬垂 else
当一个 if 语句嵌套在另一个 if 语句内部时,很可能 if 分支会多于 else 分支。此时,我们怎么知道某个给定的 else 是和哪个 if 匹配呢?
这个问题通常称作 悬垂 else(dangling else)
C++ 规定 else 与离它最近的尚未匹配的 if 匹配
In C++, the ambiguity is resolved by specifying that each
else
is matched with the closest preceding unmatchedif
.
# 使用花括号控制执行路径
要想使 else 分支和外层的 if 语句匹配起来,可以在内层 if 语句的两端加上花括号,使其成为一个块
// 末位是 8 或者 9 的成绩添加一个加号;如果末位是 0 、1 或 2 的成绩添加一个减号 | |
if (grade % 10 >= 3) { | |
if (grade % 10 > 7) | |
lettergrade += '+'; // 末尾是 8 或者 9 的成绩添加一个加号 | |
} else // 花括号强迫 else 与外层 if 匹配 | |
lettergrade += '-'; // 末位是 0 、1 或 2 的成绩添加一个减号 |
# switch 语句
switch 语句(switch statement)提供了一条便利的途径使得我们能够在若干固定选项中做出选择
例如,利用 switch 语句统计五个元音字母在文本中出现的次数
// 为每个元音字母初始化其计数值 | |
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; | |
char ch; | |
while (cin >> ch) { | |
// 如果 ch 是元音字母,将其对应的计数值加 1 | |
switch (ch) { | |
case 'a': | |
++aCnt; | |
break; | |
case 'e': | |
++eCnt; | |
break; | |
case 'i': | |
++iCnt; | |
break; | |
case 'o': | |
++oCnt; | |
break; | |
case 'u': | |
++uCnt; | |
break; | |
} | |
} | |
// 打印结果 | |
cout << "Number of vowel a: \t" << aCnt << '\n' | |
<< "Number of vowel e: \t" << eCnt << '\n' | |
<< "Number of vowel i: \t" << iCnt << '\n' | |
<< "Number of vowel o: \t" << oCnt << '\n' | |
<< "Number of vowel u: \t" << uCnt << endl; |
switch 语句首先对括号里的表达式求值,该表达式(可以是一个初始化的变量声明)紧跟在关键字 switch 的后面。表达式的值转换成整数类型,然后与每个 case 标签的值比较
如果表达式和某个 case 标签的值匹配成功,程序从该标签之后的第一条语句开始执行,直到到达了 switch 的结尾或者是遇到一条 break 语句为止
break 语句的作用是中断当前的控制流。此例中,break 语句将控制权转移到 switch 语句外面
如果 switch 语句的表达式和所有 case 都没有匹配上,将直接跳转到 switch 结构之后的第一条语句
case 关键字和它对应的值一起被称为 case 标签(case label)
case 标签必须是整型常量表达式
char ch = getVal(); | |
int ival = 42; | |
switch(ch) { | |
case 3.14: // 错误:case 标签不是一个整数 | |
case ival: // 错误:case 标签不是一个常量 | |
// ... |
任何两个 case 标签的值不能相同,否则就会引发错误
default 也是一种特殊的 case 标签
# switch 内部的控制流
如果某个 case 标签匹配成功,将从该标签开始往后顺序执行所有 case 分支,除非程序显式地中断了这一过程,否则,执行到 switch 的结尾处才会停下来
要想避免执行后续 case 分支的代码,我们必须显式地告诉编译器终止执行过程。大多数情况下,在下一个 case 标签之前应该有一条 break 语句
然而,由于每个 case 标签只能对应一个值,有时候我们希望两个或更多个值共享同一组操作。此时,我们就故意省略掉 break 语句,使得程序能够连续执行若干个 case 标签
例如:统计所有元音字母出现的总次数
unsigned vowelCnt = 0; | |
// ... | |
switch (ch) { | |
case 'a': | |
case 'e': | |
case 'i': | |
case 'o': | |
case 'u': | |
++vowelCnt; | |
break; | |
} |
C++ 程序的形式比较自由,所以 case 标签之后不一定非得换行。把几个 case 标签写在一行里,强调这些 case 代表的是某个范围内的值
switch (ch) { | |
// 另一种合法的书写形式 | |
case 'a': case 'e': case 'i': case 'o': case 'u': | |
++vowelCnt; | |
break; | |
} |
一般不要省略 case 分支最后的 break 语句。如果没写 break 语句,最好加一段注释说清楚程序的逻辑
尽管 switch 语句不是非得在最后一个标签后面写上 break ,但是为了安全起见,最好这么做。因为这样的话,即使以后再增加新的 case 分支,也不用再在前面补充 break 语句了
# default 标签
如果没有任何一个 case 标签能匹配上 switch 表达式的值,程序将执行紧跟在 default 标签(default label)后面的语句
例如,可以增加一个计数值来统计非元音字母的数量
switch (ch) { | |
case 'a': case 'e': case 'i': case 'o': case 'u': | |
++vowelCnt; // 元音字母的数量 | |
break; | |
default: | |
++otherCnt; // 非元音字母的数量 | |
break; | |
} |
即使不准备在 default 标签下做任何工作,定义一个 default 标签也是有用的。其目的在于告诉程序的读者,我们已经考虑到了默认的情况,只是目前什么也没做
标签不应该孤零零地出现,它后面必须跟一条语句或者另外一个 case 标签。如果 switch 结构以一个空的 default 标签作为结束,则该 default 标签后面必须跟一条空语句或一个空块
# 迭代语句
迭代语句通常称为循环,它重复执行操作直到满足某个条件才停下来
- while 和 for 语句在执行循环体之前检查条件
- do while 语句先执行循环体,然后再检查条件
# while 语句
只要条件为真,while 语句(while statement)就重复地执行循环体
语法形式为:
while (condition)
statement
condition 不能为空,如果 condition 第一次求值就得 false , statement 一次都不执行
condition 可以是一个表达式或者是一个带初始化的变量声明
定义在 while 条件部分或者 while 循环体内的变量,每次迭代都经历从创建到销毁的过程
当不确定到底要迭代多少次时,使用 while 循环比较合适
例如:
vector<int> v; | |
int i; | |
// 重复读入数据,直至到达文件末尾或者遇到其他输入问题 | |
while (cin >> i) | |
v.push_back(i); | |
// 寻找第一个负值元素 | |
auto beg = v.begin(); | |
while (beg != v.end() && *beg >= 0) | |
++beg; | |
if (beg == v.end()) | |
// 此时我们知道 v 中的所有元素都大于等于 0 |
# 传统的 for 语句
for 语句的语法形式:
for (init-statement; condition; expression)
statement
关键字 for 及括号里的部分称为 for 语句头
init-statement 必须是以下三种形式中的一种:声明语句、表达式语句或者空语句。因为这些语句都以分号作为结束,for 语句的语法形式也可以看作:
for (initializer; condition; expression)
statement
其中:
- init-statement 负责初始化一个值,这个值将随着循环的进行而改变
- condition 作为循环控制的条件,只要 condition 为真,就执行一次 statement 。如果 condition 第一次的求值结果就是 false ,则 statement 一次也不会执行
- expression 负责在每次循环迭代之后修改 init-statement 初始化的变量,这个变量正好就是 condition 检查的对象
- statement 可以是一条单独的语句也可以是一条复合语句
for 语句头中定义的对象只在 for 循环体内可见
# for 语句头中的多重定义
init-statement 可以定义多个对象,但只能有一条声明语句,因此,所有变量的基础类型必须相同
例如:下列代码在 init-statement 里同时定义了索引 i 和循环控制变量 sz
// 记录下 v 的大小,当到达原来的最后一个元素后结束循环 | |
for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i) | |
v.push_back(v[i]); |
# 省略 for 语句头的某些部分
for 语句头能省略掉 init-statement 、condition 和 expression 中的任何一个(或者全部)
如果无须初始化,则我们可以使用一条空语句作为 init-statement ,例如
auto beg = v.begin(); | |
for ( /* 空语句 */; beg != v.end() && *beg >= 0; ++beg) | |
; // 什么也不做 |
注意,分号必须保留,以表明我们省略掉了 init-statement 。说得更准确一点,分号表示的是一个空的 init-statement
省略 condition 的效果等价于在条件部分写了一个 true 。因为条件的值永远是 true ,所以在循环体内必须有语句负责退出循环,否则循环就会无休止地执行下去
for (int i = 0; /* 条件为空 */ ; ++i) { | |
// 对 i 进行处理,循环内部的代码必须能够终止迭代过程 | |
} |
我们也能省略掉 for 语句头中的 expression ,但是在这样的循环中就要求条件部分或者循环体必须改变迭代变量的值
vector<int> v; | |
for (int i; cin >> i; /* 表达式为空 */ ) | |
v.push_back(i); |
因为条件部分能改变 i 的值,所以这个循环无须表达式部分。其中,条件部分不断检查输入流的内容,只要读取完所有的输入或者遇到一个输入错误就终止循环
# 范围 for 语句
C++ 11 新标准引入了一种更简单的 for 语句,这种语句可以遍历容器或其他序列的所有元素
范围 for 语句(range for statement)的语法形式:
for (declaration : expression)
statement
expression 表示的必须是一个序列,比如用花括号括起来的初始值列表、数组或者 vector 或 string 等类型的对象,这些类型的共同特点是拥有能返回迭代器的 begin 和 end 成员
declaration 定义一个变量,序列中的每个元素都得能转换成该变量的类型
- 确保类型相容最简单的办法是使用 auto 类型说明符
- 如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型
每次迭代都会重新定义循环控制变量,并将其初始化成序列中的下一个值,之后才会执行 statement
statement 可以是一条单独的语句也可以是一个块
所有元素都处理完毕后循环终止
例如:把 vector 对象中的每个元素都翻倍
vector<int> v = {0,1,2,3,4,5,6,7,8,9}; | |
// 范围变量必须是引用类型,这样才能对元素执行写操作 | |
for (auto &r : v) // 对于 v 中的每一个元素 | |
r *= 2; // 将 v 中每个元素的值翻倍 |
其等价于
for (auto beg = v.begin(), end = v.end(); beg != end; ++beg) { | |
auto &r = *beg; | |
r *= 2; | |
} |
不能通过范围 for 语句增加 vector 对象(或者其他容器)的元素:因为在范围 for 语句中,预存了 end () 的值。一旦在序列中添加(删除)元素,end 函数的值就可能变得无效了
# do while 语句
do while 语句(do while statement)和 while 语句非常相似,唯一的区别是,do while 语句先执行循环体后检查条件
即,不管条件的值如何,do while 语句都至少执行一次循环
do while 语句的语法形式:
do
statement
while (condition);
do while 语句应该在后面用一个分号表示语句结束
在 do while 语句中,首先执行一次 statement 然后才求 condition 的值( condition 不能为空)。如果 condition 的值为假,循环终止;否则,重复循环过程
condition 使用的变量必须定义在循环体之外
例如,可以使用 do while 循环(不断地)执行加法运算
// 不断提示用户输入一对数,然后求其和 | |
string rsp; // 作为循环的条件,不能定义在 do 的内部 | |
do { | |
cout << "please enter two values: "; | |
int val1 = 0, val2 = 0; | |
cin >> val1 >> val2; | |
cout << "The sum of " << val1 << " and " << val2 | |
<< " = " << val1 + val2 << "\n\n" | |
<< "More? Enter yes or no: "; | |
cin >> rsp; | |
} while (!rsp.empty() && rsp[0] != 'n'); |
因为 do while 先执行语句(或者块)后判断条件,所以不允许在条件部分定义变量
do { | |
// . . . | |
mumble(foo); | |
} while (int foo = get_foo()); // 错误:将变量声明放在了 do while 的条件部分 |
# 跳转语句
跳转语句用于中断当前的执行过程
C++ 语言提供了 4 种跳转语句:
- break
- continue
- goto
- return
# break 语句
break 语句(break statement)负责终止离它最近的 while 、do while 、for 或 switch 语句,并从这些语句之后的第一条语句开始继续执行
break 语句只能出现在迭代语句或者 switch 语句内部(包括嵌套在此类循环里的语句或块的内部)。break 语句的作用范围仅限于最近的循环或者 switch
例如:
string buf; | |
while (cin >> buf && !buf.empty()) { | |
switch(buf[0]) { | |
case '-': | |
// 处理到第一个空白为止 | |
for (auto it = buf.begin()+1; it != buf.end(); ++it) { | |
if (*it == ' ') | |
break; // #1,离开 for 循环 | |
// . . . | |
} | |
//break #1 将控制权转移到这里 | |
// 剩余的 '-' 处理 | |
break; // #2,离开 switch 语句 | |
case '+': | |
// . . . | |
} // 结束 switch | |
//break #2 将控制权转移到这里 | |
} // 结束 while |
标记为 #1 的 break 语句负责终止连字符 case 标签后面的 for 循环。它不但不会终止 switch 语句,甚至连当前的 case 分支也终止不了。接下来,程序继续执行 for 循环之后的第一条语句,这条语句可能接着处理连字符的情况,也可能是另一条用于终止当前分支的 break 语句
标记为 #2 的 break 语句负责终止 switch 语句,但是不能终止 while 循环。执行完这个 break 后,程序继续执行 while 的条件部分
# continue 语句
continue 语句(continue statement)终止最近的循环中的当前迭代并立即开始下一次迭代
continue 语句只能出现在 for 、while 和 do while 循环的内部,或者嵌套在此类循环里的语句或块的内部
和 break 语句类似的是,出现在嵌套循环中的 continue 语句也仅作用于离它最近的循环
和 break 语句不同的是,只有当 switch 语句嵌套在迭代语句内部时,才能在 switch 里使用 continue (即,continue 对循环起作用,而不对 switch 起作用)
continue 语句中断当前的迭代,但是仍然继续执行循环
- 对于 while 或者 do while 语句来说,继续判断条件的值
- 对于传统的 for 循环来说,继续执行 for 语句头的 expression
- 对于范围 for 语句来说,用序列中的下一个元素初始化循环控制变量
例如:从标准输入中读取单词并处理以下画线开头的单词
string buf; | |
while (cin >> buf && !buf.empty()) { | |
if (buf[0] != '_') | |
continue; // get another input | |
// still here? the input starts with an underscore; process buf . . . | |
} |
# goto 语句
goto 语句(goto statement)的作用:从 goto 语句无条件跳转到同一函数内的另一条语句
不要在程序中使用 goto 语句,因为它使得程序既难理解又难修改
goto 语句的语法形式:
goto label;
其中,label 是用于标识一条语句的标示符
带标签语句(labeled statement)是一种特殊的语句,在它之前有一个标示符以及一个冒号,例如:
end: return; // 带标签语句,可以作为 goto 的目标 |
标签标示符独立于变量或其他标示符的名字,因此,标签标示符可以和程序中其他实体的标识符使用同一个名字而不会相互干扰
goto 语句和控制权转向的那条带标签的语句必须位于同一个函数之内
和 switch 语句类似,goto 语句也不能将程序的控制权从变量的作用域之外转移到作用域之内:
// . . . | |
goto end; | |
int ix = 10; // 错误:goto 语句绕过了一个带初始化的变量定义 | |
end: | |
// 错误:此处的代码需要 ix,但是 goto 语句绕过了它的声明 | |
ix = 42; |
向后跳过一个已经执行的定义是合法的。跳回到变量定义之前意味着系统将销毁该变量,然后重新创建它。例如:
// backward jump over an initialized variable definition is okay | |
begin: | |
int sz = get_size(); | |
if (sz <= 0) { | |
goto begin; | |
} |
在上面的代码中,goto 语句执行后将销毁 sz 。因为跳回到 begin 的动作跨过了 sz 的定义语句,所以 sz 将重新定义并初始化
# 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 子句之间传递异常的具体信息
# throw 表达式
throw 表达式包含关键字 throw 和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型
throw 表达式后面通常紧跟一个分号
例如:在执行两个 Sales_item 对象相加时,需要先检查它们是否是关于同一种书籍的
Sales_item item1, item2; | |
cin >> item1 >> item2; | |
// 首先检查两条数据是否是关于同一种书籍的 | |
if (item1.isbn() != item2.isbn()) | |
throw runtime_error("Data must refer to same ISBN"); | |
// 如果程序执行到这里,则说明两个 ISBN 相同 | |
cout << item1 + item2 << endl; |
在这段代码中,如果 ISBN 不一样,就会抛出一个异常(该异常是类型 runtime_error 的对象)
类型 runtime_error 是标准库异常类型的一种,定义在 stdexcept 头文件中
抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码
# try 语句块
try 语句块包括关键字 try 以及紧随其后的一个块(花括号括起来的语句序列),其通用语法形式为:
try { | |
program-statements | |
} catch (exception-declaration) { | |
handler-statements | |
} catch (exception-declaration) { | |
handler-statements | |
} // ... |
跟在 try 块之后的是一个或多个 catch 子句
catch 字句包括三部分:
- 关键字 catch
- 括号内一个(可能未命名的)对象的声明(称作异常声明,exception declaration)
- 一个块(用于处理异常)
当选中某个 catch 子句处理异常之后,就会执行与之对应的块
catch 一旦完成,程序将会跳转到 try 语句块对应的最后一个 catch 子句之后,以继续执行后续语句
try 语句块中的 program-statements 可以是任意 C++ 语句,包括声明在内的
try 语句块内声明的变量在块外部无法访问,特别是,在 catch 子句内也无法访问
# 编写 handler-statements
例如:可以将程序原本需要执行的任务放在 try 语句块中(这段代码可能会抛出一个 runtime_error 类型的异常),然后在 try 语句块之后跟一个 catch 字句(用于处理类型为 runtime_error 的异常)
while (cin >> item1 >> item2) { | |
try { | |
if (item1.isbn() != item2.isbn()) | |
throw runtime_error("Data must refer to same ISBN"); | |
cout << item1 + item2 << endl; | |
} catch (runtime_error err) { // 异常类型为 runtime_error ,异常命名为 err | |
// 提醒用户两个 ISBN 必须一致,询问是否重新输入 | |
cout << err.what() | |
<< "\nTry Again? Enter y or n" << endl; | |
char c; | |
cin >> c; | |
if (!cin || c == 'n') | |
break; // 用户输入 'n',跳出 while 循环 | |
// 否则,程序控制权跳回到 while 条件部分,准备下一次迭代 | |
} | |
} |
其中,what 是 runtime-error 类的一个成员函数,返回的是(初始化一个具体对象时所用的)string 对象的副本
在上例中,如果 try 语句块中抛出异常,catch 子句将会输出:
Data must refer to same ISBN
Try Again? Enter y or n
# 函数在寻找处理代码的过程中退出
程序在遇到抛出异常的代码前,可能已经经过了多个 try 语句块(例如,一个 try 语句块可能调用了包含另一个 try 语句块的函数)
此时,寻找 异常处理代码 的过程为:
- 当异常被抛出时,首先搜索抛出该异常的函数
- 如果没找到匹配的 catch 子句,则终止该函数,并在调用该函数的函数中继续寻找
- 以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的 catch 子句为止
如果最终还是没能找到任何匹配的 catch 子句,程序将会转到名为 terminate 的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出
如果一段程序没有 try 语句块且发生了异常,系统也会调用 terminate 函数并终止当前程序的执行
# 标准异常
C++ 标准库定义了一组类,用于报告标准库函数遇到的问题
C++ 定义了 4 个包含异常类的头文件:
- exception 头文件定义了最通用的异常类 exception,它只报告异常的发生,不提供任何额外信息
- stdexcept 头文件定义了几种常用的异常类(见表 5.1)
- new 头文件定义了 bad_alloc 异常类型
- type_info 头文件定义了 bad_cast 异常类型
标准库异常类只定义了以下几种运算:
- 创建或拷贝异常类型的对象
- 为异常类型的对象赋值
对于 exception 、bad_alloc 和 bad_cast 异常类型,只能以默认初始化的方式来初始化这些类型的对象,而不允许为这些对象提供初始值
对于其他异常类型,则应该使用 string 对象或者 C 风格字符串来初始化这些类型的对象,而不允许使用默认初始化的方式
- 当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息
每个异常类型都只定义了一个名为 what 的成员函数:
- what 函数没有任何参数,返回值是一个指向 C 风格字符串的
const char*
(该字符串的目的是提供关于异常的一些文本信息) - what 函数返回的 C 风格字符串的内容与异常对象的类型有关:如果异常类型有一个字符串初始值,则 what 返回该字符串;对于其他无初始值的异常类型,what 返回的内容由编译器决定
# 术语表
块(block):包围在花括号内的由 0 条或多条语句组成的序列。块也是一条语句,所以只要是能使用语句的地方,就可以使用块
break 语句(break statement):终止离它最近的循环或 switch 语句。控制权转移到循环或 switch 之后的第一条语句
case 标签(case label):在 switch 语句中紧跟在 case 关键字之后的常量表达式。在同一个 switch 语句中任意两个 case 标签的值不能相同
catch 子句(catch clause):由 catch 关键字、括号里的异常声明以及语句块三部分组成。catch 子句的代码负责处理在异常声明中定义的异常
复合语句(compound statement):块
continue 语句(continue statement):终止离它最近的循环的当前迭代。控制权转移到 while 或 do while 语句的条件部分、或者范围 for 循环的下一次迭代、或者传统 for 循环头部的表达式
悬垂 else(dangling else):是一个俗语,指的是如何处理嵌套 if 语句中 if 分支多于 else 分支的情况
- C++ 语言规定,else 应该与前一个未匹配的 if 匹配在一起
- 使用花括号可以把位于内层的 if 语句隐藏起来,这样就能更好地控制 else 该与哪个 if 匹配
default 标签(default label):是一种特殊的 case 标签,当 switch 表达式的值与所有 case 标签都无法匹配时,程序执行 default 标签下的内容
do while 语句(do while statement):与 while 语句类似,但 do while 语句先执行循环体,再判断条件。循环体代码至少会执行一次
异常类(exception class):标准库定义的一组类,用于表示程序发生的错误
异常声明(exception declaration):位于 catch 子句中的声明,指定了该 catch 子句能处理的异常类型
异常处理代码(exception handler):程序某处引发异常后,用于处理该异常的另一处代码。和 catch 子句是同义词
异常安全(exception safe):是一个术语,表示的含义是当抛出异常后,程序能执行正确的行为
表达式语句(expression statement):即一条表达式后面跟上一个分号,令表达式执行求值过程
控制流(flow of control):程序的执行路径
for 语句(for statement):提供迭代执行的迭代语句。常常用于遍历一个容器或者重复计算若干次
goto 语句(goto statement):令控制权无条件转移到同一函数中一个指定的带标签语句。goto 语句容易造成程序的控制流混乱,应禁止使用
if else 语句(if else statement):判断条件,根据其结果分别执行 if 分支或 else 分支的语句
if 语句(if statement):判断条件,根据其结果有选择地执行语句。如果条件为真,执行 if 分支的代码;如果条件为假,控制权转移到 if 结构之后的第一条语句
带标签语句(labeled statement):前面带有标签的语句。所谓标签是指一个标识符以及紧跟着的一个冒号。对于同一个标识符来说,用作标签的同时还能用于其他目的,互不干扰
空语句(null statement):只含有一个分号的语句
引发(raise):含义类似于 throw 。在 C++ 语言中既可以说抛出异常,也可以说引发异常
范围 for 语句(range for statement):在一个序列中进行迭代的语句
switch 语句(switch statement):一种条件语句
- 首先求 switch 关键字后面表达式的值
- 如果某个 case 标签的值与表达式的值相等,程序直接跨过之前的代码从这个 case 标签开始执行
- 当所有 case 标签都无法匹配时,如果有 default 标签,从 default 标签继续执行;如果没有,结束 switch 语句
terminate :是一个标准库函数,当异常没有被捕捉到时调用。terminate 终止当前程序的执行
throw 表达式(throw expression):一种中断当前执行路径的表达式。throw 表达式抛出一个异常并把控制权转移到能处理该异常的最近的 catch 子句
try 语句块(try block):跟在 try 关键字后面的块,以及一个或多个 catch 子句。如果 try 语句块的代码引发异常并且其中一个 catch 子句匹配该异常类型,则异常被该 catch 子句处理。否则,异常将由外围 try 语句块处理,或者程序终止
while 语句(while statement):只要指定的条件为真,就一直迭代执行目标语句
参考:C++ Primer 中文版(第 5 版)