1.异常机制的语法
try {
try-fields //我们程序执行要实现某种功能所必需的语句
throw exception-object;//利用条件判断,当某种条件满足时抛出异常表达式
}
catch (exception-[object | pointer | reference] e) { //捕获异常并进行异常处理,不同的异常表达式类型需要不同的catch块,
catch-fields
}
catch (...) { //捕获异常并进行异常处理,这里面的...是可以捕捉任意类型的异常
catch-fields
}。
例1:除法运算过程中的除数为零的判断。
没有异常处理的程序:
double Div(double a,double b) { if (b==0) //除数b为零,出错告警中断 { cout<<"Error:attempted to divid by zero!/n"; exit(1); } else return a/b; //除数b为非零,正常返回除法运算结果 } void main() { cout<<"8/2="<<Div(8,2)<<endl; cout<<"1.5/0.0="<<Div(1.5,0.0)<<endl; cout<<"3.4/3.1="<<Div(3.4,3.1)<<endl; }
带异常处理的程序:
double Div1(double a,double b) { if (b==0) throw b; //发现异常,抛出异常对象b return a/b; } void main() { try { cout<<"8/2="<<Div1(8,2)<<endl; cout<<"1.5/0.0="<<Div1(1.5,0.0)<<endl; cout<<"3.4/3.1="<<Div1(3.4,3.1)<<endl; } catch(double) //异常处理程序 { cout<<"Error:attempted to divid by zero!/n"; } }
2.异常机制分析
★try:诊断异常代码;
try{
//可能出现异常的情况
}
☆可能出现异常的三种情况:
①可执行语句;②一个函数调用;③一个函数调用另一个函数;
★throw:抛出错误信息;
判断条件检查
条件满足 throw 参数(只有一个,可以是任何类型,甚至是一个对象)
例:
if(分母==0){
throw 参数(只有一个,可以是任何类型,甚至是一个对象)
}
★catch:捕获异常信息;
catch(参数类型参数)//只能一个参数,形参可以被省略,但省略后不能输出异常信息,依然可以捕获;
{ 异常处理 }
注意:catch块不能访问try块里面定义的临时变量。
★注意:如果throw抛出了异常,异常类型如果与catch块后面的类型匹配,catch块内的代码将会被执行,在try语句后面可以有多个catch块,程序会寻找第一个相匹配的catch块,实行catch块的语句代码,然后跳到最后一个catch块的下一行代码,如果没有匹配的catch块,则异常返回上一层try-catch语句,如果没有相应的catch发现,程序将会终结。
★try-throw-catch的三种写法:
//第一种:(操作放在try块中进行判断)
void func(){ float x, num, den; ... //initialize num and den try{ //把操作放到try块里面,不良的写法; if(den==0){ throw "Divided by zero"; } x=num/den; } //... }
//第二种:(把操作放到函数体中判断并实现)
float divide(float a, float b){ //在函数体中实现操作,推荐写法; if(b==0){ throw "divided by zero"; } return a/b; } void func(){ float x, num, den; //initialize num and den try{ x=divide(num, den); } catch(const char* error){ cout<<error; } //... }
//第三种:函数嵌套
float divide(float a, float b){ if(b==0){ throw "divided by zero"; } return a/b; } float middle(float a, float b){ //嵌套写法,推荐写法; return divide(a, b); } void func(){ float x, num, den; //initialize num and den try{ x=middle(num, den); } catch(char* error){ cout<<error; } //... }
函数嵌套的异常捕捉例子:
#include<iostream> using namespace std; float divide(int a, int b){ if(b==0){ throw "divided by zero"; } return float(a)/float(b); } float middle(int a, int b){ try{ return divide(a, b); } catch(const char* str){ cout<<"Caught by function middle."<<endl; throw str; } } void func(int d){ float x; int num=100; int den=d; //initialize num and den try{ x=middle(num, den); } catch(const char* error){ cout<<error<<endl; } } int main() { int i=1; cin>>i; func(i); return 0; }
3.异常处理的问题------栈展开(Stack unwinding):
★定义:如果一个函数里产生异常,那么这个函数将会被终结,并且本地变量(栈上的变量)会被释放。但是如果有指针且动态分配了内存,那么栈上的指针将会被释放,而指针指向的堆内存没有被释放,这时会发生内存泄漏。在这种情况下,为了避免内存泄漏,必须把指针抛给它的上一层调用者,让它来释放这块堆内存。我们可以把这个指针封装到一个错误消息类里面去,然后抛出这个类的对象(构造函数构造的临时对象),为了避免临时对象的生成,我们在catch块里用这个类的引用做参数。
例:
func(){
int a=5; //在栈上声明的;
int b=8; //在栈上声明的;
char* p=new char[100] //p在栈上,p指向的内存在堆上;
//throw "exception"; //会发生内存泄漏;
...
...
}
▲为了避免内存泄露,我们需要将指针抛出。我们把指针封装在一个错误类里面,然后把对象抛出,为了避免拷贝构造,我们传一个对象的引用。
例:
#include<iostream> using namespace std class Error_message { public: char* message; int* arrayptr; Error_message(char* str, int* p):message(str),arrayptr(p){} }; void f(){ int* a=new int[10]; int i=0; if(i==0){ throw Error_message("error", a); //throw "error"(抛出一个构造函数构造出来的临时对象); } delete [] a; //已经throw了,这里的delete无作用; } void g(){ try{ f(); } catch(Error_message& m){ delete [] m.arrayptr; //通过构造函数删除指针在堆上分配的空间; cout<<m.message<<endl; } catch(const char* str){ cout<<str<<endl; } } int main() { g(); return 0; }
▌不捕获异常(Uncaught exception):
★定义:如果一个异常没有被catch住,或者没有写catch块,这种情况就叫不捕获异常。如果一个异常没有被捕获住,则会终结(terminate)函数。
例:
func(){ int* p=new char[100000000000000]; if(p==NULL){ throw "exception"; //此处终结函数; } delete p; } void my_clear(){ cout<<"OK,clear!"<<endl; } //terminate() set_terminate(my_clear); //调用set_terminate()捕获异常; int main() { func(); }
重设异常的两个函数:From MSDN
set_terminate():Installs your own termination routine to be called by terminate.
set_new_handler( ):Installs a user function that is to be called when operator new fails in its attempt to allocate memory.
new_handler set_new_handler(new_handler _Pnew);
例子:
// new_set_new_handler.cpp // compile with: /EHsc #include<new> #include<iostream> using namespace std; void __cdecl newhandler( ) { cout << "The new_handler is called:" << endl; throw bad_alloc( ); return; } int main( ) { set_new_handler (newhandler); try { while ( 1 ) { new int[5000000]; cout << "Allocating 5000000 ints." << endl; } } catch ( exception e ) { cout << e.what( ) << " xxx" << endl; } } 结果: Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. Allocating 5000000 ints. The new_handler is called: bad allocation