本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版 | |||
cs:programming:cpp:cpp_primer:5_statements [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:5_statements [2024/01/14 13:46] (当前版本) – ↷ 页面programming:cpp:cpp_primer:5_statements被移动至cs:programming:cpp:cpp_primer:5_statements codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======语句====== | ||
+ | C++ Primer 笔记 第五章\\ | ||
+ | |||
+ | ---- | ||
+ | |||
+ | |||
+ | ====简单语句==== | ||
+ | C++ 中以 '';'' | ||
+ | <code cpp> | ||
+ | ival + 5; //useless since the result is discard | ||
+ | </ | ||
+ | 由于单纯表达式语句会直接丢掉其运算结果,因此往往这样的语句都会附带其他的运算,比如赋值或者打印。 | ||
+ | ===空语句=== | ||
+ | 空语句(// | ||
+ | <code cpp> | ||
+ | while (cin >> s && s != sought) | ||
+ | ; //null statement | ||
+ | </ | ||
+ | 上面的语句在条件中就制定好了结束的情形:只要 '' | ||
+ | <WRAP center round tip 100%> | ||
+ | 为空语句添加注释,这样可以使阅读者注意到该语句是有意忽略的。 | ||
+ | </ | ||
+ | ==漏写、多写分号== | ||
+ | 漏写分号会导致语法上的问题。而因为空语句的性质,非法的 '';'' | ||
+ | <code cpp> | ||
+ | while (iter != svec.end()) ; | ||
+ | ++iter; // increment is not part of the loop. | ||
+ | </ | ||
+ | 上例中处于条件语句末尾的空语句顶替下方的迭代器自加成为了 while 的循环体。 | ||
+ | ===复合语句 / 块=== | ||
+ | 复合语句 (//Compound statement// | ||
+ | Block 也可以为空: | ||
+ | <code cpp> | ||
+ | while (condtions) | ||
+ | { } //empty block, semicolon is not needed. | ||
+ | </ | ||
+ | |||
+ | ====条件语句==== | ||
+ | |||
+ | 主要的条件语句有if-else / switch-case。具体应用哪种语句根据条件的数量来看。总的来说,条件越多,用switch-case的效率越高。 | ||
+ | |||
+ | ===If-else=== | ||
+ | * 使用 curly brace 确定正确的 scope。 | ||
+ | * C++ 中,if-else 如果有嵌套,需要注意 if-else 的配对。C++ 中 if 总是找寻最近的 else 配对,因此有可能出现本来要与外层 if 配对的 else 被内层 If 抢了先(这个问题被称为// | ||
+ | ===switch-case=== | ||
+ | Switch 语句应用与具有大量条件,但条件都是固定的情况。一个典型的例子是对一段话中的元音 a/e/i/o/u 出现的次数进行计数: | ||
+ | <code cpp> | ||
+ | while (cin >> ch) { | ||
+ | switch (ch) { | ||
+ | case ' | ||
+ | ++aCnt; | ||
+ | break; | ||
+ | case ' | ||
+ | ++eCnt; | ||
+ | break; | ||
+ | case ' | ||
+ | ++iCnt; | ||
+ | break; | ||
+ | case ' | ||
+ | ++oCnt; | ||
+ | break; | ||
+ | case ' | ||
+ | ++uCnt; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | '' | ||
+ | 如果希望找到匹配条件后直接进行新一轮的匹配,使用 '' | ||
+ | 需要注意的是: | ||
+ | * 条件表达式(上例中的 '' | ||
+ | * case label 必须是**常量表达式** | ||
+ | * 同一个 switch 语句中不能存在两个值相同的 case。 | ||
+ | |||
+ | ==Control flow within a switch== | ||
+ | 默认情况下,当某一个 case 匹配以后,switch 的控制流会接着执行之后的 case 匹配。在匹配的 case 之后使用 '' | ||
+ | <code cpp> | ||
+ | unsigned vowelCnt = 0; | ||
+ | while (cin >> ch) { | ||
+ | switch (ch) { | ||
+ | case ' | ||
+ | ++vowelCnt; | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <WRAP center round info 100%> | ||
+ | switch-case 语句很少不用到 break。如果不用,最好注释说明为什么不用。同时为了安全期间,推荐在 switch 语句的末尾加一个 break。这样一来,新添 case 就无须再担心跳出的问题了。 | ||
+ | </ | ||
+ | |||
+ | ==The default Label== | ||
+ | '' | ||
+ | <WRAP center round tip 100%> | ||
+ | 设置 default case(即便是空的),是向之后的阅读者表明我们考虑到了默认的情况。 | ||
+ | </ | ||
+ | ==Switch 中变量的定义== | ||
+ | 考虑以下代码: | ||
+ | <code cpp> | ||
+ | case true: | ||
+ | string file_name; | ||
+ | int ival = 0; | ||
+ | int jval; | ||
+ | break; | ||
+ | case false: | ||
+ | jval = next_num(); | ||
+ | if(file_name.empty()) {/* ... */} | ||
+ | </ | ||
+ | 当 case 是 false 的时候会发生什么情况? // // | ||
+ | 首先下结论,这种写法在 C++ 中是被禁止的。case 的跳转会直接 Bypass 掉变量的初始化,导致接下来的被使用的变量都是未初始化的。书上对此下了定义: | ||
+ | >//It is illegal to jump from a place where a variable with an initializer is out of scope to a place where that variable is in scope.// | ||
+ | 但实际的学习过程中,有一些问题需要解答:\\ \\ | ||
+ | 第一个疑问: switch 语句中的 scope 到底是怎么界定的?\\ \\ | ||
+ | 答:以 curly brace 来决定。一般来说,switch 后面跟了一对 curly brace 代表了只存在一个 scope。任何 case 都属于这个 scope,都没有自己独立的 scope。\\ \\ | ||
+ | 第二个疑问:既然 case 没有独立的 scope,那么 switch 语句是怎么让某一个 case 单独执行的?\\ \\ | ||
+ | 答:与 '' | ||
+ | 第三个疑问:书上的解答不是说从 scope 外的初始化点往 scope 里的 variable 处跳转是违法的吗?为什么你上面说只有一个 scope呢?\\ \\ | ||
+ | 答:这里的 scope,并不是指代的 switch 的 scope,而是代表了变量的生存周期的 scope。从变量声明的概念可以得知,变量的生存周期是从变量声明的那一刻开始,到变量所在 scope 结束为止。因此,**非法的跳转实际上意味着从变量的生存周期外部,跳转到了变量的生存周期内部**。\\ \\ | ||
+ | 按照书上的例子,如果执行了 false,那么跳转点就直接进入了变量 '' | ||
+ | >//If transfer of control enters the scope of any automatic variables// (e.g. by jumping forward over a declaration statement), //the program is ill-formed// | ||
+ | < | ||
+ | 那什么是 automatic variables? 总的来说,在函数中声明的变量,其生存周期并不都是从程序开始到程序结束的。C++ 通过**存储期**(// | ||
+ | 第四个疑问:为什么 '' | ||
+ | 答:上面的 C++ 标准之后还有一些例外: | ||
+ | >// | ||
+ | >// | ||
+ | >// | ||
+ | >// | ||
+ | '' | ||
+ | Refs: | ||
+ | * [[https:// | ||
+ | * [[https:// | ||
+ | 解决上述问题的方式是显式的指定 case 自己的 scope: | ||
+ | <code cpp> | ||
+ | case true: | ||
+ | { | ||
+ | // ok: declaration statement within a statement block | ||
+ | string file_name = get_file_name(); | ||
+ | // ... | ||
+ | } | ||
+ | break; | ||
+ | case false: | ||
+ | if (file_name.empty()) // error: file_name is not in scope | ||
+ | </ | ||
+ | |||
+ | ====迭代语句==== | ||
+ | |||
+ | ===while=== | ||
+ | while 循环的条件可以由表达式或者已经初始化的变量声明来充当,比如: | ||
+ | <code cpp> | ||
+ | int = 1; | ||
+ | if (i) {//using a declared variable as the condition。 | ||
+ | statment; | ||
+ | } | ||
+ | </ | ||
+ | <WRAP center round info 100%> | ||
+ | while 循环的条件中可以定义变量。该变量会在**每次迭代开始的时候创建,结束的时候销毁**。 | ||
+ | </ | ||
+ | ==while 的常见用法== | ||
+ | 第一种常见用法是循环读取数据: | ||
+ | <code cpp> | ||
+ | while (cin >> var) { | ||
+ | //do sth | ||
+ | } | ||
+ | </ | ||
+ | 第二种用法是在 loop 结束之后读取 loop 中的循环控制变量的值: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | int i; | ||
+ | while(cin >> i) | ||
+ | v.push_back(i); | ||
+ | //find the first neative element | ||
+ | auto beg = v.begin(); | ||
+ | while (beg != v. end() && *beg >= 0) | ||
+ | ++beg; | ||
+ | if (beg = v.end()) //all element in v >= 0 | ||
+ | </ | ||
+ | 从 '' | ||
+ | |||
+ | |||
+ | ===For 语句=== | ||
+ | |||
+ | 对于不确定循环的情况来说,用while是很好的。但如果我们希望指定循环多少次,那么我们可以用for。\\ | ||
+ | 传统的for由三部分组成,中间用分号分开: | ||
+ | <code cpp linenums: | ||
+ | for (init-statement ; condition ; expression) | ||
+ | statment; | ||
+ | </ | ||
+ | init-statement 可以是声明语句,可以是表达式语句,可以是空语句。 | ||
+ | ==For 的执行流程== | ||
+ | - 初始化 init statement | ||
+ | - 测试条件,如果测试不通过,**直接结束循环** | ||
+ | - 测试通过,执行,循环到条件不成立是结束。 | ||
+ | ==在 for 头部添加多个定义== | ||
+ | For 的初始语句可以定义多个变量。但因为语句只有一句,因此要保证所有**被定义的变量类型相同**。 | ||
+ | ==For 头部中可以省略的部分== | ||
+ | For 头部中,无论是初始化语句、条件语句,还是表达式语句,都是可以**省略**的: | ||
+ | * 不需要初始化的时候可以省略初始化语句,比如在循环外部已经完成了循环的初始化; | ||
+ | * 省略条件语句意味着条件永远为真;正常情况下需要在循环体内添加退出代码。 | ||
+ | * 省略表达式意味着需要在条件语句中推动循环。比如 '' | ||
+ | ===Range for=== | ||
+ | 在C++11中,我们有了 for 的另外一种表达形式: | ||
+ | |||
+ | <code cpp> | ||
+ | for (declaration : expression) | ||
+ | | ||
+ | </ | ||
+ | 这里的expression必须是一个**序列**(List初始化的序列 or 数组 or string or vector)。declaration 定义一个变量来表示序列里每一个元素。为了保证这个变量和元素的类型一致,我们可以用auto来定义变量。如果我们想对这些元素进行**写操作**,那么变量类型必须是对应的**引用**类型。\\ \\ | ||
+ | 实际上 range for 等同于: | ||
+ | <code cpp> | ||
+ | for (auto beg = v.begin(); beg != v.end(); ++beg) {} | ||
+ | </ | ||
+ | 因此 range for 是**不能**用于为 vector (或者其他容器)**添加** 元素的,因为元素的个数从一开始就被迭代器定下来了。 | ||
+ | ===Do while=== | ||
+ | Do / while 会**先运行一次循环体,再进行条件的测试**: | ||
+ | <code cpp> | ||
+ | do | ||
+ | statement | ||
+ | while (condtion); | ||
+ | </ | ||
+ | <WRAP center round info 100%> | ||
+ | do / while 后面需要跟 semicolon。 | ||
+ | </ | ||
+ | * do while 要求条件**不能为空**。 | ||
+ | * 条件使用的变量**必须在外部定义**。 | ||
+ | ====跳转语句==== | ||
+ | |||
+ | ===break / continue=== | ||
+ | break 会直接跳出**最近**的循环,而 continue 只会跳出最近循环的当次迭代。对于不同类型的循环,continue 有不同的执行方式: | ||
+ | * switch:当 switch 内嵌于某个循环体的时候,才可以使用 contintue. | ||
+ | * while / do while:continue 会先验证条件,再进行下一次循环。 | ||
+ | * tranditional for:continue 从 **expression statement** 开始下一轮循环。 | ||
+ | * range for:contintue 会在下一轮循环开始之前初始化循环变量。 | ||
+ | ===goto=== | ||
+ | goto 提供了一种执行的跳转。这种跳转是无条件的,其跳转的起始点和终点在**同一个函数**内。 | ||
+ | goto 使用 label 进行跳转: | ||
+ | <code cpp> | ||
+ | goto label; | ||
+ | </ | ||
+ | label 指代 label statement,由 identifier + colon 组成: | ||
+ | <code cpp> | ||
+ | identifier: statement; | ||
+ | </ | ||
+ | label statement 中的 indentifier 是独立的,因此可以与其他标识符公用一个名字;与其他重名的实体也互不影响。\\ \\ | ||
+ | goto 语句的合法使用遵循与 switch 语句相同的准则,即**跳转点不能进入变量的 scope**。 | ||
+ | ====Try Blocks 和异常处理==== | ||
+ | 异常处理分为两个部分: | ||
+ | * 检测部分,只能检测并报警,在处理模块作用后停止 | ||
+ | * 处理部分,负责处理问题 | ||
+ | |||
+ | C++中的异常处理由3部分组成: | ||
+ | - throw 表达式,检测异常,并提供信号(抛出异常)。 | ||
+ | - try / catch: 处理异常。try block 负责执行异常代码,并在 catch 中寻找处理方案。 | ||
+ | - exception class. 用于抛出异常 (throw) 和 处理异常 (try-catch) 之间传递异常的具体信息。\\ | ||
+ | |||
+ | ===A throw Expression=== | ||
+ | throw 的书写方式: | ||
+ | <code cpp> | ||
+ | throw expression; | ||
+ | </ | ||
+ | 该表达式的内容决定 trhrow 会抛出什么样的异常。比如: | ||
+ | <code cpp> | ||
+ | throw runtime_error(" | ||
+ | </ | ||
+ | '' | ||
+ | ===The Try Block=== | ||
+ | try block 的写法由一个 '' | ||
+ | <code cpp> | ||
+ | try { | ||
+ | program.. | ||
+ | } | ||
+ | catch (exception-declaration) { | ||
+ | handler.. | ||
+ | } | ||
+ | catch (exception-declaration) { | ||
+ | handler.. | ||
+ | } //..... | ||
+ | </ | ||
+ | try 会根据异常类型选择 catch。 catch 对异常进行定义,然后运行对应 block 里的处理内容。try / catch 互为独立的 scope,在其内部声明的变量对外不可见。 | ||
+ | |||
+ | ===异常处理的顺序=== | ||
+ | |||
+ | 我们在复杂程序里经常可以遇到一种情况:程序在抛出异常之前,已经执行了很多个try,比如嵌套的try-catch语句。那么检测到异常后,应该如何处理? | ||
+ | 异常处理的顺序刚好和整个过程相反,整个过程是一个**从内到外**的过程: | ||
+ | - 异常被抛出的时候,首先搜索**抛出异常**的函数。 | ||
+ | - 如果没有找到该函数相对应的 catch 语句,则终止该函数,并在**调用该函数的函数**中寻找。 | ||
+ | - 如果还是没有找到,终止这个函数,接着搜索调用他的函数。 | ||
+ | - 以此类推,按程序执行的顺序逐层回退,直到找到相应的 catch。 | ||
+ | - 如果最终没有找到对应的 catch,那么会执行 STL 函数 '' | ||
+ | 总结一下就是: | ||
+ | * 解决方案总没有问题多 | ||
+ | * 自己处理不了的事交给上级处理 | ||
+ | * 都处理不了就关门 ('' | ||
+ | ==关于 Exception Safety== | ||
+ | 什么是 exception satety? | ||
+ | 在程序抛出异常的时候,程序多半是处于进行中的状态,参与运算的某些资源可能只处理了一部分。Exception safety 指在这种情况下如何去清理、保护或者恢复这些资源。 | ||
+ | |||
+ | |||
+ | |||
+ | ===STL 异常类=== | ||
+ | C++ 中定义了一组标准类来传递异常的信息。这些异常类被分别定义在 4 个**头文件**中: | ||
+ | \\ \\ < | ||
+ | 而 '' | ||
+ | \\ \\ < | ||
+ | ==STL 异常类支持的操作== | ||
+ | * 支持创建、拷贝以及**互相的**赋值。 | ||
+ | * 只允许**默认初始化**的类对象:'' | ||
+ | * 使用 string / c-string 初始化的类对象:其余的异常类,初始化值作为额外的信息使用。 | ||
+ | * 操作:'' | ||
+ | <code cpp> | ||
+ | try{ | ||
+ | if(condition){ | ||
+ | throw exception(" | ||
+ | } | ||
+ | } | ||
+ | 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. | ||
+ | } | ||
+ | </ | ||