C++ Primer 总结索引 | 第五章:语句

1、C++语句 提供一组 控制流语句 以支持更复杂的执行路径

1、简单语句

1、一个表达式 末尾加上分号 就变成了 表达式语句。表达式语句的作用是 执行表达式 并丢掉求值结果

ival + 5; // 没有实际用处的表达式语句
cout << ival; // 有用的表达式语句

表达式语句中的 表达式 在求值时 附带有其他效果,如 给变量赋了新值 或者 输出了结果

2、空语句:单独的分号 ; // 空语句
语法上需要 单逻辑上不需要:如 循环的全部工作 都可以在 条件部分完成:例 读取输入流的内容 直到遇到 一个特定的值为止

// 重复读入数据 直至到达文件末尾 或 某次输入的值等于 sought
while (cin >> s && s != sought)
	; // 空语句

while循环 条件部分首先先从 标准输入读取一个值 并且隐式地检查cin,假设读取成功 检查读进来的值 是否为sought的值。如果发现等于sought,循环终止;否则,从cin中继续 读取另一个值,再一次判断 循环的条件

空语句应该加上注释

3、注意分号:
包含两段语句:表达式语句 和 空语句:ival = v1 + v2;;
if / while的条件后多跟了分号 会造成无休止的循环:

// 额外的分号,循环体 是空语句
while (iter != svec.end()); 
	++iter; // 递增运算 不属于 循环的一部分,虽然形式上有缩进

4、复合语句(也称为 块):指用 花括号括起来的语句 和 声明的序列
一个块 就是一个作用域,在块中引入的名字 只能在块内部 以及 嵌套在块中的子块里访问。名字只在有限的区域可见,该区域从 名字定义处开始,到 名字所在的最内层块的结尾 为止

语法要求一条语句,逻辑上需要多条,需要 多条语句用 花括号括起来,把语句序列 转变成 块

while (val <= 10) {
	sum += val;
	++val;
}

使用 逗号运算符,使 不需要块

while (val <= 10)
        sum += val, ++val;

块不以 分号 作为结束

2、语句作用域

1、可以在 if、switch、while 和 for语句 的控制结构内 定义变量。定义在控制结构中的变量 只在相应语句的内部可见

while (int i = get_num()) // 每次迭代创建并初始化i
	cout << i << endl;
i = 0; // 错误:在循环外部无法访问i

其他代码也需要访问 控制变量,则 变量需要定义在 语句的外部:
错误代码

while (bool status = find(word)) { /* . . . */ }
		if (!status) { /* . . . */ }

改成

bool status;
while (status = find(word)) {/* ... */}
if (!status) {/* ... */}

控制结构定义的对象 的值 马上要由结构本身 使用,所以 这些变量必须初始化
错误代码

while (string::iterator iter != s.end()) { /* . . . */ }

改成

std::string::iterator iter = s.begin();
while (iter != s.end()) { /* . . . */ }

3、条件语句

包含if、switch语句

3.1 if 语句

1、悬垂else:规定else 与 离他最近的尚未匹配的if 匹配,不管 格式缩进

2、使用花括号 控制执行路径

3、条件运算符的优先级太低,括号可以很少

3.2 switch 语句

1、能够在 若干固定选项中 做出选择
如果 输入的字符 与某个元音匹配,并将 该字母的数量+1

// 为 每个元音字母初始化 其计数值
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
	// 如果ch是元音字母,将其对应的计数值+1
	switch (ch) {
		case 'a':
			++aCnt;
			break;
		case 'e':
			++eCnt;
			break;
		case 'i':
			++iCnt;
			break;
		case 'o':
			++oCnt;
			break;
		case 'u':
			++uCnt;
			break;
	}
}

switch 语句 首先对括号里的 表达式求值,该表达式 紧跟在 关键字switch的后面,可以是 一个初始化变量声明;表达式的值 转换为 整数类型,然后与每个case 标签的值 比较

