What & How & Why

这是本文档旧的修订版!


语句

C++ Primer 笔记 第五章
我的笔记均包含大量个人理解内容,存在一定偏差。如果您发现错误,请留言提出,谢谢!

1.简单语句

1.1.空语句

空语句用在逻辑上不需要,但是语法需要的地方。比如for循环的条件中就可以完成计算,但是仍然需要一个空语句作为结束。我们可以用一个“;”(semicolon)表示空语句。在大多数情况下,空语句是需要注释出来的;我们也需要注意不要多写semicolon,因为每一个分号都代表一个空语句。

1.2.块/语句作用域

如果我们需要在循环里用一大段代码来进行运算,我们就需要用curly braces“{}“把这些代码圈起来表示为一个整体。这些代码组成的整体叫复合语句(compound statements),也叫块(block)。对于块来说,块内定义的变量作用域就在块内。
块内定义的变量必须初始化,因为这些变量会被定义他们的控制结构所用到。

2.条件语句

主要的条件语句有if-else / switch-case。具体应用哪种语句根据条件的数量来看。总的来说,条件越多,用switch-case的效率越高。

2.1.If-else

If-else的主要注意点是:if-else关键字总是寻找最近对应的if/else配对,所以要确保相邻的if/else是不是应该对应起来的的条件判断内容。

2.2.switch-case

switch-case是通过case_label来进行条件判断的,所以在一个语句里,case的值不能有重复的。如果不是希望多组case共同表达一个分支,我们应该在每个case执行完毕后接上一个break,用于跳出语句。
switch-case中,在case作用域中定义并初始化变量是错误的,因为控制流很可能绕过这个定义语句,导致这个变量定义无效。所以如果需要在case作用域里定义变量,我们需要加上”{}“。这样保证了被定义的变量在作用域内,从而之后的case标签都在作用域外。

3.迭代语句

3.1.while / do while

while / do while循环的条件可以由表达式或者已经初始化的变量声明来充当,比如:

int  = 1;
if (i) {//using a declared variable as the condition。
    statment;
}
while语句在条件一开始false的时候是不会执行的,do while 语句则会执行一遍。因此而对于do while来说,会先执行块再判断条件,所以在do while的条件里定义变量是非法的

3.2. for / range for

对于不确定循环的情况来说,用while是很好的。但如果我们希望指定循环多少次,那么我们可以用for。
传统的for由三部分组成,中间用分号分开:

for (init-statement ; condition ; expression)
    statment;
跟while 一样,如果条件起始为false,那么语句就不会执行,而for中定义的初始值只在for循环里有效。对于这个init-statement, 我们是可以同时定义几个对象的。但这个初始化语句也跟一般的多变量初始化一样,我们需要保证定义的变量类型都相同。
在C++11中,我们有了for的另外一种表达形式:

for (declaration : expression)
     statments;
这里的expression必须是一个序列(List初始化的序列/数组/string/vector)。declaration 定义一个变量来表示序列里每一个元素。为了保证这个变量和元素的类型一致,我们可以用auto来定义变量。如果我们想对这些元素进行写操作,那么变量类型必须是对应的引用类型。

4.跳转语句

4.1.break / continue

break 会跳出最近的循环,而continue只会跳出最近循环的当次迭代。

4.2.goto

goto语句的语法是:

goto label;
这个label是由label名和冒号组成。如果goto语句绕过了一个变量的定义,而直接跳转到了直接使用这个变量的语句,那么这个goto是非法的。
goto会显著破外程序的结构性,所以最好不要用。

5.异常处理

C++中的异常处理由3部分组成:

  1. throw表达式,用于抛出异常
  2. try / catch: 我们把代码放到try block中,这样try就可以侦测到抛出的异常,然后把异常的类型与catch中的异常判断做对比。如果有匹配,则通过对应的catch子语句对异常进行处理。
  3. exception class. 用于抛出异常 (throw) 和处理异常(try-catch)之间传递异常的具体信息。

整个异常处理的过程是这样的:
我们用throw抛出异常 → 用try捕获异常 → 用catch处理异常 → catch无法处理则转到Library function terminate。

5.1.异常处理的顺序

我们在复杂程序里经常可以遇到一种情况:程序在抛出异常之前,已经执行了很多个try,比如嵌套的try-catch语句。那么检测到异常后,应该如何处理? 异常处理的顺序刚好和函数调用相反:

  1. 异常被抛出的时候,首先搜索抛出异常的函数。
  2. 如果没有找到相对应的catch语句,则函数终止,并在调用该函数的函数中寻找。
  3. 如果还是没有找到,终止这个函数,接着搜索调用他的函数。
  4. 以此类推,按程序执行的顺序逐层回退,直到找到相应的catch。
  5. 如果最终没有找到,转到terminate function。

5.2.标准异常类

C++中定义了一组标准类来传递异常的信息。这些异常类被分别定义在4个header中:

header 作用
exception 定义了最常见的异常类。它只报告异常的出现。
stdexcept 定义了几种通用类
new 定义了bad_alloc的异常类型
type_info 定义了 bad_cast的异常类型

而stdexcept里定义了一些详细的异常类,大概分为runtime_error和logic_error两部分,详情见下图:

exception / bad_alloc / bad_cast 只能通过默认初始化。而其他异常的类型恰好相反:不允许默认初始化,应该使用string/cstyle string 对其进行初始化。这些异常类型定义了一个what的function,不需要参数,返回一个指向cstring的char类型指针。这个what函数实际上就是用于打印从throw那里获取的错误信息。比如:

try{
    if(condition){
        throw exception("oh its an error!") // the string is what what_function got.
    }
}
catch(exception e){ //define object for exception class.
    cout << e.what() << endl; // using e to access exception class member function what(), print out the error string.
}
~~DUOSHUO~~