第5章 语句
表达式语句:
一个表达式末尾加上分号,就变成了表达式语句。表达式语句的作用是执行表达式并丢弃掉求值结果。
;//空语句:语法上需要一条语句但逻辑上不需要,此时应该使用空语句。
简答语句:
复合语句(块):是指用花括号括起来的(可能为空的)语句和声明序列,复合语句也被称作块(block)。一块就是一个作用域。while或for的循环体必须是一条语句,但要做很多事情,就用花括号括起来,从而把语句序列转变成块。
块不以分号作为结束。
空块的作用等价于空语句
switch:
不能使用逗号,应该用:,ival不是const;
unsigned odd_cnt = 0;
unsigned ival=512, jval=1024, kval=4096;
unsigned bufsize;
unsigned swt = get_bufCnt();
switch(swt) {
case 1, 3, 5, 7, 9:
++odd_cnt;
break;
case ival:
bufsize = ival * sizeof(int);
break;
}
return 0;
5.4 迭代语句
while和for在执行循环体之前检查条件,do while语句先执行循环体,然后再检查条件。
当不确定要迭代多少次时,使用while循环比较合适。还有一种情况是想在循环结束后访问循环控制变量。
while循环特别使用与那种条件保持不变、反复执行操作的情况,如当未达到文件末尾时不断读取一个值。
for循环更像是按步骤迭代,它的索引值在某一个范围内一次变化。
5.6 TRY语句块和异常处理
异常处理机制为程序中异常检测和异常处理两部分的协作提供支持。
throw表达式:引发异常
try语句块(try block)和catch子句(catch clause),后者被称为异常处理代码(exception handler)
异常类(exception class):用于在throw表达式和相关的catch子句之间传递异常的具体信息。
5.6.1 throw表达式
包括关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型,throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。异常抛出将终止当前的函数,并把控制权转移给处理该异常的代码。
try{
throw runtime_error("Data must refer to same ISBN");
} catch(runtime_error err) {
cout << err.what();
}
类型runtime_error是标准库异常类型的一种,定义在stdexcept头文件中。
catch子句包括三部分:关键字catch、括号内一个(可能未命名的)对象的声明(称异常声明)以及一个块。
try语句块内声明的变量在块外部无法访问,特别是在catch子句内无法访问。
catch子句,若没有找到,就沿着程序的执行路径逐层回退,直到找到适当类型的catch子句。若最终还是没有找见,就转到terminate的标准函数,该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。
如果一段程序没有try语句块且发生异常,系统会调用terminate函数并终止当前程序的执行。
在异常发生期间正常执行了“清理”工作的程序被称为异常安全的代码。然而经验表明,编写异常安全的代码非常困难,这部分知识也(远远)超出了本书的范围。
对于那些确实要处理异常并继续执行的程序,要加倍注意,必须时刻清楚异常何时发生,异常发生后程序如何确保对象有效、资源无泄漏、程序处于合理状态,等等。
5.6.3 标准异常
C++标准库定义了一组类,用于报告标准库函数遇到的问题,也可在用户编写的程序中使用,分别定义在4个头文件中:
- exception头文件,定义通用异常类exception。只报告异常的发生,不提供任何额外信息
- stdexcept头文件,定义了几种常用的异常类
exception 最常见的问题
runtime_error 只有在运行时才检查出的问题
range_error 运行时错误:生成的结果超出有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 程序逻辑错误
domain_error 逻辑错误:参数对应的结果值不存在
invalid_argument 逻辑错误:无效参数
length_error 逻辑错误:试图创建一个超出该类型长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
- new头文件,定义了bad_alloc异常类型
- type_info头文件定义了bad_cast异常类型
只能以默认初始化的方式初始化:exception、bad_alloc、bad_cast对象。其他异常类型应该使用string对象或C风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。该初始值含有错误相关的信息。
C++之父在谈C++语言设计的书(The Design and Evolution of C++)里谈到:
"low-level events, such as arithmetic overflows and divide by zero, are assumed to be handled by a dedicated lower-level mechanism rather than by exceptions. This enables C++ to match the behaviour of other languages when it comes to arithmetic. It also avoids the problems that occur on heavily pipelined architectures where events such as divide by zero are asynchronous."
简单翻译一下: “底层的事件,比如计算上的溢出和除0错,被认为应用有一个同样底层的机制去处理它们,而不是异常。这让C++在涉及到算术领域的性能,可以和其它语言竞争。再者,它也避免了当某些事件,比如除0错是异步的情况下(译者注:比如在别一个线程里发生)所可能造成的‘管道重重’的架构这一问题。”
所以,说起来,和原生数组访问越界为什么不是一异常并无两样,主要还是为了“效率/性能”。对于大多数时候的除法操作,我们会让它出现除数为0的可能性很小,当你认为有这种可能,就自己检查吧,然后自己定义一个除0错的异常。
很多C++库,还是实现了EDivByZero之类异常,但仅限于这个库里的代码。它们做了检查。
总结一下:C++为什么抓不到除0错的“异常”? 答:因为C++标准眼里,除0错不是一个异常。再进一步:C++编译器,在编译除法操作时,没有为它加上额外的检查代码以抛出一个异常;也没有要求处理不同OS之间对(已经发生的)除0错的处理。
另一个原因是,简单化,比如如果要判断除0错,浮点数如何判断呢?
说到底,在事情发生之前,判断一下不是更好?
Code:
if (b == 0)
{
cout << "别玩我啦...拿个0当除数?" << std::endl;
return -1;
}