如果 表达式 和 某个case标签的值 匹配成功,程序从 该标签之后的第一条语句 开始执行,直到 到达了switch 的结尾 或者 遇到一条 break 语句为止。简言之,break的作用是 中断当前控制流,将 控制权 转移到 while语句的 右花括号处

如果 switch语句的表达式 和 所有case都没有匹配上,将直接跳转到switch结构之后的 第一条语句。case 关键字 和它对应的值 一起被称为case标签。case标签 必须是 整型 常量表达式

char ch = getVal();
int ival = 42;
switch (ch) {
case 3.14: // 错误:case标签 不是一个整数
case ival: // 错误:case标签 不是一个常量

2、switch 内部的控制流:如果某个case标签匹配成功,将 从该标签开始往后顺序 执行所有case分支,除非 程序显式中断,否则直到 switch结尾处 才停下来。要想 避免执行后续 case分支 的代码,必须 显式地告诉编译器终止执行过程。通常,在下一个case标签 之前应该有一条 break语句

有时,希望 两个或更多个值 共享一组操作,此时 省略break语句,使得 程序能够连续执行 若干个case标签
如:统计 所有元音字母出现的总次数:

unsigned vowelCnt = 0;
switch (ch)
{
	// 出现了a, e, i, o, u 中任意一个 都会将vowelCnt的值+1
	case 'a':
	case 'e':
	case 'i':
	case 'o':
	case 'u':
		++vowelCnt;
		break;
}

case 标签之后 不一定非得换行。把几个case标签 写在一行里,强调这些case 代表的是某个范围内的值

switch (ch)
{
	// 另一种合法的书写方式
	case 'a': case 'e': case 'i': case 'o': case 'u':
		++vowelCnt;
		break;
}

漏写break 容易引发缺陷,一般 不要省略case 分支最后的 break语句。如果没写 break 语句,最好加一段注释说明程序逻辑

尽管 switch语句 不是非得在 最后一个标签后面写上break,但是为了安全,最好这么做。即使以后 再增加新的case分支,也不用在前面 补充break语句

3、default标签:没有任何一个case标签 能配上switch表达式的值,程序 将执行紧跟在default标签 后面的语句
比如 可以增加一个计数值 来统计非元音字母的数量,只要在default分支内 不断递增名为 otherCnt的变量即可:

switch (ch) {
	case 'a': case 'e': case 'i': case 'o': case 'u':
		++vowelCnt;
		break;
	default:
		++otherCnt;
		break;
	}
}

标签 不应该孤零零地出现,后面必须跟上 一条语句 或 另外一个case标签。如果 switch结构 以一个空的default标签作为 结束,则 应该在default标签后面 必须 跟上一条空语句 或者 一个空块
即使不准备在 default标签下 做任何工作,定义一个default标签也是有用的,告诉读者 考虑到了默认情况,只是啥也没做

4、switch内部的变量定义:如果被略过的代码中 含有变量的定义?
如果 在某处一个带有初值的变量 位于作用域之外,在另一处 该变量位于 作用域之内,则 从前一处跳转到后一处的行为 是非法行为
C++规定,不允许跨过变量的初始化语句 直接跳转到 该变量作用域内的 另一个位置

case true:
	// 因为程序的执行流程 可能绕开下面的 初始化逻辑,所以 该switch语句 不合法
	string file_name; // 错误:控制流绕过一个 隐式初始化的变量
	int ival = 0; // 错误:控制流绕过一个 显式初始化的变量
	int jval; // 正确:因为jval没有初始化
	break;
case false:
	// 正确:jval虽然在作用域内,但是它没有初始化
	jval = next_num(); // 正确:给jval赋一个值

对于跳过部分,如果只是声明一个值 没有初始化的话,并不会跳过:

char s;
cin >> s;
switch (s) {
case 't':
	int ival;
	break;
case 'f':
	ival = 5;
	cout << ival << endl;
	break;
}

输入f 时,会正常输出5

