第25课 - 异常处理 - 上
1. 典型问题一
所有的代码都有可能不按照预定的方式进行
double Div(double a, double b)
{ return a / b;}
double Add(double a, double b)
{ return a + b;}
double Minus(double a, double b)
{ return a - b;}
double Multi(double a, double b)
{ return a * b;}
这一组数组单纯的实现了四则运算,但是出发函数却总是会有意外。很明显,当除数为0的首,Div函数的调用将产生无法预见的错误。
C方式的解决方案:
double Div(double a, double b, bool* valid)
{
*valid = true;
if( (-0.000000001 < b) && (b < 0.000000001) )
{
*valid = false;
return 0;
}
return a / b;
}
缺陷:
(1)每次调用Div函数后都必须判断结果是否合法。
(2)参数valid如果没有指向合法的内存空间会产生错误。(没有办法解决)
2. 典型问题二
程序中会出现大量的处理异常的代码
int MemSet(void* dest, unsigned int length, unsigned char v) //将内存设置为某一个值
{
if( dest == NULL )
{
return INVALID_POINTER;
}
if( length < 4 )
{
return INVALID_LENGTH;
}
if( (v < 0) || (v > 9) )
{
return INVALID_PARAMETER;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; i<length; i++)
{
p[i] = v;
}
return SUCCESS;
}
C方式的典型调用代码
#include <cstdlib>
#include <iostream>
using namespace std;
int MemSet(void* dest, unsigned int length, unsigned char v)
{
if( dest == NULL )
{
return -1;
}
if( length < 4 )
{
return -2;
}
if( (v < 0) || (v > 9) )
{
return -3;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; i<length; i++)
{
p[i] = v;
}
return 0;
}
int main(int argc, char *argv[])
{
int ai[5];
double ad[4];
char ac[3];
int ret;
ret = MemSet(ai, sizeof(ai), 0);
if( ret == 0 ) //下面的代码没法一眼就看出来哪个是正确的,哪个是处理异常的
{
}
else if( ret == -1 )
{
}
else if( ret == -2 )
{
}
else if( ret == -3 )
{
}
ret = MemSet(ad, sizeof(ad), 1);
if( ret == 0 )
{
}
else if( ret == -1 )
{
}
else if( ret == -2 )
{
}
else if( ret == -3 )
{
}
ret = MemSet(ac, sizeof(ac), 2);
if( ret == 0 )
{
}
else if( ret == -1 )
{
}
else if( ret == -2 )
{
}
else if( ret == -3 )
{
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
缺陷:
正常逻辑代码和异常处理代码混合在一起,导致代码迅速膨胀,难以掩护。
其他C方式的异常处理方案(不常用,使得析构函数不被使用)
goto语句
setjmp()和longjmp()
#include <cstdlib>
#include <iostream>
using namespace std;
int MemSet(void* dest, unsigned int length, unsigned char v)
{
if( dest == NULL )
{
return -1;
}
if( length < 4 )
{
return -2;
}
if( (v < 0) || (v > 9) )
{
return -3;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; i<length; i++)
{
p[i] = v;
}
return 0;
}
int main(int argc, char *argv[])
{
int ai[5];
double ad[4];
char ac[3];
int ret = 0;
ret = MemSet(ai, sizeof(ai), 0);
if( ret != 0 )
{
goto ERROR;
}
ERROR:
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
在C语言中可以使用上面两种解决方案将异常处理代码放到统一的地方,与正常逻辑代码分开。
但在C++中,这两种方法可能导致对象的构造函数或者析构函数得不到调用而引发错误。
3. 典型问题三
C++中提供了try和catch语块对可能产生异常的代码进行分开处理:
try语块处理正常逻辑,catch语块处理异常。
C++语言中通过throw语句引发一个异常。
#include <cstdlib>
#include <iostream>
using namespace std;
#define DIV_ZERO_ERROR -1
double Div(double a, double b)
{
if( (-0.000000001 < b) && (b < 0.000000001) ) //抛出零异常
{
throw DIV_ZERO_ERROR;
}
return a / b;
}
int main(int argc, char *argv[])
{
try
{
cout<<Div(3, 1.1)<<endl;
cout<<Div(1, 0)<<endl;
cout<<Div(1, 2)<<endl;
//正常逻辑代码,可能产生除零异常
}
catch(int error)
{
cout<<"Exception: "<<endl; //处理除零异常
cout<<error<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
运行结果:
2.72727
Exception:
-1
处理机制:
throw语句用于将异常“对象”抛出。
throw语句将异常抛出,如果在当前函数中没有try ... catch语句能够处理该异常,则当前函数将立即返回。异常被传递到上层调用函数,仍然需要try ... catch语句进行处理,如果上层函数也没有能力处理该异常,则异常继续向更上层函数的函数传递。
如果在函数调用栈中的所有函数都无法处理抛出的异常,则程序异常终止。
异常顺着函数调用栈“向上”传播,直到有响应的catch块处理。
4. 手把手写代码-使用try...catch
#include <cstdlib>
#include <iostream>
using namespace std;
void MemSet(void* dest, unsigned int length, unsigned char v)
{
if( dest == NULL )
{
throw -1;
}
if( length < 4 )
{
throw -2;
}
if( (v < 0) || (v > 9) )
{
throw -3;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; i<length; i++)
{
p[i] = v;
}
}
int main(int argc, char *argv[])
{
int ai[5];
double ad[4];
char ac[3];
try
{
MemSet(ai, sizeof(ai), 0);
MemSet(ad, sizeof(ad), 1);
MemSet(ac, sizeof(ac), 2);
}
catch(int e)
{
cout<<e<<endl;
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
运行结果:
-2
5. try ... catch匹配示例
同一个try语句块可以跟上多个catch语句块:同一个try语句块可以抛出多种不同类型的异常,不同类型的异常由不同的catch语句块负责处理。
异常被抛出后会自上而下注意匹配catch语句块:异常匹配时,不会进行默认类型转换。
#include <cstdlib>
#include <iostream>
using namespace std;
int test(int i)
{
if( i == 1 )
{
throw -1;
}
if( i == 2 )
{
throw "ERROR";
}
if( i == 3 )
{
throw 0.5;
}
if( i == 4 )
{
throw 'd';
}
return i;
}
int main(int argc, char *argv[])
{
for(int i=0; i<3; i++)
{
try
{
cout<<test(i)<<endl;
}
catch(int e)
{
cout<<"Int: "<<e<<endl;
}
catch(const char* e)
{
cout<<"const char*: "<<e<<endl;
}
catch(double e)
{
cout<<"double: "<<e<<endl;
}
}
cout << "Press the enter key to continue ...";
cin.get();
return EXIT_SUCCESS;
}
运行结果:
0
Int:-1
const char*:ERROR
若for(int i=0; i<3; i++)改成for(int i=0; i<5; i++),将有多余的项目是处理不了的,程序会运行不了。
小结:
异常处理是程序中随处可见的情况。
在工程实践中,大多数的代码都是用于处理异常的。
异常处理的质量直接决定最终产品的质量。
C++中提供了try ... catch语句用于将正常逻辑代码与异常处理代码进行分开处理。