1.switch结构中,case 标号必须是整型常量表达式,否则会出现编译错误。如果两个 case 标号具有相同的值,同样也会导致编译时的错误。
对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量:
case true: // error: declaration precedes a case label string file_name = get_file_name(); break; case false: // ...
制定这个规则是为避免出现代码跳过变量的定义和初始化的情况。
回顾变量的作用域,变量从它的定义点开始有效,直到它所在块结束为止。现在考虑如果在两个 case 标号之间定义变量会出现什么情况。该变量会在块结束之前一直存在。对于定义该变量的标号后面的其他 case 标号,它们所关联的代码都可以使用这个变量。如果 switch 从那些后续 case 标号开始执行,那么这个变量可能还未定义就要使用了。
在这种情况下,如果需要为某个特殊的 case 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。
case true:
{
// ok: declaration statement within a statement block
string file_name = get_file_name();
// ...
}
break;
case false:
// ...
2.for语句:
可以在 for 语句的 init-statement 中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型
const int size = 42;
int val = 0, ia[size];
// declare 3 variables local to the for loop:
// ival is an int, pi a pointer to int, and ri a reference to int
for (int ival = 0, *pi = ia, &ri = val;
ival != size;
++ival, ++pi, ++ri)
// ...
3.do while
string rsp; // used in the condition; can't be defined inside the do
do {
cout << "please enter two values: ";
int val1, val2;
cin >> val1 >> val2;
cout << "The sum of " << val1 << " and " << val2
<< " = " << val1 + val2 << "/n/n"
<< "More? [yes][no] ";
cin >> rsp;
} while (!rsp.empty() && rsp[0] != 'n');
因为要到循环语句或者语句块执行之后,才求解循环条件,因此 do while 循环不可以采用如下方式定义变量:
// error: declaration statement within do condition is not supported
do {
// ...
mumble(foo);
} while (int foo = get_foo()); // error: declaration in do condition
如果可以在循环条件中定义变量的话,则对变量的任何使用都将发生在变量定义之前!
4.break语句:
break 只能出现在循环或 switch 结构中,或者出现在嵌套于循环或 switch 结构中的语句里。
对于 if 语句,只有当它嵌套在 switch 或循环里面时,才能使用 break。break 出现在循环外或者 switch 外将会导致编译时错误。当 break 出现在嵌套的 switch 或者循环语句中时,将会终止里层的 switch 或循环语句,而外层的 switch 或者循环不受影响。
5.continue语句:
continue 语句导致最近的循环语句的当次迭代提前结束。对于 while 和 do while 语句,继续求解循环条件。而对于 for 循环,程序流程接着求解 for 语句头中的 expression 表达式。
6.goto语句:
goto 语句的语法规则如下:
goto label;
其中 label 是用于标识带标号的语句的标识符。在任何语句前提供一个标识符和冒号,即得带标号的语句:
end: return; // labeled statement, may be target of a goto
形成标号的标识符只能用作 goto 的目标。因为这个原因,标号标识符可以与变量名以及程序里的其他标识符一样,不与别的标识符重名。goto 语句和获得所转移的控制权的带标号的语句必须位于于同一个函数内。
goto 语句不能跨越变量的定义语句向前跳转:
// ...
goto end;
int ix = 10; // error: goto bypasses declaration statement
end:
// error: code here could use ix but the goto bypassed its declaration
ix = 42;
如果确实需要在 goto 和其跳转对应的标号之间定义变量,则定义必须放在一个块语句中。
7.异常处理:
C++ 的异常处理中包括:
(1)throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw 引发了异常条件。
系统通过 throw 表达式抛出异常。throw 表达式由关键字 throw 以及尾随的表达式组成,通常以分号结束,这样它就成为了表达式语句。throw 表达式的类型决定了所抛出异常的类型。
throw 语句使用了一个表达式。在本例中,该表达式是 runtime_error 类型的对象。runtime_error 类型是标准库异常类中的一种,在 stdexcept 头文件中定义。
(2)try 块,错误处理部分使用它来处理异常。try 语句块以 try 关键字开始,并以一个或多个 catch 子句结束。在 try 块中执行的代码所抛出(throw)的异常,通常会被其中一个 catch 子句处理。由于它们“处理”异常,catch 子句也称为处理代码。
try 块的通用语法形式是:
try {
program-statements
} catch (exception-specifier) {
handler-statements
} catch (exception-specifier) {
handler-statements
} //...
try 块以关键字 try 开始,后面是用花括号起来的语句序列块。try 块后面是一个或多个 catch 子句。每个 catch 子句包括三部分:关键字 catch,圆括号内单个类型或者单个对象的声明——称为异常说明符,以及通常用花括号括起来的语句块。如果选择了一个 catch 子句来处理异常,则执行相关的块语句。一旦 catch 子句执行结束,程序流程立即继续执行紧随着最后一个 catch 子句的语句。
(3)由标准库定义的一组异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。
8.标准异常:
C++ 标准库定义了一组类,用于报告在标准库中的函数遇到的问题。程序员可在自己编写的程序中使用这些标准异常类。标准库异常类定义在四个头文件中:
(1)exception 头文件定义了最常见的异常类,它的类名是 exception。这个类只通知异常的产生,但不会提供更多的信息。
(2)stdexcept 头文件定义了几种常见的异常类,这些类型在表中列出。
exception |
The most general kind of problem. 最常见的问题。 |
runtime_error |
Problem that can be detected only at run time. 运行时错误:仅在运行时才能检测到问题 |
range_error |
Run-time error: result generated outside the range of values that are meaningful. 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error |
Run-time error: computation that overflowed. 运行时错误:计算上溢 |
underflow_error |
Run-time error: computation that underflowed. 运行时错误:计算下溢 |
logic_error |
Problem that could be detected before run time. 逻辑错误:可在运行前检测到问题 |
domain_error |
Logic error: argument for which no result exists. 逻辑错误:参数的结果值不存在 |
invalid_argument |
Logic error: inappropriate argument. 逻辑错误:不合适的参数 |
length_error |
Logic error: attempt to create an object larger than the maximum size for that type. 逻辑错误:试图生成一个超出该类型最大长度的对象 |
out_of_range |
Logic error: used a value outside the valid range. 逻辑错误:使用一个超出有效范围的值 |
(3)new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new抛出的异常。
(4)type_info 头文件定义了 bad_cast 异常类型。
异常类型只定义了一个名为 what 的操作。这个函数不需要任何参数,并且返回 const char* 类型值。它返回的指针指向一个 C 风格字符串
9.使用预处理器进行调试:
可使用 NDEBUG 预处理变量实现有条件的调试代码:
int main()
{
#ifndef NDEBUG
cerr << "starting main" << endl;
#endif
// ...
预处理器还定义了其余四种在调试时非常有用的常量:
__FILE__ 文件名
__LINE__ 当前行号
__TIME__ 文件被编译的时间
__DATE__ 文件被编译的日期
可使用这些常量在错误消息中提供更多的信息:
if (word.size() < threshold)
cerr << "Error: " << _ _FILE_ _
<< " : line " << _ _LINE_ _ << endl
<< " Compiled on " << _ _DATE_ _
<< " at " << _ _TIME_ _ << endl
<< " Word read was " << word
<< ": Length too short" << endl;
另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。
预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:
assert(expr)
只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为 false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。
continue 语句只能出现在 for、while 或者 do while 循环中,包括嵌套在这些循环内部的块语句中。
与 while 语句不同。do-while 语句总是以分号结束。