5、既统计元音字母的小写形式,也统计元音字母的大写形式,同时 也能统计空格、制表符、和换行符的数量

 unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, tabCnt = 0, newlineCnt = 0;
 char ch;
 while (cin >> std::noskipws >> ch) // while都没加括号,下面一整块 算一条语句
     switch (ch) {
         case 'a':
         case 'A':
             ++aCnt;
             break;
         case 'e':
         case 'E':
             ++eCnt;
             break;
         case 'i':
         case 'I':
             ++iCnt;
             break;
         case 'o':
         case 'O':
             ++oCnt;
             break;
         case 'u':
         case 'U':
             ++uCnt;
             break;
         case '\n':
             ++newlineCnt;
             break;
         case '\t':
         case '\v':
             ++tabCnt;
             break;
     }

cin >> std::noskipws >> ch:不忽略 任意地方的空格

修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量: ff、fl和fi
需要找个值 记录上一轮元素

 unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, tabCnt = 0, newlineCnt = 0, ffCnt = 0, flCnt = 0, fiCnt = 0;
 char ch, prech = '\0';
 while (cin >> std::noskipws >> ch)
 {
     switch (ch) {
         case 'a':
         case 'A':
             ++aCnt;
             break;
         case 'e':
         case 'E':
             ++eCnt;
             break;
         case 'i':
             if(prech == 'f') ++fiCnt;
         case 'I':
             ++iCnt;
             break;
         case 'o':
         case 'O':
             ++oCnt;
             break;
         case 'u':
         case 'U':
             ++uCnt;
             break;
         case '\n':
             ++newlineCnt;
             break;
         case '\t':
         case '\v':
             ++tabCnt;
             break;
         case 'f':
             if(prech == 'f') ++ffCnt;
             break;
         case 'l':
             if(prech == 'f') ++flCnt;
             break;
     }
     prech = ch;
 }

switch case语法不对:

    unsigned evenCnt = 0, oddCnt = 0;
    int digit = get_num() % 10;
    switch (digit) {
        case 1, 3, 5, 7, 9: // 错误
            oddcnt++;
            break;
        case 2: case 4: case 6: case 8: case 0: // 正确
            evencnt++;
            break;
    }

case标签必须是整形常量表达式:

    unsigned ival=512, jval=1024, kval=4096; // 错误
	
	const unsigned ival=512, jval=1024, kval=4096; // 正确
    unsigned bufsize;
    unsigned swt = get_bufCnt();
    switch(swt) {
        case ival:
            bufsize = ival * sizeof(int);
            break;
        case jval:
            bufsize = jval * sizeof(int);
            break;
        case kval:
            bufsize = kval * sizeof(int);
            break;
    }

4、迭代语句

迭代语句 通常称之为 循环,其重复执行直到 满足某个条件才停下来。while 和 for 语句在执行循环体之前 检查条件,do while 语句先执行循环体,然后再 检查条件

4.1 while 语句

1、while 的条件部分 可以是 一个表达式 或 是一个带初始化的 变量声明。应该 由条件本身 或者是 循环体 设法改变表达式的值,否则 循环可能无法终止

定义在while条件部分 或者 while循环体内的变量 每次迭代都经历 从创建到销毁的过程

2、使用 while循环:
1)不确定 到底要迭代多少次,比如 读取输入的内容
2)想在循环结束后 访问循环控制变量
如:寻找 第一个负值元素

auto beg = v.begin();
while (beg != v.end() && *beg >= 0)
	++beg;
if (beg == v.end()) // 此时我们知道 v中的所有元素都大于等于0

4.2 传统的for语句

1、for语句的语法格式

for (init-statement; condition; expression)
	statement;

init-statement 必须是以下三种形式中的一种:声明语句、表达式语句 或者 空语句

一般情况下,init-statement 负责初始化一个值,这个值将随着 循环的进行 而改变。condition 作为循环控制的条件,只要condition为真,就执行一次statement。expression 负责修改 init-statement 初始化的变量,这个变量 正好就是 condition检查的对象,修改发生在 每次循坏迭代之后

