简单语句
C++ 中,大多数语句以分号结束。
程序语句最简单的形式是空语句(只有一个单独的分号)
如果在程序的某个地方,语法上需要一个语句,但逻辑上并不需要,此时应该使用空语句。
使用空语句时应该加上注释,以便任何读这段代码的人都知道该语句是有意省略的。
无关的空语句并非总是无害的。
在 C++ 中,对象或类的定义或声明也是语句。
复合语句,通常被称为块,是用一对花括号括起来的语句序列(也可能是空的)。块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。通常,一个名字只从其定义处到该块的结尾这段范围内可见。
与其他大多数语句不同,块并不是以分号结束的。
语句作用域
有些语句允许在它们的控制结构中定义变量,如for,while
在条件表达式中定义的变量必须初始化,该条件检验的就是初始化对象的值。
在语句的控制结构中定义的变量,仅在定义它们的块语句结束前有效。这种变量的作用域限制在语句体内。
如果程序需要访问某个控制结构中的变量,那么这个变量必须在控制语句外部定义。
if 语句根据特定表达式是否为真来有条件地执行另一个语句
最简单的if语句语法为:
if (condition)
statement
其中的 condition 部分必须用圆括号括起来。它可以是一个表达式,或者一个初始化声明。
通常,statement 部分可以是复合语句,即用花括号括起来的块语句。
if else 语句的语法形式为:
if (condition)
statement1
else
statement2
statement2 既可以是任意语句,也可以是用花括号起来的块语句
悬垂 else
C++ 中悬垂 else 问题带来的二义性,通过将 else 匹配给最后出现的尚未匹配的 if 子句来解决
建议总是在 if 后面使用花括号。这样做可以避免日后修改代码时产生混乱和错误。至少,无论 if(或者 while)后面是简单语句,例如赋值和输出语句,还是其他任意语句,使用花括号都是一个比较好的做法。
关键字 case 和它所关联的值称为 case 标号。每个 case 标号的值都必须是一个常量表达式
小心case穿透!
存在一个普遍的误解:以为程序只会执行匹配的 case 标号相关联的语句。实际上,程序从该点开始执行,并跨越 case 边界继续执行其他语句,直到 switch 结束或遇到 break 语句为止。
对于 switch 结构,漏写 break 语句是常见的程序错误。
尽管没有严格要求在 switch 结构的最后一个标号之后指定 break 语句,但是,为了安全起见,最好在每个标号后面提供一个 break 语句,即使是最后一个标号也一样。如果以后在 switch 结构的末尾又需要添加一个新的 case 标号,则不用再在前面加 break 语句了。
慎用 break 语句,它并不总是恰当的
default 标号提供了相当于 else 子句的功能
哪怕没有语句要在 default 标号下执行,定义 default 标号仍然是有用的。
一个标号不能独立存在,它必须位于语句之前。如果 switch 结构以 default 标号结束,而且 default 分支不需要完成任何任务,那么该标号后面必须有一个空语句。
case 标号必须是整型常量表达式
如果两个 case 标号具有相同的值,同样也会导致编译时的错误。
对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量,制定这个规则是为避免出现代码跳过变量的定义和初始化的情况。
如果需要为某个特殊的 case 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。
当条件为真时,while 语句反复执行目标语句
while (condition)
statement
循环条件 condition 可以是一个表达式,或者是提供初始化的变量定义。
在循环条件中定义的变量在每次循环里都要经历创建和撤销的过程。
for 语句的语法形式是:
for (initializer; condition; expression)
statement
initializer必须是声明语句、表达式语句或空语句。
for 语句头中,可以省略 init-statement、condition 或者 expression(表达式)中的任何一个(或全部)
可以在 for 语句的 init-statement 中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型
do
statement
while (condition);
与 while 语句不同。do-while 语句总是以分号结束。
它保证循环体至少执行一次
break 语句用于结束最近的 while、do while、for 或 switch 语句,并将程序的执行权传递给紧接在被终止语句之后的语句。
break 只能出现在循环或 switch 结构中,或者出现在嵌套于循环或 switch 结构中的语句里。
break 出现在循环外或者 switch 外将会导致编译时错误。
continue 语句导致最近的循环语句的当次迭代提前结束。对于 while 和 do while 语句,继续求解循环条件。而对于 for 循环,程序流程接着求解 for 语句头中的 expression 表达式。
continue 语句只能出现在 for、while 或者 do while 循环中,包括嵌套在这些循环内部的块语句中。
goto 语句提供了函数内部的无条件跳转,实现从 goto 语句跳转到同一函数内某个带标号的语句。
(从上世纪 60 年代后期开始,不主张使用 goto 语句 -_- !!!)
用法:
goto label;
goto 语句不能跨越变量的定义语句向前跳转
如果确实需要在 goto 和其跳转对应的标号之间定义变量,则定义必须放在一个块语句中
try 块和异常处理
C++ 的异常处理中包括:
1. throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。
2. try 块,错误处理部分使用它来处理异常。try 语句块以 try 关键字开始,并以一个或多个 catch 子句结束
3. 由标准库定义的一组异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。
如果不存在处理该异常的 catch 子句,程序的运行就要跳转到名为 terminate 的标准库函数,该函数在 exception 头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。
标准异常
1. exception 头文件定义了最常见的异常类,它的类名是 exception。这个类只通知异常的产生,但不会提供更多的信息。
2. stdexcept 头文件定义了几种常见的异常类
3. new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new抛出的异常
4. type_info 头文件定义了 bad_cast 异常类型
使用预处理器进行调试
可使用 NDEBUG 预处理变量实现有条件的调试代码
如果 NDEBUG 未定义,那么程序就会将信息写到 cerr 中。如果 NDEBUG 已经定义了,那么程序执行时将会跳过 #ifndef 和 #endif 之间的代码。
默认情况下,NDEBUG 未定义,这也就意味着必须执行 #ifndef 和 #endif 之间的代码。在开发程序的过程中,只要保持 NDEBUG 未定义就会执行其中的调试语句。开发完成后,要将程序交付给客户时,可通过定义 NDEBUG 预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义 NDEBUG 命令行选项。
例:#g++ -DNDEBUG -c main.cc
预处理器还定义了其余四种在调试时非常有用的常量(以下为两个连续的下划线):
__FILE__ 文件名
__LINE__ 当前行号
__TIME__ 文件被编译的时间
__DATE__ 文件被编译的日期
另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。
assert(expr)
只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为 false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。
与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用 assert 来测试“不可能发生”的条件
#include <iostream> #include <string> #include <cassert> using namespace std; int main() { ; // null statement cout << "type exit to end the loop >" << endl; string s; // read until we hit end-of-file or find an input equal to sought while (cin >> s && s != "exit") ; // null statement cout << "type exit to end the loop >" << endl; while (cin >> s && s != "exit") { } // empty block cout << "input any char you want , press CTRL+Z to end >" << endl; char ch; // initialize counters for each vowel int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; while (cin >> ch) { // if ch is a vowel, increment the appropriate counter switch (ch) { case 'a': // int first = 1; error ! crosses initialization of `int first' ++aCnt; { int first = 1; // ok in the block } break; case 'e': ++eCnt; break; case 'i': ++iCnt; break; case 'o': ++oCnt; break; case 'u': int last = 1; //ok the last case label ++uCnt; break; } } // print results 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; int ival; const double dval = 3.14; switch(ival) { case 1 : ; break; /* case 1 : error: duplicate case value * ; * break; * case 3.14: error:case label does not reduce to an integer constant * ; * break; * case dval: error:`dval' cannot appear in a constant-expression * ; * break; */ default : break; } // for(int i,int j; ;) //error expected `,' or `;' before "int" ; for(int i,j,k; ;) //ok i j k has the same type break; goto label1; cout << "I will not be printed..." << endl; label1: cout << "here is label1 " << endl; #ifndef NDEBUG cerr << "ending main..." << endl; #endif //assert(1>2); cerr << "An Error Info like this: " << __FILE__ << " : line " << __LINE__ << endl << " Compiled on " << __DATE__ << " at " << __TIME__ << endl; return 0; }