1. c++的异常处理介绍
c++的异常处理语法:
int main(void)
{
try
{
double a = div(6, 0);
}
catch(...)
{
printf("div 0 is error!!\n");
}
return 0;
}
如上try…catch两个语句块,将正常功能代码和异常处理代码在一个函数中分隔开,提高代码的可读性:try语句用于处理正常代码逻辑,catch语句用于处理异常情况,try语句中的异常由对应的catch语句处理。
在div()函数中,若发现除以0操作,可以通过throw语句抛出异常,并终止本函数运行:
double div(double a, double b)
{
double tmp = 0.00000001;
if ((-tmp < b) && (b < tmp))
{
throw 0;
}
else
return a / b;
}
编译运行:
在div()函数中抛出的异常必须被catch处理,当前函数能够处理异常,程序则继续往下执行,当前函数无法处理异常,则函数停止执行并返回。未被处理的异常会顺着函数调用栈向上抛出,直至被处理为止,
若直到main()函数都没有处理,那么程序将停止执行:
int main(void)
{
//try
//{
double a = div(6, 0);
//}
//catch(...)
//{
// printf("div 0 is error!!\n");
//}
return 0;
}
编译运行:
一个函数执行过程中可能会抛出多重类型的异常,那么也就意味着,一个try语句可以跟上多个catch。catch语句可以定义具体处理的异常类型,不同的类型的异常由不同的catch语句负责处理。代码中的catch(…)可用于处理所有类型的的异常,需要注意,任何异常都只能被捕获一次。
void exception_test1()
{
try{
throw 'a';
}
catch(char c)
{
printf("catch(char c)\n");
}
catch(int i)
{
printf("catch(int i)\n");
}
catch(double d)
{
printf("catch(double d)\n");
}
catch(...)
{
printf("catch(...)\n");
}
}
int main(void)
{
exception_test1();
return 0;
}
编译运行:
异常抛出后,编译器将自上而下严格匹配每一个catch语句处理的类型,异常处理会进行严格类型匹配,不会进行任何类型转换:
void exception_test2()
{
throw std::string("hello");
}
int main(void)
{
try
{
exception_test2();
}
catch(char* s)
{
printf("catch(char* s)\n");
}
catch(const char* cs)
{
printf("catch(char* s)\n");
}
catch(std::string ss)
{
printf("catch(std::string ss)\n");
}
return 0;
}
编译运行:
2. catch语句块抛出异常
前面讲到try语句块中可能会抛出异常,抛出的异常被当前函数或者函数调用栈的上一和函数的catch语句块处理。在catch语句块中,处理异常操作还可以是把异常再次抛出,catch抛出的异常需要外层的try…catch语句捕获。
在catch没有抛出异常的情景中:
void test_func(int i)
{
switch (i)
{
case 1:
throw -1;
break;
case 2:
throw -2;
break;
case 3:
throw -3;
break;
default:
break;
}
printf("void test_func(int i)\n");
}
int main(void)
{
try
{
test_func(2);
}
catch(int i)
{
printf("catch(int i), i = %d\n", i);
}
return 0;
}
编译运行:
抛出异常为-2,此时对于程序调试者可能还尚未知道-2究竟是什么错误码。然而它并不知道test_func(int i)的源代码,只能通过另外的查询手段去获取-2是什么异常,所以说比较麻烦。在实际工程中,catch语句块捕获到的异常可以被重新解释后抛出,假设:
-1代表输入错误
-2代表通讯异常
-3代表其它错误
上面函数不动,增加run_test_func()函数:
void run_test_func(int i)
{
try{
test_func(i);
}
catch (int i)
{
if (i == -1)
{
throw "input error!";
}
else if (i == -2)
throw "communication erroe!";
else if ( i == -3)
throw "error!";
}
}
在main()函数中,try语句块调用run_test_func()函数,catch语句块捕获run_test_func()可能抛出的异常:
int main(void)
{
try
{
run_test_func(2);
}
catch(const char* s)
{
printf("%s\n", s);
}
return 0;
}
编译运行:
这样就能对程序所抛出的异常类型一目了然。
3. 抛出类类型异常
异常的类型还可以是自定义的类类型,对于类类型的异常匹配规则依旧是从上而下严格匹配,但是注意,子父类的赋值兼容性原则在异常匹配中仍然适用,所以说匹配子类异常的 catch应该放在上面,匹配父类异常的chtch放在下面。
//异常的基类
class exceptionBase
{
public:
};
class exceptionSub : public exceptionBase
{
private:
int m_id;
std::string desc_str;
public:
exceptionSub(int id, std::string str) : m_id(id), desc_str(str)
{}
int id()
{
return m_id;
}
std::string description()
{
return desc_str;
}
};
void test_func(int i)
{
switch (i)
{
case 1:
throw -1;
break;
case 2:
throw -2;
break;
case 3:
throw -3;
break;
default:
break;
}
printf("void test_func(int i)\n");
}
void run_test_func(int i)
{
try{
test_func(i);
}
catch (int i)
{
if (i == -1)
{
//throw "input error!";
throw exceptionSub(-1, "input error!");
}
else if (i == -2)
//throw "communication erroe!";
throw exceptionSub(-2, "communication erroe!");
else if ( i == -3)
//throw "error!";
throw exceptionSub(-3, "error!");
}
}
int main(void)
{
try
{
run_test_func(2);
}
catch(exceptionSub& ee)
{
printf("ID: %d\n", ee.id());
printf("description: %s\n", ee.description().c_str());
}
catch(exceptionBase& ee) //因为支持赋值兼容性原则,所以父类类型应放在子类类型后面
{
printf(" catch(exceptionBase& ee)\n");
}
return 0;
}
编译运行:
4. try…catch的两种特殊写法
(1) c++的函数声明和定义时可以直接指定可能抛出的异常类型,异常声明成为函数声明的一部分。这可以提高代码的可阅读性,如:
void func1(int i) trow(int) //表示该函数可能抛出int类型的异常
{
//...
}
void func2(int i, char c) //表示该函数可能抛出int类型、char类型的异常
{
//...
}
需要注意的是,函数的异常声明是一种和编译器的协议,也就是说函数声明异常后就只能抛出所声明的类型的异常。
void func(int i) throw(int)
{
if (i > 5)
throw -i;
}
int main(void)
{
try{
func(9);
}
catch(int i)
{
printf("exception: %d\n", i);
}
catch(...)
{
printf("catch(...)\n");
}
return 0;
}
编译运行正常:
尝试在声明抛出异常类型为int的func()函数中抛出char类型的异常:
void func(int i) throw(int)
{
if (i > 5)
throw 'e';
}
编译运行:
即使是在main()函数中有catch(…)代码用于捕捉所有异常,但是不起作用,func()函数违背了与编译器的协议了。
(2) try…catch的意义在于用于分隔正常功能代码与异常功能代码,这个分隔实现还可以写成这样:
void test_fun(int i) try
{
func(i);
printf("test_fun...\n");
}
catch(int i)
{
printf("test_fun, catch(int) = %d\n", i);
}
catch(...)
{
printf("test_fun, catch(...)\n");
}
int main(void)
{
test_fun(12);
test_fun(4);
return 0;
编译运行:
这样的写法会让初学者感觉try…catch处于两个函数一样,其实不然,它们跟前面写的在一个函数内实现的效果是一致的。
c++的异常先笔记到这里,下来看看c++标准库的异常类。