2、for 语句头中定义的对象 只能在for循环体内 可见

3、for 语句头中的 多重定义:init-statement 也可以定义多个对象,但是 init-statement 只能有一条声明语句,因此,所有变量 的基础类型相同

vector <int> v;
// 记录下v的大小,当达到 原来的最后一个元素后 结束循环
for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i)
	v.push_back(v[i]);

在 init-statement 里同时定义了 索引i 和 循环控制变量sz

4、省略for语句头的某些部分:for 语句头 能省略掉 init-statement、condition 和 expression 中的任何一个(或者全部)

分号代表 一个空的init-statement。省略condition的效果 等价于 在条件部分写了个true。因为条件的值 永远是true,所以 在循环体内 必须有语句 负责退出循环,否则 循环就会无休止地执行下去

也能省略掉for语句头中的 expression,但是 在这样的循环中 就要求条件部分 或者 循环体 必须改变迭代变量的值

5、while 循环特别适用于 条件保持不变、反复执行操作的情况。for 循环更像是在 按步迭代,它的索引值 在某个范围内 依次变化

4.3 范围for语句

1、语法形式:

for (declaration : expression)
	statement

expression 表示的必须是 一个序列,比如 用花括号括起来的 初始值列表、数组 或者 vector或string等类型的 对象,这些类型的共同特点是 拥有能返回迭代器的 begin和end成员

declration 定义一个变量,序列中每个元素 都能转换成该变量的类型。确保 类型相容 最简单的办法是 使用auto类型说明符

4.4 do while 语句

1、先执行循环 后检查条件。不管 条件如何,都 至少执行一次循环

do
	statement
while (condition); // 有分号

condition 不能为空,condition 使用的变量 必须定义在循环体之外

// 不断提示用户输入一对数,然后求和
string rsp; // 定义在循环体之外
do { // 大括号别忘了
	int val1 = 0, val2 = 0;
	cin >> val1 >> val2;
	cout << val1 + val2 << endl;
	cin >> rsp; 
} while (!rsp.empty() && rsp[0] != '\n'); // 分号别忘了

条件部分 检查用户做出的回答,如果用户没有回答,或者 用户的回答以字母n开始,循环都将终止。否则 循环继续执行

对于 do while 来说 先执行语句 或 块,后判断条件,不允许在条件部分 定义变量

do {
	// ...
	mumble(foo);
} while (int foo = get_foo()); // 错误:将变量声明放在了 do的条件部分

5、跳转语句

C++提供4种 跳转语句:break、continue、goto 和 return

5.1 break 语句

1、break语句 负责终止离它最近的 while、do while、for 或 switch 语句,并从这些语句之后的 第一条语句 开始继续执行

5.2 continue 语句

1、continue语句 终止最近的循环中的 当前迭代 并立即 开始下一次迭代。continue 语句只能出现在 for、while和do while循环内部(没有switch),或嵌套在 此类循环里的语句 或 块的内部

和break一样,出现在 嵌套循环中的continue语句 仅作用于离它最近的循环

continue 语句中断当前迭代,仍然继续循环。对于 while或者 do while 语句,继续判断条件的值;对于 传统的for循环来说,继续执行 for语句头的expression;而对于 范围for循环 来说,序列中的下一个元素初始化 循环控制向量

例:找到的两个连续重复单词必须以大写字母开头

string pre_s, cur_s, tmp_s;
while (cin >> cur_s) {
	if (!isupper(cur_s[0])) continue;// 不是大写字母开头的直接下一轮,判断大写字母 / string使用下标
	if (cur_s == pre_s) {
		cout << cur_s << " ";
		tmp_s = cur_s;
		break;
	}
	pre_s = cur_s;
}
if (tmp_s.empty()) {
	cout << "no repeat" << endl;
}

5.3 goto 语句

1、goto语句 无条件跳转到 同一函数内的 另一条语句
不要在程序里使用 goto语句

