异常无处不在!尽管程序是你设计的,但是却不一定按你的设想运行.面对随时有可能出现的异常情况(俗称程序跑飞了),你该如何应对?C++提出了异常机制.
异常是一种程序控制机制,与函数机制互补.函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它可以在出现"意外"时中断当前函数,并以某种机制(类型匹配)回馈给隔代的调用者相关的信息.
在我以前的文章里,常常讲到防御式编程,即考虑可能出现的非正常情况(比如传入函数必须是正值,却传入了负值),并返回一些有意义的值来及时退出函数而不是让它继续执行.但是这样极其麻烦,比如在函数调用者处还要根据返回值做相应处理,如果调用的层次多了还得依次分层返回.C++异常很好的处理了这个问题.
异常处理就好像直通车一样,不必依次返回.
throw
抛出异常.表达式可以是任何类型,比如你可以抛出一个整数,可以抛出一串字符串,还可以抛出一个自定义的类.(是不是比返回一个固定类型的值好多了?)void function(){
throw 表达式;
}
try
把可能抛出异常的程序块包含起来,用catch
捕捉异常并处理.从上至下匹配,且只会执行其中一条catch
语句,如果没有找到匹配,则会自动调用abort
结束程序.try{
//其他程序
function();
//其他程序
}catch(异常类型){
//处理代码
}catch(异常类型 形参){
//处理代码
}catch(...){//所有其他异常类型
//处理代码
}
catch
中处理不了的异常,可以继续throw
向调用者抛出.try
中的内容称为保护块.
为增加程序的可读性,可以在函数声明中列出可能抛出的所有异常类型.
void function()throw(int, string, double) {//表示可能会抛出整数类型,字符串类型,浮点类型的异常
//...
}
如果抛出了未包含在异常声明内的其他类型异常,则可能会导致程序终止.
如果没有异常接口声明,则会抛出任何异常.
如果不想抛出任何异常,使用throw()
声明.
#include
#include
#include
using namespace std;
int mySqrt(int num) {
if (num < 0) {
throw -1;
}
printf("函数正常结束.");
return sqrt(num);
}
int main(void) {
try {
cout << "开始测试" << endl;
mySqrt(-1);
cout << "函数正常结束!" << endl;
}
catch (int error) {
cout << "抛出了整数类型异常:" << error << endl;
}
catch (...) {
cout << "其他类型的异常" << endl;
}
system("pause");
}
#include
#include
#include
using namespace std;
int mySqrt(int num) {
if (num < 0) {
throw -1;
}
else if (sqrt(num) * sqrt(num) != num) {
throw string("该数开方后不是整数!!!");
}
printf("函数正常结束.");
return sqrt(num);
}
int main(void) {
try {
cout << "开始测试" << endl;
mySqrt(3);
cout << "函数正常结束!" << endl;
}
catch (int error) {
cout << "抛出了整数类型异常:" << error << endl;
}
catch (string error) {//string& error也可以
cout << "抛出了字符串类型异常:" << error << endl;
}
catch (...) {
cout << "其他类型的异常" << endl;
}
system("pause");
}
#include
#include
#include
using namespace std;
int mySqrt(int num) {
if (num < 0) {
throw -1;
}
else if (sqrt(num) * sqrt(num) != num) {
const char* words = "该数开方后不是整数!!!";
throw words;
}
printf("函数正常结束.");
return sqrt(num);
}
int main(void) {
try {
cout << "开始测试" << endl;
mySqrt(3);
cout << "函数正常结束!" << endl;
}
catch (int error) {
cout << "抛出了整数类型异常:" << error << endl;
}
catch (string& error) {
cout << "抛出了字符串类型异常:" << error << endl;
}
catch (char* error) {//加上const才会进入该语句!!!
cout << "抛出了字符串类型(char*)异常:" << error << endl;
}
catch (...) {
cout << "其他类型的异常" << endl;
}
system("pause");
}
#include
#include
#include
using namespace std;
class myException {
public:
myException(int num) {
this->num = num;
id = 1;
cout << "调用了构造函数,ID = " << id << endl;
}
myException(const myException& c) { //拷贝构造函数
num = c.num;
id = c.id + 1;
cout << "调用了拷贝构造函数,ID = " << id << endl;
}
~myException() {
cout << "调用了析构函数,ID = " << id << endl;
}
void print() {
cout << "数字太大,输入的数字:" << num << endl;
}
private:
int num;
int id;
};
int mySqrt(int num) {
if (num < 0) {
throw -1;
}
else if (sqrt(num) * sqrt(num) != num) {
const char* words = "该数开方后不是整数!!!";
throw words;
}
else if (num > 100000) {
myException exception(num);//局部变量,id = 1
throw exception;//通过拷贝构造函数生成了一个全局变量,id = 2
}
printf("函数正常结束.");
return sqrt(num);
}
int main(void) {
try {
cout << "开始测试" << endl;
mySqrt(1000000);
cout << "函数正常结束!" << endl;
}
catch (int error) {
cout << "抛出了整数类型异常:" << error << endl;
}
catch (string& error) {
cout << "抛出了字符串类型异常:" << error << endl;
}
catch (const char* error) {
cout << "抛出了字符串类型(const char*)异常:" << error << endl;
}
catch (myException error) {//使用拷贝构造函数(传参),id = 3
cout << "抛出了自定义类类型异常:";
error.print();
}
catch (...) {
cout << "其他类型的异常" << endl;
}
system("pause");
}
注意到这里调用了两次拷贝构造函数,效率不高,使用匿名类可以少调用一次,catch
内加引用可以再少调用一次.
修改后版本:
#include
#include
#include
using namespace std;
class myException {
public:
myException(int num) {
this->num = num;
id = 1;
cout << "调用了构造函数,ID = " << id << endl;
}
myException(const myException& c) { //拷贝构造函数
num = c.num;
id = c.id + 1;
cout << "调用了拷贝构造函数,ID = " << id << endl;
}
~myException() {
cout << "调用了析构函数,ID = " << id << endl;
}
void print() {
cout << "数字太大,输入的数字:" << num << endl;
}
private:
int num;
int id;
};
int mySqrt(int num) {
if (num < 0) {
throw -1;
}
else if (sqrt(num) * sqrt(num) != num) {
const char* words = "该数开方后不是整数!!!";
throw words;
}
else if (num > 100000) {
throw myException(num);//使用匿名类提高效率
}
printf("函数正常结束.");
return sqrt(num);
}
int main(void) {
try {
cout << "开始测试" << endl;
mySqrt(1000000);
cout << "函数正常结束!" << endl;
}
catch (int error) {
cout << "抛出了整数类型异常:" << error << endl;
}
catch (string& error) {
cout << "抛出了字符串类型异常:" << error << endl;
}
catch (const char* error) {
cout << "抛出了字符串类型(const char*)异常:" << error << endl;
}
catch (myException& error) {//使用引用接参提高效率
cout << "抛出了自定义类类型异常:";
error.print();
}
catch (...) {
cout << "其他类型的异常" << endl;
}
system("pause");
}
细心的读者可以发现函数正常结束(两处)这句话一直都没有打印出来过.
异常也是类,拥有类的特性,有继承,多态,虚函数等等.
异常类 | 头文件 | 异常的含义 |
---|---|---|
bad_alloc | exception | 用new动态分配空间失败 |
bad_cast | new | 执行dynamic_cast失败 |
bad_typeid | typeinfo | 对某个空指针p执行typeid(* p) |
bad_exception | typeinfo | 当某个函数fun()因在执行过程中抛出了异常声明所不允许的异常而调用unexpected()函数时,若unexpected()函数又一次抛出了fun()的异常声明所不允许的异常,且fun()的异常声明列表中有bad_exception,则会有一个bad_exception异常在fun()的调用点被抛出 |
ios_base::failure | ios | 用来表示C++的输入输出流执行过程中发生的错误 |
underflow_error | stdexcept | 算术运算时向下溢出 |
overflow_error | stdexcept | 算术运算时向上溢出 |
range_error | stdexcept | 内部计算时发生作用域的错误 |
out_of_range | stdexcept | 表示一个参数值不在允许的范围之内 |
length_error | stdexcept | 尝试创建一个长度超过最大允许值的对象 |
invalid_argument | stdexcept | 表示向函数传入无效参数 |
domain_error | stdexcept | 执行一段程序所需要的先决条件不满足 |
一些语言规定了只能抛某个类的派生类,比如Java规定抛掷的类必须派生自Exception
类.C++没有强制的规定,不过并不妨碍我们按照这样的规则使用.因为使用标准库提供的异常类,或是根据这些类派生出新的类会让我们方便很多.比如logic_error
,runtime_error
及其派生类都有传字符串的构造函数(通过它传错误信息)及what()
函数(通过它得到错误信息).