C++学习笔记五-语句
一、复合语句(块):复合语句,通常被称为块,是用一对花括号括起来的语句序列(也可能是空的)。块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。通常,一个名字只从其定义处到该块的结尾这段范围内可见。复合语句用在语法规则要求使用单个语句但程序逻辑却需要不止一个语句的地方。例如,while 或 for 语句的循环体
二、语句作用域:在语句的控制结构中定义的变量,仅在定义它们的块语句结束前有效。这种变量的作用域限制在语句体内。
三、条件表达式:一个类类型能否用在条件表达式中取决于类本身。迄今为止,在所有用过的类类型中,IO 类型可以用作条件,但 vector 类型和 string 类型一般不可用作条件。
四、switch 语句:
1.关键字 case 和它所关联的值称为 case 标号。每个 case 标号必须是整型常量表达式。
例如,下面的标号将导致编译时的错误:
// illegal case label values case 3.14: // noninteger case ival: // nonconstant
如果两个 case 标号具有相同的值,同样也会导致编译时的错误。
2.如果表达式与其中一个 case 标号的值匹配,则程序将从该标号后面的第一个语句开始依次执行各个语句,直到 switch 结束或遇到 break 语句为止。如果没有发现匹配的 case 标号(并且也没有 default 标号),则程序从 switch 语句后面的第一条继续执行。
3.如果需要为某个特殊的 case 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。
case true: { // ok: declaration statement within a statement block string file_name = get_file_name(); // ... } break; case false: // ...
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) // ...
七、do while循环:在循环条件中使用的变量,不能在do内定义。如果可以在循环条件中定义变量的话,则对变量的任何使用都将发生在变量定义之前!
八、break 语句:用于结束最近的 while、do while、for 或 switch 语句,并将程序的执行权传递给紧接在被终止语句之后的语句。当 break 出现在嵌套的 switch 或者循环语句中时,将会终止里层的 switch 或循环语句,而外层的 switch 或者循环不受影响
九、continue 语句:导致最近的循环语句的当次迭代提前结束。对于 while 和 do while 语句,继续求解循环条件。而对于 for 循环,程序流程接着求解 for 语句头中的 expression 表达式。
十、try 块和异常处理:
1.异常就是运行时出现的不正常,例如运行时耗尽了内存或遇到意外的非法输入。异常存在于程序的正常功能之外,并要求程序立即处理。在设计良好的系统中,异常是程序错误处理的一部分。当程序代码检查到无法处理的问题时,异常处理就特别有用。在这些情况下,检测出问题的那部分程序需要一种方法把控制权转到可以处理这个问题的那部分程序。错误检测程序还必须指出具体出现了什么问题,并且可能需要提供一些附加信息。
2.try 块的通用语法形式是:
try { program-statements } catch (exception-specifier) { handler-statements } catch (exception-specifier) { handler-statements } //...
try 块以关键字 try 开始,后面是用花括号起来的语句序列块。try 块后面是一个或多个 catch 子句。每个 catch 子句包括三部分:关键字 catch,圆括号内单个类型或者单个对象的声明——称为异常说明符,以及通常用花括号括起来的语句块。如果选择了一个 catch 子句来处理异常,则执行相关的块语句。一旦 catch 子句执行结束,程序流程立即继续执行紧随着最后一个 catch 子句的语句。
3.在复杂的系统中,程序的执行路径也许在遇到抛出异常的代码之前,就已经经过了多个 try 块。例如,一个 try 块可能调用了包含另一 try 块的函数,它的 try 块又调用了含有 try 块的另一个函数,如此类推。对于这种情况:
寻找处理代码的过程与函数调用链刚好相反。抛出一个异常时,首先要搜索的是抛出异常的函数。如果没有找到匹配的 catch,则终止这个函数的执行,并在调用这个函数的函数中寻找相配的 catch。如果仍然找到相应的处理代码,该函数同样要终止,搜索调用它的函数。如此类推,继续按执行路径回退,直到找到适当类型的 catch 为止。
如果不存在处理该异常的 catch 子句,程序的运行就要跳转到名为 terminate 的标准库函数,该函数在 exception 头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。
4.标准异常类中定义在 <stdexcept> 头文件,标准库异常类只提供很少的操作,包括创建、复制异常类型对象以及异常类型对象的赋值。 exception、bad_alloc 以及 bad_cast 类型只定义了默认构造函数,无法在创建这些类型的对象时为它们提供初值。其他的异常类型则只定义了一个使用 string 初始化式的构造函数。当需要定义这些异常类型的对象时,必须提供一想 string 参数。string 初始化式用于为所发生的错误提供更多的信息。异常类型只定义了一个名为 what 的操作。这个函数不需要任何参数,并且返回 const char* 类型值。
5.使用预处理器进行调试: 用 NDEBUG 预处理变量实现有条件的调试代码:
int main() { #ifndef NDEBUG cerr << "starting main" << endl; #endif // ...
如果 NDEBUG 未定义,那么程序就会将信息写到 cerr 中。如果 NDEBUG 已经定义了,那么程序执行时将会跳过 #ifndef 和 #endif 之间的代码。
6.另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件.预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:
assert(expr)
只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为 false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。
assert用法总结与注意事项:
1)在函数开始处检验传入参数的合法性
如:int resetBufferSize(int nNewSize) { //功能:改变缓冲区大小, //参数:nNewSize 缓冲区新长度 //返回值:缓冲区当前长度 //说明:保持原信息内容不变 nNewSize<=0表示清除缓冲区 assert(nNewSize >= 0); assert(nNewSize <= MAX_BUFFER_SIZE); ... }2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
不好: assert(nOffset>=0 && nOffset+nSize<=m_nInfomationSize);
好: assert(nOffset >= 0);
assert(nOffset+nSize <= m_nInfomationSize);
3)不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题
错误: assert(i++ < 100)
这是因为如果出错,比如在执行之前i=100,那么这条语句就不会执行,那么i++这条命令就没有执行。
正确: assert(i < 100)
i++;
4)assert和后面的语句应空一行,以形成逻辑和视觉上的一致感
5)有的地方,assert不能代替条件过滤