2、goto语句 语法形式:goto label; label 是用于标示一条语句的 标识符。带标签语句 是一种特殊的语句,在它的之前 有一个标识符 以及 一个冒号:

label: return; // 带标签语句,可作为 goto的目标

标签指示符 独立于变量 或 其他指示符的名字,因此,标签指示符 可以和 程序中其他实体的标识符 使用同一个名字 而不会相互干扰。goto 语句 和 控制权转向的那条带标签的语句 必须位于同一个函数内

和switch语句类似,goto 语句 也不能将程序的控制权 从变量的作用域之外 转移到 作用域之内

// ...
goto end;
int ix = 10; // 错误:goto语句 绕过了一个带初始化的变量定义

end:
	ix = 10; // 错误:此处的代码需要使用ix,但是goto语句 绕过了它的声明

向后跳过一个已经执行的定义 是合法的。跳回到 变量定义之前 意味着 系统将销毁该变量,然后 重新创建它

// 向后跳过一个带初始化的变量定义 是合法的
begin:
	int sz = get_size();
	if (sz <= 0) {
		goto begin;
	}

goto语句 执行后将销毁sz。因为 跳回到 begin的动作 跨过了sz的定义 语句,所以sz将 重新定义并初始化

6、try 语句块 和 异常处理

1、异常是指 存在于运行时的反常行为,这位行为 超出了函数的正常功能的范围
典型的异常 包括:失去和数据库的连接 以及 遇到意外输入等

2、异常检测:检测出问题的部分 只发出信号,信号的发出方 无需知道 故障将在何处解决
异常处理:专门的代码处理问题

3、异常处理机制 为程序中 异常检测 和 异常处理 这两部分的协作 提供支持。异常处理包括:
1)throw 表达式:异常检测部分 使用throw表达式 来表示它遇到了 无法处理的问题。throw引发了异常

2)try 语句块:异常处理部分 使用try语句块 处理异常。try 语句块 以关键字try 开始,并以 一个或多个catch子句 结束。try 语句块中代码 抛出的异常 通常会被某个catch子句处理
因为 catch 子句处理异常,所以 经常被称为 异常处理代码

3)一套 异常类:用于在throw表达式 和 相关的catch子句之间 传递异常的具体信息

6.1 throw表达式

1、程序的异常检测部分 使用throw表达式 引发了一个异常。throw表达式 包含关键字throw 和 紧随其后的一个表达式,表达式的类型 就是抛出的异常的类型。throw表达式后面通常加分号,构成 表达式语句

以 第一章 5.4 成员函数 与 输出异常处理信息 例子为例,程序是 检查它读入的记录是否是 关于同一种书籍的

#include 
#include "Sales_item.h"
//练习1.24
int main() {
	Sales_item book, book_sum;
	if (std::cin >> book_sum) {
		while (std::cin >> book) {
			if (book.isbn() == book_sum.isbn()) {
				book_sum += book;
			}
			else {
				std::cout << book_sum << std::endl;
				book_sum = book;
			}
		}
		std::cout << book_sum << std::endl;
	}
	else {
		//没有输入警告信息
		std::cerr << "No data?" << std::endl;
		return -1;//表示失败
	}
	return 0;
}

在真实的程序中,应该把 对象相加的代码 和 用户交互的代码 分离开来。改写程序 使得检查完成后 不再直接输出一条信息,而是抛出 一个异常

// 首先 检查两条数据 是否关于同一种书籍
if (item1.isbn() != item2.isbn())
	throw runtime_error("Data must refer to same ISBN");
// 如果程序执行到了这里,表示两个ISBN是相同的
cout << item1 + item2 << endl;

如果ISBN不一样 就抛出一个异常,该异常是类型runtime_error 的对象。抛出异常 将终止当前函数,并把 控制权转移给 能处理该异常的代码

类型runtime_error 是标准库异常 中的一种,定义在 stdexcept 头文件中。我们必须初始化runtime_error 的对象,方式是 给它提供一个 string对象 或者一个C风格的字符串,这个字符串中 有一些关于异常的 辅助信息

