第二章 语句

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_allocbad_cast
  • 对于除exception、bad_allocbad_cast的异常类,我们应该用string对象或者C风格字符串初始化这些类型的对象,不允许使用默认初始化的方式
  • 异常类只定义了一个名为what的成员函数,返回一个提供错误信息的C风格字符串
  • 如果异常类型有一个字符串初始值,那么what方法返回该字符串;对于其他无初始值的异常类型来说,what返回的内容由编译器决定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值