C++ 异常
C++ 异常 exception
《C++ Primer 中文版第五版》 Ch 5.6
1. try 语句块和异常处理
- 典型的异常
- 失去数据库连接
- 遇到意外输入
如果程序的问题是输入无效,则异常处理部分可能会要求用户重新输入正确的数据;
如果丢失了数据库连接,会发出报警信息
- C++ 中的异常处理包括:
- throw 表达式(throw expression)
- 异常检测部分使用throw 表达式来表示它遇到了无法处理的问题。我们说
throw
引发( raise )了异常。
- 异常检测部分使用throw 表达式来表示它遇到了无法处理的问题。我们说
- try 语句块(try block)
- 以 关键字
try
开始,并以一个或多个catch
子句(catch clause) 结束 try
语句块中代码抛出的异常通常会被某个catch
子句处理。因为catch
子句 “处理” 异常,所以它们也被称作 异常处理代码( exception handler )。
- 以 关键字
- 一套 异常类(exception class)
- 用于在 throw 表达式和相关的 catch 子句之间传递异常的具体信息
- throw 表达式(throw expression)
1.1 throw 表达式
throw + 表达式
表达式类型就是抛出的异常类型
-
代码示例:
Sales_item item1, item2; cin >> item1 >> item2; // 首先检查 item1 和 item2 是否表示同一种书籍 if (item1.isbn() == item2.isbn()) { cout << item1 + item2 << endl; return 0; // 表示成功 } else { cerr << "Data must refer to same ISBN" << endl; return -1; // 表示失败 }
真实的程序中,应该把对象相加的代码和用户交互的代码分离开来。
改写程序,不在直接输出一条信息,而是抛出一个异常:
// 首先检查两条数据是否是关于同一种书籍的
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 风格的字符串,这个字符串有一些关于异常的辅助信息。
1.2 try 语句块
-
语法
try { program-statements } catch (exception-declaration) { hadler-statements } catch (exception-declaration) { hadler-statements } // ...
异常声明 exception declaration
1.2.1 编写处理代码
假设执行 Sales_item 对象加法的代码是与用户交互的代码 分离开来的。其中与用户交互的代码负责处理发生的异常,它的形式可能如下:
while (cin >> item1 >> item2)
{
try
{
// 执行添加两个 Sales_item 对象的代码
// 如果添加失败,代码抛出一个 runtime_error 异常
} catch (runtime_error err) {
// 提醒用户两个 ISBN 必须一致,询问是否重新输入
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
break; // 跳出 while 循环
}
}
给用户的提示信息中输出了 err.what()
的返回值。err 的类型是 runtime_error
,因此能推断 what
是 runtime_error 类的一个成员函数。
每个标准库异常类都定义了名为 what 的成员函数,这些函数没有参数,返回值是 C 风格字符串(即 const char*)。其中,runtime_error 的 what 成员返回的是 初始化一个具体对象时所用的 string 对象的副本。如果上一节的代码抛出异常,则本节的 catch 子句输出:
Data must refer to same ISBN
Try Again? Enter y or n
1.2.2 函数在寻找处理代码的过程中退出
一个 try 包含另一个 try,新的 try 可能又包含了另一个 try 语句块的新函数,以此类推
寻找处理代码的过程与此相反
- 当异常被抛出时,首先搜索抛出该异常的函数。
- 如果没有找到匹配的 catch 子句
- 终止该函数,并在调用该函数的函数中继续寻找
- 如果还是没有找到匹配的 catch 子句,这个新的函数也被终止,继续搜索调用它的函数
- 如果没有找到匹配的 catch 子句
以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的 catch 子句为止。
如果最终还是没能找到任何匹配的 catch 子句,程序转到名为 terminate 的标准库函数,执行该函数将导致程序非正常退出
如果一段程序没有 try 语句块且发生了异常,系统会调用 terminate
函数并终止当前程序的执行。
异常中断了程序的正常流程
编写异常安全的代码非常困难,我们必须时刻清除异常何时发生,异常发生后程序应该如何 确保对象有效、资源无泄漏、程序处于合理状态 等等
1.3 标准异常
C++ 标准库定义了一组类,用于报告标准库函数遇到的问题。
4 个头文件:
1.3.1 exception
- 定义了最通用的异常类
exception
。 - 只报告异常的发生,不提供任何额外信息
1.3.2 stdexcept
-
定义了几种常用的异常类
定义的异常类 exception 最常见的问题 runtime_error 只有在运行时才能检测出的问题 range_error 运行时错误:生成的结果超出了有意义的值域范围 overflow_error 运行时错误:计算上溢 underflow_error 运行时错误:计算下溢 logic_error 程序逻辑错误 domain_error 逻辑错误:参数对应的结果值不存在 invalid_argument 逻辑错误:无效参数 length_error 逻辑错误:试图创建一个超出该类型最大长度的对象 out_of_range 逻辑错误:使用一个超出有效范围的值
1.3.3 new
- 定义了
bad_alloc
异常类型 - new 申请内存空间如果失败的话抛出这个异常
1.3.4 type_info
- 定义了
bad_cast
异常类型
1.3.5 初始化
exception
、bad_alloc
和 bad_cast
只能默认初始化,也就是无初始值。
其他异常类型,应该使用 string 对象或者 C 风格字符串初始化这些类型的对象,创建这类对象时,必须提供初始值
1.3.6 成员函数 what
异常类型之定义了一个名为 what
的成员函数,没有任何参数,返回一个指向 C 风格字符串的 const char*
,提供关于异常的一些文本信息。
what
函数返回的 C 风格字符串的内容与异常对象的类型有关。如果异常类型有一个字符串初始值,则 what
返回该字符串。对于其他无初始值的异常类型来说,what
返回的内容由编译器决定。
1.4 练习题
-
编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果
-
思路:
除数不能为0,这里先不用 try-catch 机制,而是直接输出错误信息
-
题解:
#include <iostream> using namespace std; int main() { int ival1, ival2; cin >> ival1 >> ival2; if (ival2 == 0) { cout << "除数不能为 0" << endl; return -1; } cout << "两数相除的结果是:" << ival1 / ival2 << endl; return 0; }
这里没有考虑输入的数不是 int 类型的情况,这种情况在 1.5 小节中具体实现
-
修改你的程序,使得当第二个数是 0 时抛出异常。先不要设定 catch 子句,运行程序并真地为除数输入零,看看会发生什么?
-
思路:
通过抛出异常来提示用户的输入错误
-
题解:
#include <iostream> #include <stdexcept> using namespace std; int main() { cout << "Please enter the dividend and the divisor in order:" << endl; int ival1, ival2; cin >> ival1 >> ival2; if (ival2 == 0) throw runtime_error("The divisor cannot be zero!"); cout << "The quotient of two numbers is: " << ival1 / ival2 << endl; return 0; }
键入
5 0
结果:
5 0 terminate called after throwing an instance of 'std::runtime_error' what(): The divisor cannot be zero!
8 4 The quotient of two numbers is: 2
-
-
修改上一题的程序,使用
try
语句块去捕获异常。catch
子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行try
语句块的内容-
题解:
#include <iostream> #include <stdexcept> using namespace std; int main() { cout << "Please enter the dividend and the divisor in order: " << endl; int ival1, ival2; while (cin >> ival1 >> ival2) { try { if (ival2 == 0) throw runtime_error("ERROR! The divisor cannot be zero!"); cout << "The quotient of two numbers is: " << ival1 / ival2 << endl; return 0; } catch (runtime_error err) { cout << err.what() << "\nTry Again? Please enter y or n" << endl; char c; cin >> c; if (!cin || c == 'n') break; } } return 0; }
-
1.5 C++ 数据输入类型异常处理示例
https://blog.csdn.net/weixin_45652508/article/details/108546384
输入一个整数,判断是否为 素数
-
如果输入的是小数或者其他字符,返回输入错误的提示
-
主要用到了
atoi
函数,将字符串转换为 整数,如果不是数字,atoi 会返回 0,以此判断输入的是否是 数字 -
atoi 遇到小数不会报错,所以先查找小数点,排除小数的情况
-
判断素数用到了
sqrt
(square root 平方根) 函数,在库中
#include <iostream>
#include <cstdlib.h>
#include <cmath>
using namespace std;
bool isPrime(int a)
{
if (a < 0)
{
cout << "a is not a prime number" << endl;
return false;
}
for (int i = 2; i <= int(sqrt(a)); ++i)
{
if (a % i == 0)
{
cout << "a is not a prime number" << endl;
return false;
}
}
cout << "a is a prime number!" << endl;
return true;
}
int main()
{
string str;
int a(1);
cin >> str;
try
{
// 先搜索小数点
size_t fi = str.find('.');
// find 函数在找不到的情况下会返回 npos
if (fi != str.npos)
throw 1; // 有小数点,抛出异常 1
if (str != "0" && str != "1" && str != "2")
{
a = atoi(str.c_str());
if (a == 0)
throw 2; // str 无法转换为 int,抛出异常2
isPrime(a);
}
else
{
if (str == "2") cout << "a is a prime number" << endl;
else cout << "a is not a prime number" << endl;
}
}
catch (int)
{
cout << "The input number is illegal" << endl;
exit(0);
}
return 0;
}