6.2 try 语句块

1、try 语句块的 通用语法形式是

try {
	program-statements
} catch (exception-declaration) {
	handler-statements
} catch (exception-declaration) {
	handler-statements
} // ...

try 语句块的一开始 是关键字try,随后 紧跟着一个块
跟在 try块之后的是 一个或多个catch子句。catch 子句包括三部分:关键字catch、括号内一个(可能未命名的)对象的声明(称作异常声明)以及 一个块。当选中了 某个catch子句 处理异常之后,执行与之对应的块。catch 一旦完成,程序跳转到 try语句块 最后一个catch子句之后 的那条语句继续执行

try 语句块中的 program-statements 组成程序的正常逻辑。像其他任意块一样,program-statements 可以有包括声明在内的 任意C++语句。try 语句块内声明的变量 在块外部无法访问,特别是在 catch子句内 也无法访问

2、编写处理代码:6.1中的例子,假设执行Sales_item对象加法的代码 是与用户交互的代码分开来的。其中与 用户交互的代码负责处理发生的异常,它的形式 可能如下所示

while (cin >> item1 >> item2) {
	try {
		// 执行添加两个Sales_item 对象的代码
		// 如果添加失败,代码抛出一个 runtime_error 异常
	} catch (runtime_error err) {
		// 提醒用户两个ISBN必须一致,询问是否 重新输入
		cout << err.what() << "\nTry again? y / n" << endl;
		char c;
		cin >> c;
		if (!cin || c == 'n')
			break; // 跳出while循环
	}
}

程序本来要执行的代码 出现在 try语句块中

try 语句块 对应一个 catch子句,该子句负责处理类型为 runtime_error的异常。如果 try语句块的代码 抛出了runtime_error异常,接下来执行 catch块内的语句
在catch子句中,输出信息 要求用户指定程序是否继续。如果 用户输入’n’,执行 break语句 并退出while循环;否则,直接执行while循环的右侧花括号,意味着 程序控制权 跳回到while条件部分 进行下一次迭代
给用户的提示信息中 输出了 err.what() 的返回值。err的类型是 runtime_error,what是runtime_error 类的一个成员函数,返回的是 初始化一个具体对象时 所用的string对象的副本。每个标准库异常类 都定义了名为what的成员函数,这些函数没有参数,返回值是C风格字符串(const char*)

3、函数在 寻找处理代码的过程中退出:在 复杂系统中,程序在遇到 抛出异常的代码前,其执行路径 可能已经经过了 多个try语句块

寻找处理代码的过程 和 函数调用链 刚好相反
当异常被抛出的时候,首先 搜索抛出该异常的 函数
当 没找到匹配的catch子句,终止该函数,并在 调用该函数的函数中 继续寻找。如果 还是没有找到匹配的catch子句,终止该函数,并在 调用该函数的函数中 继续寻找
以此类推,沿着程序执行路径 逐层回退,直到 找到适当类型的catch子句为止

如果最终 还是没能找到 任何匹配的catch子句,程序转到名为 terminate 的标准库函数。该函数的行为 和系统有关,一般情况下,执行该函数 将导致程序非正常退出。对于 没有任何try语句块定义的异常 也是类似处理:没有try语句块 意味着没有匹配的catch子句

4、编写 异常安全代码 困难:异常中断了 程序的正常流程,从而导致对象处于无效或未完成状态,或者资源没有正常释放 等等。那些在异常发生期间 正确执行了“清理”工作的程序 被称为 异常安全代码
确实要 处理异常 并继续执行的程序,必须时刻清楚 异常何处发生,异常发生后 程序如何确保对象有效、资源无泄漏、程序处于合理状态 等等

6.3 标准异常

1、C++标准库 定义了一组类,用于 报告标准库函数 遇到的问题。这些异常类 也可以在用户编写的程序中使用,分别定义在4个头文件中:
1)exception 头文件定义了 最通用的异常类 exception。它只报告异常的发生,不提供 任何额外的信息
2)stdexcept 头文件定义了 几种常用的异常类

