1 异常处理
异常处理是一种允许两个独立开发的程序组件在程序执行期间遇到程序不正常的情况,相互通信的机制。
一般的异常为:除0错误,数组越界,内存不足等;
在C语言中遇到异常的处理方式:终止程序(发生段错误时),返回错误码(在系统编程中常见)等;
但是此类方式都不能清晰的告诉程序猿发生了什么错误,描述的不明确,所以C++中引入了异常处理机制。
2 异常处理机制
异常处理的思想:当函数在执行过程中出现异常时,不在本函数内立即执行,而是将异常抛出,到调用该函数的地方对它进行处理。
异常处理的三个模块
(1)try 检查是否异常
(2)throw抛出异常
(3)catch捕获异常
try-catch块语法:
try
{
// 检查可能会出现异常的函数或语句
}
catch(类型 形参)
{
// 捕获到异常,需要执行的代码
}
我们以“除0错误”为例进行演示:
#include <iostream>
using namespace std;
int div(int num1,int num2)
{
if(num2 == 0)
{
throw string("除0错误"); // 抛出异常
}
return num1/num2;
}
int main()
{
int num1 = 10;
int num2 = 0;
try // try 检查异常
{
ret = div(num1,num2);
}
catch(const string& s) // catch捕获异常
{
cout<<s<<endl;
}
return 0;
}
运行结果为:
【注】:(1)异常必须被捕获,否则会报错;
(2)抛出异常后会影响执行流,会直接跳到捕获它的地方去,后面的代码便不执行了;
(3)如果有很多捕获它的地方,则跳到离抛出异常处最近的类型匹配的捕获异常的地方;
(4)捕获异常后,代码继续按顺序执行。
3 栈展开
我们先通过一段代码了解一下栈展开
#include <iostream>
using namespace std;
int div(int num1,int num2)
{
if(num2 == 0)
{
throw string("除0错误");
}
return num1/num2;
}
void f1(int num1,int num2)
{
div(num1,num2);
}
void f2(int num1,int num2)
{
div(num1,num2);
}
int main()
{
int num1 = 10;
int num2 = 0;
int ret = 0;
try
{
// ret = div(num1,num2);
f2(num1,num2);
}
catch(const string& s)
{
cout<<s<<endl;
}
cout<<ret<<endl;
return 0;
}
找到catch语句,处理被抛出异常的过程为:
(1)如果throw语句在try块内,检查与try块相关联的catch语句,如果类型匹配则处理异常;
(2)如果为找到,则继续在调用它的函数内继续查找是否有匹配的catch语句;
(3)如果有,则处理异常,如果没有则继续查找…;
(4)直至找到main函数中,如果有匹配的catch语句,则处理异常,如果没有,则报错。
4 异常安全
我们先通过“除0”异常处理的例子演示异常安全:
#include <iostream>
using namespace std;
int div(int num1,int num2)
{
if(num2 == 0)
{
throw string("除0错误");
}
return num1/num2;
}
void catch_div(int num1,int num2)
{
try
{
div(num1,num2);
}
catch(const string& s)
{
cout<<s<<endl;
}
}
int main()
{
int* p = new int();
catch_div(10,0); // 在调用函数catch_div中有捕获异常,则程序直接跳到捕获异常的地方
delete p; // 不能正确完成delete指针,可能造成内存泄漏,引发异常安全的问题
return 0;
}
解决办法:
(1)采用异常的重新抛出;
(2)采用智能指针,将资源开辟即初始化。
5 异常的重新抛出
异常的重新抛出:有些时候,单个catch字句不能完全处理异常,catch子句可以通过重新抛出异常,让更上层的catch子句来处理它。
#include <iostream>
using namespace std;
int div(int num1,int num2)
{
if(num2 == 0)
{
throw string("除0错误");
}
return num1/num2;
}
void catch_div(int num1,int num2)
{
try
{
div(num1,num2);
}
catch(const string& s)
{
cout<<s<<endl;
}
}
int main()
{
int* p = new int();
// catch_div(10,0);
try
{
catch_div(10,0);
}
catch(...) // 不知道catch_div里面是什么类型的异常
{
delete p;
throw; // 抛出默认类型的异常
}
delete p;
return 0;
}
我们在main函数内部不直接调用catch_div,而是对catch_div的异常进行捕捉,如果捕捉到异常,则释放p指针并将该异常重新抛出,让上层catch字句去处理该异常,如果未捕捉到异常,则在try中调用catch_div之后直接释放指针p。
6 异常优缺点
优点:
(1)相比于传统C语言的方式而言,异常对程序错误的原因描述的更加明确;
(2)Java、Python等其他语言也都引入了异常,更容易与其他语言兼容;
(3)测试方便。
缺点:
(1)造成执行流乱跳,不按顺序执行,不方便预判程序的运行结果;
(2)效率降低,相较于直接返回错误码而言,异常还需要找捕获它的地方;
(3)可能会引发异常安全的问题。