2.1 if语句
简介
C++语言提供了两种按照条件执行的语句:
- if语句:根据条件决定控制流
- switch语句:计算整型表达式的值,并根据这个值从几条执行路径中选择一条
if语句
和golang不同的是,C++语言中条件语句的condition必须用括号括起来。
// 普通的if语句
if (condition)
statement
// if-else语句
if (condition)
statement
else
statement2
2.2 for语句
传统的for语句
1. 语法
for语句的语法形式是:
for (initializer; condition; expression)
statement
- initializer:初始化值
- condition:循环控制条件,为真就会执行一次statement
- expression:修改initializer初始化的变量
2. for语句头中的多重定义
initializer中可以定义多个对象,但是只能有一条声明语句,因此所有变量的基础类型必须相同:
// 通过for循环把vector的元素拷贝一份添加到原来的元素后面
vector<int> v;
for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i) {
v.push_back(v[i]);
}
范围for语句
1. 语法
C++11新标准引入了范围for语句:
for (declaration : expression)
statement
其中expression表示的是一个序列,比如用花括号括起来的初始值列表、数组或者vector等类型的对象,这些类型的特点是拥有能返回迭代器begin和end成员。declaration定义一个变量,序列中每个元素都能转换成该变量的类型。确保类型相容最简单的办法是使用auto类型说明符。
2. 注意事项
在范围for循环中预存了end()
的值,一旦在序列中添加(删除)元素,end函数的值就可能变得无效。
2.3 break语句
简介
break语句负责终止离他最近的while、do while、for或者switch语句,并从这些语句之后的第一条语句继续执行。
注意事项
Tips:break语句的作用范围仅限于最近的循环或者switch。
std::string buf;
while (cin << buf && !buf.empty()) {
switch(buf[0]) {
case '-':
for (auto it = buf.begin()+1; it != buf.end(); ++it) {
if (*it == ' ') {
break; // 第一个break: 离开for循环
}
}
break; // 第二个break: 离开switch
case '+':
// do something...
}
}
2.4 continue语句
简介
Tips:和break语句不同的是,只有当switch语句嵌套在迭代语句内部时,才能在switch里使用continue,用于结束外层循环中的当前迭代。
continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。只能出现在for、while、do while循环的内部,或者嵌套在此类循环里的语句或块的内部。
2.5 while语句
while语句
只要条件为真,while语句就会重复地执行循环体,语法是:
while (condition)
statement
Tips:定义在while条件部分或者while循环体内的变量每次迭代都经历从创建到销毁的过程。
使用while主要出于两种原因:
- 当不确定需要迭代多少次时
- 想要在循环结束后访问循环控制变量
do while
do while语句和while语句非常相似,唯一的区别是它执行循环体再检查条件,因此无论条件值如何都至少会执行一遍循环:
do
statement
while (condition);
2.6 switch语句
简介
C++语言提供了两种按照条件执行的语句:
- if语句:根据条件决定控制流
- switch语句:计算整型表达式的值,并根据这个值从几条执行路径中选择一条
switch语句
1. 例子:统计每个元音字母的数量
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
switch (ch) {
case 'a':
++aCnt;
break;
case 'e':
++eCnt;
break;
case 'i':
++iCnt;
break;
case 'o':
++oCnt;
break;
case 'u':
++uCnt;
break;
}
}
2. case标签
- case标签必须是整型常量表达式
- 任意两个case标签的值不能相同
- 如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,直到遇到break语句
- 可以把多个case标签写在同一行内,强调这些case表示的是某个范围内的值
// 统计元音字母出现次数
unsigned vowelCnt = 0;
char ch;
while (cin >> ch) {
switch (ch) {
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
}
}
3. break
一般不要省略case分支最后的break语句,如果没写break语句,最好加一段注释说明程序的逻辑,否则可能带来非预期的结果。
4. default标签
Tips:标签不应该孤零零存在,它后面必须跟上一条语句或者另外一个case标签。如果switch结构以一个空的default标签作为接受,则该default标签后面必须跟上一条空语句或者一个空块。
如果没有任何一个case标签能匹配上switch表达式的值,那么程序将执行紧跟在default标签后面的语句:
// 统计元音字母和非元音字母出现次数
unsigned vowelCnt = 0;
unsigned otherCnt = 0;
char ch;
while (cin >> ch) {
switch (ch) {
case 'a': case 'e': case 'i': case 'o': case 'u':
++vowelCnt;
break;
default:
++otherCnt;
break;
}
}
4. switch内部的变量定义
switch的执行流程中可能会跨过某些case标签,如果需要为某个case分支定义并初始化一个变量,我们应该把变量定义在块内,从而保证后面所有case标签都在变量的作用域之外:
case true:
{
string file_name = get_file_name();
}
break;
case false:
// 编译报错: file_name不在作用域之内
if (file_name.empty())
2.7 goto语句
简介
Tips:不要在程序中使用goto语句,它会使得程序既难理解又难修改。
goto语句的作用是从当前位置无条件跳转到同一函数内的另一条语句。
goto label;
注意事项
和switch语句类型,goto语句也不能将程序的控制权从变量的作用域之外转移到作用域之内:
// ...
goto end;
int ix = 10;
end:
// 错误: 此处的代码需要使用ix, 但是goto绕过了它的声明
跳回到变量定义之前是合法的,这时候系统将销毁该变量并重新创建它:
begin:
int sz = get_size();
if (sz <= 0) {
goto begin; // goto语句执行后将销毁sz, 然后重新定义并初始化sz
}
2.8 try语句块和异常处理
简介
典型的异常包括失去数据库连接以及遇到意外输入等,异常处理机制为程序中异常检测和异常处理两部分的协作提供支持:
- thorw表达式:throw表达式用于表示它遇到了无法处理的问题,我们说throw引发了异常
- try语句块:异常处理部分使用try语句块处理异常,它以关键字try开始,并以一个或多个catch子句结束
- 异常类:用于在throw表达式和catch子句之间传递异常的具体信息
throw表达式
程序的异常检测部分用throw表达式抛出一个异常:
#include <stdexcept>
throw runtime_error("tomocat");
try语句块
try语句块的语法是:
Tips:try语句块内声明的变量在块外部无法访问,特别是在catch子句内也无法访问。
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
}
例子:
while (cin >> item1 >> item2) {
try {
// 执行item1和item2的操作, 失败了抛出runtime_error异常
} catch (runtime_error err) {
cout << err.what() << endl;
}
}
Tips:如果一个程序没有try语句块且发生了异常,系统会调用terminate函数并终止当前程序的执行。当异常被抛出时,首先搜索该异常的函数,如果没能找到匹配的catch子句,那么终止该函数并在调用该函数的函数中继续寻找。如果还是没找到匹配的catch子句,这个新的函数也被终止,继续搜索调用它的函数。如果最终还是没能找到任何匹配的catch子句,系统会调用terminate函数并终止当前程序的执行。
标准异常
1. C++标准库异常
C++标准库定义了一组类用于报告标准库遇到的问题,它们分别定义在4个头文件中:
- exception头文件中定义了最通用的异常类exception,它只报告异常的发生,不提供任何额外信息
- stdexcept头文件中定义了几种常用的异常类,后续会列举
- new头文件中定义了
bad_alloc
异常类型 type_info
头文件中定义了bad_cast
异常类型
2. stdexcept定义的异常
stdexcept头文件中定义的异常类如下:
异常类 | 含义 |
---|---|
exception | 最常见的问题 |
runtime_error | 只有在运行时才能检测出的问题 |
range_error | 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domian_error | 逻辑错误:参数对应的结果值不存在 |
invalid_argument | 逻辑错误:无效参数 |
length_error | 逻辑错误:试图创建一个超出该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |
3. 注意事项
- 我们只能以默认初始化的方式初始化exception、
bad_alloc
和bad_cast
- 对于除exception、
bad_alloc
和bad_cast
的异常类,我们应该用string对象或者C风格字符串初始化这些类型的对象,不允许使用默认初始化的方式 - 异常类只定义了一个名为what的成员函数,返回一个提供错误信息的C风格字符串
- 如果异常类型有一个字符串初始值,那么what方法返回该字符串;对于其他无初始值的异常类型来说,what返回的内容由编译器决定