异常:异常是一种处理错误的方式,当一个函数发现自己无法处理这个错误时,就让函数的直接调用者或者间接调用者去处理这个错误
异常的使用机制
throw: 通过throw关键字,当程序出现问题时,程序会抛出一个异常
catch: catch关键字用于捕获异常,在想要处理问题的地方,通过异常处理程序捕获异常,可以有多个catch进行捕获
try: try块的代码标识将被激活特定异常,后面通常跟着一个或多个catch块
try
{
//保护的标识代码
}catch(ExceptionName e1)
{
//catch块
}catch(ExceptionName e2)
{
//catch块
}catch(ExceptionName eN)
{
//catch块
}
异常抛出和匹配规则
- 1.异常通过抛出对象引发的,此对象类型决定了应该激活那个catch的处理代码
- 2.选中的处理代码是调用链中与该对象类型匹配且距离抛出异常位置最近的内一个
- 3.抛出异常对象之后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以生成一个拷贝对象,这个拷贝的临时对象会被catch以后销毁
- 4.catch(…)可以捕获任意类型的异常,但是不知道异常错误是什么
在函数调用链中异常栈展开匹配原则
- 1.首先检查throw本身是否在try块的内部,如果在try块内部,然后再查找匹配的catch语句,如果有与之匹配的catch语句,则调到catch的地方进行处理
- 2.没有匹配的catch则退出当前函数栈,继续再调用函数的栈中进行查找匹配的catch
- 3.如果到达main函数栈,仍旧没有所匹配的,则终止程序,所以经常在最后加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会终止(上面部骤,沿着这个调用链查找匹配的catch子句就叫做栈展开)
- 4.找到相匹配的catch语句并处理之后,会继续沿着catch子句后面继续执行
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
int main()
{
try {
Func();
}
catch (const char* errmsg){
cout << errmsg << endl;
}
catch (...) {
cout << "unkown exception" << endl;
}
return 0;
}
对于上面的代码,会发现当发现除0发生错误时会抛出异常,而且下面的array数组没有得到释放
所以需要在捕获异常之后,再将异常抛出去,交给下一层处理
double Division(int a, int b)
{
// 当b == 0时抛出异常
if (b == 0)
throw "Division by zero condition!";
else
return ((double)a / (double)b);
}
void Func()
{
try{
int len, time;
cin >> len >> time;
cout << Division(len, time) << endl;
}
catch(...){
cout<<"delete []"<<array<<endl;
delete[]array;
throw;
}
cout<<"delete []"<<array<<endl;
}
int main()
{
try {
Func();
}
catch (const char* errmsg){
cout << errmsg << endl;
return 0;
}
抛异常规范:
- 1.在函数的后面接throw(类型)表示函数可能抛掷的所有异常类型
- 2.函数的后面接throw()表示函数不会抛出异常
- 3.若无异常接口声明,则表示此函数可能会抛出任意类型异常
// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator new (std::size_t size, void* ptr) throw();
异常安全:
- 1.构造函数完成对象的构造和初始化时,最好不要在构造函数中抛出异常,否则可能会导致对象不完整或没有完全初始化
- 2.析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄露(内存泄露,句柄未关闭等问题)
异常优缺点:
优点:
1.异常对象定义完成之后,可以清晰的显示错误的各种信息,可以更好的帮助定位程序的bug
2.灵活运用异常,能够更好的进行单元测试的白盒测试
3.部分函数使用异常更方便处理,比如构造函数没有返回值,不方便使用错误码的方式处理
缺点:
1.异常会导致程序的执行流乱跳,运行时出错抛异常就会乱跳,不容易分析程序和跟踪错误
2.C++没有垃圾回收机制,资源需要自己管理(开辟,释放),所以异常容易导致内存泄漏和死锁等异常安全问题