定义的异常类 含义
exception 最常见的问题
runtime_error 只有在运行时才能检测出问题
range_error 运行时错误:生成的结果超出了有意义的值的范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 程序逻辑错误
domain_error 逻辑错误:参数对应的结果值不存在
invalid_argument 逻辑错误:无效参数
length_error 逻辑错误:试图创建一个 超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值

3)new头文件 定义了 bad_alloc 异常类型
4)type_info 头文件 定义了 bad_cast 异常类型

2、标准库异常类 只定义了几种运算,包括 创建或拷贝 异常类型的对象,以及 为异常类型的对象赋值

只能 以默认初始化 的方式 初始化exception、bad_alloc 和 bad_cast 对象,不允许为这些对象 提供初始值
其他异常类型的行为 恰好相反:应该使用string对象 或者 C风格字符串 初始化这些类型的对象,但是不允许 使用默认初始化的方式。当创建 此类对象的时候,必须提供初始值,该初始值 含有错误相关的信息 runtime_error("Data must refer to same ISBN");

异常类型只定义了一个 名叫what的成员函数,该函数 没有任何参数,返回值是一个 指向C风格的字符串的 const char*。该字符串的目的是 提供关于异常的 一些文本信息
what函数 返回的C风格字符串的内容 与 异常对象的类型有关。如果 异常类型有一个字符串初始值,则what返回该字符串。对于其他无初始值的 异常类型来说,what返回的内容 由编译器决定

3、编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果,使得当第二个数是0时抛出异常

#include 
#include 

int main()
{
	int i1,i2;
	std::cin >> i1 >> i2;
	if(i2 == 0)
	{
		throw std::runtime_error("divisor can't be 0");
	}
	std::cout << i1/i2 << std::endl;

	return 0; 
}

输入:1 0
输出:
terminate called after throwing an instance of ‘std::runtime_error’
what(): divisor can’t be 0
Aborted (core dumped)

使用try语句块 去捕获异常。catch子句 应该为用户输出一条提示信息,询问其 是否输入新数 并重新执行try语句块的内容

#include 
#include 

using std::cout; using std::cin; using std::endl; using std::runtime_error;

int main()
{
	int n1, n2;
	while (cin >> n1 >> n2) { // 不管有没有错都可以继续,当然有错可以选择
		try {
			if (n2 == 0) throw runtime_error("divisor can't be zero.");
			cout << n1 / n2 << endl;
		}
		catch (runtime_error err) // 加括号,申明了一个变量err
		{
			cout << err.what() << "\nTry again?  Y/N" << endl;
			char c;
			cin >> c;
			if (!cin || c == 'N')
				break;
		}
	}
	return 0;
}

7、术语表

1、块:(即复合语句)包围在花括号内的 由0条 或 多条语句组成的序列。块也是一条语句,只要是能使用语句的地方,就可以使用 块

2、case标签:在switch语句中 紧跟在case 关键字之后的常量表达式。在同一个 switch语句中 任意两个case标签的值 不能相同

3、continue语句:中止离它最近的循环的当前迭代。控制权转移到 while 或 do while 语句的条件部分、或者 范围for循环的下一次迭代、或者 传统for循环头部的表达式

4、catch 子句:由三部分组成:catch关键字、括号里的 异常声明(位于 catch子句中的声明,指定了该catch 子句能处理的异常类型)以及 一个语句块。catch 子句的代码 负责处理在异常声明中 定义的异常

5、throw表达式:一种 中断当前执行路径的 表达式。throw 表达式抛出一个异常 并把控制权转移到 能处理该异常的最近的 catch语句

6、try语句块:跟在try关键字后面的块,以及 一个或多个catch子句。如果 try语句块的代码 引发异常 并且其中一个catch子句 匹配该异常类型,则 异常被该catch子句处理。否则,异常 将由外围try语句块处理,或者 程序中止

你可能感兴趣的:(C++,Primer,c++,开发语言)