异常处理一般性概念
修复技术是提高代码健壮性的最有效方法之一。C语言中实现错误处理的方法是将函数调用与错误处理程序紧密结合起来,这使得错误处理的使用很不方便。在传统的C语言程序设计中,一个函数的出错信息往往是通过函数的返回值获得,这就要求函数的使用者在每次的函数调用时都要进行相应的错误判断,有时,返回值所包含的错误信息较为复杂时,对出错信息的判断和处理也变的非常复杂,而且对同一函数的重复 调用往往要伴随着对错误信息的重复判断和处理。这样的异常处理方式使得程序代码既累赘,可读性又差。而且往往因为缺乏条理而遗漏对某个错误的处理。
异常处理通过把异常的检查和异常的处理分开,使得程序的代码简洁、清晰、可读性强。
例如:假设有一个初始化数据库的函数InitDb(),它需要调用创建数据库的函数CreateDb(),创建用户及授权函数CreateUser(),创建表函数CreateTable(),CreateTable()又调用创建索引的函数CreateIndex(),如下图:
每个函数都以返回值表示处理的结果,在InitDb()的返回值中,如果既要包含最终的错误信息,又要包含那一层次调用出错的信息,一种代码的形式如下:
int CreateUser()
{
//...
}
int CreateIndex()
{
//...
}
int CreateTable()
{
//....
if((iRet=CreateIndex())<0)
//错误处理
}
int CreateDb()
{
//...
}
int InitDb()
{
if((iRet=CreateDb())==0)
{
if((iRet=CreateUser())==0)
{
if((iRet=CreateTable()==0))
{
//...
return0;
}
else
{
//错误处理
return??;
}
}
else
{
//错误处理
return??
}
}
else
{
//错误处理
return??
}
}
//或者InitDb函数采用以下形式
int InitDb()
{
if((iRet=CreateDb())<0)
{
//错误处理
//return??
}
if((iRet=CreateUser())<0)
{
//错误处理
//return??
}
if((iRet=CreateTable()<0))
{
//错误处理
//return??
}
return0;
}
从上述实例代码中很容易看出:代码复杂而难以维护。但是如果使用异常处理,可简化为如下形式:
void CallFunc()
{
try
{
InitDb();
}
catch(???)
{
//异常处理
}
}
void InitDb()
{
CreateDb(); //在CreateDb()内部如果执行失败,抛出异常
CreateUser(); //在CreateUser()内部如果执行失败,抛出异常
CreateTable(); //在CreateTable()函数内部如果执行失败,抛出异常
}
C++异常处理
C++的异常处理分为两部分:
第一部分是检查程序处理情况,如果处理结果非期望的结果,则生成一个异常,并将异常抛出,抛出后,由异常的接收者处理这个异常,异常抛出的语法形式为:
throw 异常对象
throw是C++关键字,表示要抛出异常。异常对象表示异常信息,它既可以是普通数据类型的实例,也可以是类类型的对象实例。
第二部分捕捉被抛出的异常,并对异常进行处理,其形式为:
try
{
//程序执行语句,可能抛出异常。
}
catch(异常类型[,参数])
{
//处理异常
}
其中try是关键字,其后的语句块叫try块,其中的一些语句直接(比如throw表达式)或者间接(比如对包含throw表达式的函数的调用)地抛出异常。
抛出的异常只有在try语句块中才能被catch语句捕获。紧接着try块的是catch语句,用来捕获异常,catch之后的括号中含有数据类型参数,参数类型说明了希望捕获的异常的具体类型,catch中的参数类型之后的参数是可选的。如果有参数,则异常被捕获时,抛出的异常对象会初始化参数。
catch中包含参数使得在被捕获的对象信息可以在异常处理程序中被使用。catch后的语句块就是对应于被捕获的异常的处理程序。
例1
C++的异常处理基本用法演示
n 建立控制台空工程ExampleOne,并加入实现文件ExampleOne.cpp。
#include<iostream>
usingnamespace std;
classCMyError
{
public:
CMyError()
{
ErrCode=0;
strcpy(ErrInfo,"");
cout<<"CMyError()"<<endl;
}
CMyError(int code, char* pinfo)
{
ErrCode = code;
strcpy(ErrInfo,pinfo);
cout<<"CMyError()"<<endl;
}
CMyError(const CMyError &obj)
{
ErrCode = obj.ErrCode;
strcpy(ErrInfo,obj.ErrInfo);
cout<<"CMyError(const CMyError&obj)"<<endl;
}
~CMyError()
{
cout<<"~CMyError()"<<endl;
}
virtual void printErrInfo()
{
printf("%s\n",ErrInfo);
}
int ErrCode;
char ErrInfo[50];
};
classCTest
{
public:
int Func(int i)
{
try
{
if(i<10)
{
CMyError e(-2,"不能小于10");
throw e;
//throw CMyError(-2, "不能小于10"); //强制类型转换,抛出类类型实例
}
if(i>20)
{
int e;
e = -1;
throw -1; //抛出基本类型数据的实例
}
}
catch(CMyError &e)
{
e.printErrInfo();
throw e;
}
return i;
}
};
intmain(int argc, char* argv[])
{
CTest test;
int iIn;
cin>>iIn;
try
{
int x=test.Func(iIn);
cout<<"At the end of tryblock"<<endl;
}
//也可以是
//catch(CMyError)
//catch(CMyError &e)
//如果抛出来的是堆中分配的对象的指针
//此处也可以接收指针。
catch(CMyError e)
{
cout<<"catch CErrorobject"<<endl;
int b=2;
}
//也可以接收标准数据类型的实例
catch(int i)
{
cout<<"catch intvariable:"<<i<<endl;
}
return 0;
}
编译运行程序,输入小于10、大于10并小于20、大于20的整数,观察程序运行结果,并理解相关代码和注释行。
如果throw表达式或者抛出异常的子函数没在当前函数的try语句块中,或者虽然包含在try语句块中,但是catch捕捉的类型与实际抛出的类型不匹配,那系统将把异常抛给上一层次的函数,直到遇到一个能够处理改异常的函数。
例2
演示异常的连续抛出和捕捉
#include<iostream>
#include<string>
usingnamespace std;
classMyException
{
int a;
string s;
public:
MyException()
{
a=0;
s = "Error";
}
MyException(int id, string msg)
{
a=id;
s = msg;
}
void display()
{
cout<<s<<" and ErrorID ="<< a;
}
};
intmain()
{
void f1();
//f1();
try
{
f1();
}
catch(double)
{
cout<<"OK0!"<<endl;
}
cout<<"end0"<<endl;
return 0;
}
voidf1()
{
void f2();
try
{
f2();
}
catch(char)
{
cout<<"OK1!";
}
cout<<"end1"<<endl;
}
voidf2()
{
void f3();
try
{
f3();
}
catch(int)
{
cout<<"Ok2!"<<endl;
}
cout<<"end2"<<endl;
}
voidf3()
{
double a=0;
try
{
throw a;
}
catch(float)
{
cout<<"OK3!"<<endl;
}
try
{
MyException e(105,"DataError!!!");
throw e;
}
catch (MyException e)
{
e.display();
}
cout<<"end3"<<endl;
}
编译运行程序,可以看出在f3()中抛出的double型异常,一层一层往外抛出,最终在主函数中才被捕捉到。
如果直到最外层的main函数都没能捕捉到异常,则系统会自动调用函数:
void terminate();
该函数在eh.h中定义,缺省情况下,terminate()将调用abort()来终止程序,abort()终止程序的同时输出缺省的异常提示信息。
这样是有问题的! 程序员也可以通过函数set_terminate()改变最终的默认异常接收函数terminate,set_terminate()的原型为:
typedefvoid(*pfv)();
pfvset_terminate(pfv);
但是不管你设置的异常接受函数内部怎么处理,函数执行完毕后,程序结束执行。
C++多个异常的组织
一个函数可以抛出多个异常,可以在try块后放置多个catch块来捕获多个异常,如例1;
异常也可以嵌套,里层抛出的异常一层层地往外匹配catch块,直到匹配成功或者跳出main()交系统处理。
例如:
intFunc()
{
try
{
try
{
throw char(1);
}
catch(int ie)
{
cout<<"level 1catch"<<endl;
}
}
catch(char ce)
{
cout<<"level 2catch"<<endl;
}
return 0;
}
里层的异常将被外层的catch捕获。
异常被捕获后,仍可将异常再次抛出以期待被外层的catch捕获。再次抛出异常用关键字throw即可,不必加上再次抛出的异常名,实际上,它隐含地认为抛出的是当前捕获的异常对象。
例如:
voidFunc2()
{
try
{
int i=9;
throw i;
}
catch(int ie)
{
cout<<"Func2()catch"<<endl;
throw;
}
}
voidFunc()
{
try
{
Func2();
}
catch(int ie)
{
cout<<"Func()catch"<<endl;
}
}
intmain(int argc, char* argv[])
{
Func();
return 0;
}
对于一个函数可以抛出的多种异常我们可以通过以下两种方法来进行组织和管理。
第一种方法就是利用枚举来组织异常。
例3
利用枚举来组织管理异常演示
#include<iostream>
usingnamespace std;
enumENUM_ERROR{StackOverflow,NullPointer,ZeroDevide,OtherError};
voidFunc(int i)
{
try{
if(i%3==0)
throw StackOverflow;
if(i%4==0)
throw NullPointer;
if(i%5==0)
throw ZeroDevide;
}
catch (int OtherError)
{
throw OtherError;
}
}
intmain(int argc, char* argv[])
{
int i;
cin>>i;
try
{
Func(i);
}
catch(ENUM_ERROR e)
{
switch(e)
{
case StackOverflow:
cout<<"StackOverflow"<<endl;
break;
case NullPointer:
cout<<"NullPointer"<<endl;
break;
case ZeroDevide:
cout<<"ZeroDevide"<<endl;
break;
case OtherError:
cout<<"ZeroDevide"<<endl;
break;
}
}
catch(...)
{
cout<<"Unknownexception!"<<endl;
}
return 0;
}
编译运行程序,输入3、4、5的倍数模拟产生不同的异常,并用枚举管理他们,对于捕捉到的枚举型异常使用switch进行分支处理。
第一种方法就是利用虚函数来组织异常。利用虚函数组织异常可以使得在增加新的异常时不必修改源程序,同时又能按具体的异常对象自动选择合适的异常处理程序。
只要把异常类中与异常处理有关的函数声明为虚函数,并把catch()中的类型参数设定为基类的引用或者指针就行了。这样,在异常处理中只要统一按照对基类异常的处理就行了。
例4
利用虚函数来组织管理异常演示
#include <iostream>
using namespace std;
class CErrorBase
{
public:
virtualvoid PrintError()
{
cout<<"CErrorBase"<<endl;
}
};
class CErrorOverflow:publicCErrorBase
{
public:
virtualvoid PrintError()
{
cout<<"CErrorOverflow"<<endl;
}
};
class CErrorNullPointer:publicCErrorBase
{
public:
virtualvoid PrintError()
{
cout<<"CErrorNullPointer"<<endl;
}
};
class CErrorZeroDevide:public CErrorBase
{
public:
virtualvoid PrintError()
{
cout<<"CErrorZeroDevide"<<endl;
}
};
void Func(int i)
{
if(i%3==0)
throwCErrorOverflow();
if(i%4==0)
throwCErrorNullPointer();
if(i%5==0)
throwCErrorZeroDevide();
}
int main(int argc, char* argv[])
{
inti;
cin>>i;
try
{
Func(i);
}
catch(CErrorBase&e)
{
e.PrintError();
}
return0;
}
这样一来就使得对异常的处理变得简单而统一,事实上,MFC的异常处理就是以此为基础的。
C++异常接口说明
一个函数往往要抛出异常对象,函数的使用者应当了解函数所抛出的异常,从而设置catch语句捕获异常对象,并对捕获的异常对象进行处理,但是,除非看到函数的源代码,否则函数的使用者无法从函数的原型声明中知道函数可能抛出的异常。
C++为此提供了异常的接口说明,它在函数说明中就列出了函数可能抛出的异常,因为不必了解函数代码就可以知道函数与哪些异常有关。异常接口说明是函数声明的一部分,它紧跟在普通的函数声明之后。异常接口说明的一般形式为:
返回类型 函数名(参数列表) throw(异常类型1,异常类型2,,,);
throw后扩后内的内容是此函数可能抛出的所有异常的集合。各类型间用逗号隔开。例如:
void func(int i) throw(char,int);
说明函数func只可能抛出char类型和int类新的异常对象,而不会再抛出其他异常。
如果一个函数声明中没有异常说明列表,那么隐含地表示函数可以抛出任何类型的异常。例如:
int Func(int i);
表示Func函数可能会抛出各种类型的异常。
如果规定函数不会抛出任何异常,则应该设置函数声明的异常接口为空,例如:
int Func(int i) throw();
说明Func函数不会抛出任何异常。
MFC异常处理
MFC异常的语法和语义是构建在标准C++异常的语法和语义的基础上的,是用宏的方式对标准C++的异常进行包装。
MFC异常类体系是以虚函数方式组织的。CException是所有MFC异常类的基类,所有名字为CXXXException形式的类都是从抽象类CException派生的,如下表所示。
异常类 | 含义 |
CMemoryException | 内存不足 |
CFileException | 文件异常 |
CArchiveException | 存档/序列化异常 |
CNotSupportedException | 响应对不支持服务的请求 |
CResourceException Windows | 资源分配异常 |
CDaoException | 数据库异常(DAO 类) |
CDBException | 数据库异常(ODBC 类) |
COleException | OLE 异常 |
COleDispatchException | 调度(自动化)异常 |
CUserException | 用消息框警告用户然后引发一般 CException 的异常 |
MFC则定义了一组宏:
TRY
CATCH,AND_CATCH, 和END_CATCH
THROW和 THROW_LAST (最近的异常)
这些宏非常象C++的异常关键字try、catch和throw。
对于每个MFC异常类CXXXException,都有一个全局的辅助函数AfxThrowXXXException() ,它构造、初始化和抛出这个类的对象。你可以用这些辅助函数处理预定义的异常类型,用THROW处理自定义的对象(当然,它们必须是从CException派生的)。
基本的设计原则是:
n 用TRY块包含可能产生异常的代码。
n 用CATCH检测并处理异常。
异常处理函数并不是真的捕获对象,它们其实是捕获了指向异常对象的指针。MFC靠动态类型来辨别异常对象。可以在一个TRY块上捆绑多个异常处理函数,每个捕获一个C++静态类型的不同的对象。第一个处理函数使用宏CATCH,以后的使用AND_CATCH,用END_CATCH结束处理函数队列。
MFC自己可能触发异常,你也可以显式触发异常(通过THROW或MFC辅助函数)。在异常处理函数内部,可以用THROW_LAST再次抛出最近一次捕获的异常。
例5
MFC内设异常类使用演示
n 建立控制台空工程ExampleFive,并加入实现文件ExampleFive.cpp。
void Func2()
{
TRY
{
printf("raisingmemory exception\n");
AfxThrowMemoryException();
printf("thisline should never appear\n");
}
CATCH(CException,pe)//!!!注意,抛出的是对象的指针
{
printf("caughtgeneric exception; rethrowing\n");
THROW_LAST(); //重新抛出必须用THROW_LAST();而不是THROW()
printf("thisline should never appear\n");
}
END_CATCH
printf("thisline should never appear\n");
}
void Func3()
{
TRY
{
Func2();
printf("thisline should never appear\n");
}
CATCH(CFileException,pe)
{
printf("caughtfile exception\n");
}
AND_CATCH(CMemoryException,pe)
{
printf("caughtmemory exception\n");
}
AND_CATCH(CException,pe) //捕获剩下的MFC异常类对象
{
printf("caughtgeneric exception\n");
pe->ReportError();
}
END_CATCH
}
int main()
{
AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0);
Func3();
return0;
}
编译出现错误,不认识TRY宏,以及函数AfxWinInit和类CException,需要设置以动态连接库的方式使用MFC,并且包含头文件afx.h,afxdb.h。
#include <afx.h>
#include <afxdb.h>
编译运行程序,体验MFC异常处理宏和异常处理类的用法。
例6
MFC异常处理基本类的使用演示
n 使用例5的方法建立工程,假如一下代码并进行使用MFC的设置。
#include<afx.h>
#include <afxdb.h>
class CMyException:public CException
{
public:
virtualBOOL GetErrorMessage(LPTSTR lpszError, UINT nMaxError,PUINT pnHelpContext)
{
ASSERT(lpszError!= NULL && AfxIsValidString(lpszError, nMaxError));
charszError[]="我的错误处理信息!";
strncpy(lpszError,szError,nMaxError-1);
lpszError[nMaxError-1]=0;
returnTRUE;
}
};
void Func()
{
THROW(newCMyException);
}
int _tmain(int argc, TCHAR* argv[],TCHAR* envp[])
{
AfxWinInit(::GetModuleHandle(NULL),NULL, ::GetCommandLine(), 0);
TRY
{
Func();
}
CATCH(CException,pe)
{
pe->ReportError();
}
END_CATCH
return0;
}
编译并运行程序,理解MFC异常类和抛出并捕捉的用法。
名字空间
名字空间目的是进行名字范围(作用域)的控制
C语言的命名控制
通过static进行命名控制,它有两种含义:
n 在固定地址上分配(静态数据区),而不是在堆栈上分配
n 对一个给定的编译单位来说是本地的。名字范围是本地的
函数内部的静态变量-局部静态变量
char OneChar(const char* string =0)
{
staticconst char* s;
if(string)
{
S= string;
return*s;
}
else
return0;
}
变量s内容在静态区中分配,且仅在函数范围内可见。
函数体内的静态对象也是一个道理。其构造函数在首次分配时调用,函数退出并不调用析构,南昌是在程序退出时调用。
变量的范围控制(连接控制)
所有的全局对象都是隐含为静态存储的。
int a = 0;
则a存于静态区。其命名范围如何?
n 默认为全局范围, 其它文件要引用,则extern int a;
n 如定义成: static Int a = 0; 则static仅仅改变了可见性。
上述规则对于函数(可理解了一个名字)也是一样的。
命名空间的目标和定义
C中名字可以嵌套,但全局函数,全局变量及类名字还是在一个范围内,即全局名字空间中。
static可以让变量或函数成为本地可见(文件内可见),但不能解决大程序中全局空间的名字冲突问题。
C++中的命名空间可以将唯一的全局空间层次化。
定义语法:
namespace MYName
{
全局变量定义
全局函数定义,
全局类定义,结构,。。。。
}
namespace与class,struct,union,enum的区别:
n Namespace只能在全局范围内定义,目的是对全局空间层次化,可隐含一些全局名字。它们可以组合和嵌套。
n Namespace的结尾没有。
n 一个Namespace可以在多个头文件中用同一个Namespace名字定义,就象重复定义一个类一样。
但同一空间中不能出现同名名字。
//one.h
namespace MYName
{
全局变量定义
全局函数定义,
全局类定义,结构,。。。。
}
//TWO.h
namespace MYName
{
全局变量定义
全局函数定义,
全局类定义,结构,。。。。
}
n 一个Namespace名字可以用另一个名字做其别名,这可以简化原名字的长度。
namespace chinaShanxiJinchengMYName
{
全局变量定义
全局函数定义,
全局类定义,结构,。。。。
}
namespace MYName =chinaShanxiJinchengMYName;
n 不能象类一样,去建立一个Namespace名字空间的实例。
未命名的命名空间--命名空间后没有定义名字
在同一个编译单元内(文件),这个无名空间无限制有效,但只能有一个。相当于该空间的所有名字都加了static
命名空间的使用
有两个方法可引用命名空间的名字:
n 范围分解---用范围分解运算符来引用名字
namespace MYName
{
classX{
staticint I;
public:
voidf();
};
int a = 0;
classZ;
voidfoo();
}
intMYName::X::i = 9;
MYName::a= 10;
classMYName::Z
{
…..
};
n using指令
namespace MYName
{
classX{
staticint I;
public:
voidf();
};
int a = 0;
classZ;
voidfoo();
}
//使用
voidtest()
{
usingnamespace MYName;
a= 10;
}
如果不用using指令,则引用a时就得用完全范围限定。
例12
名字空间的定义和使用演示
n 建立控制台空工程ExampleTwelve,并依次加入一下文件(一次只能加一个):
ExampleTwelvea.cpp
//自定义一个名字空间NS1,对自定义名字空间成员的
//定义类似于全局名字空间的成员定义
namespace NS1
{
inti;
}
//名字空间可以分多处定义,此处在名字空间NS1中添加新的定义
namespace NS1
{
//在名字空间中可以定义类型、变量、函数等,如果定义变量
//该变量的的实例化时机可以认为等同于全局变量
intj;
//在同一名字空间中不可以有重复的定义,但是在不同的名字空间
//可以,比如在a.cpp中有全局名字空间的类型CA的定义,在名字空间
//NS1照样可以定义
classCA
{
public:
CA();
};
}
//对名字空间内部的成员的使用,
//应该遵循要么引入名字空间,要么用名字空间加作用域解析符的方式
NS1::CA::CA()
{}
using namespace NS1;
//引入多个名字空间,则要注意避免名字冲突的问题
namespace NS2
{
intj=2;
}
using namespace NS2;
int main(void)
{
i=9; //i的使用没问题
//j=3; //j的使用导致了名字冲突
//NS1::i=9; //对于上一句的情况,只能采用作用域解析符的方式
//NS1::j=10;
return0;
}
ExampleTwelveb.cpp
#include <iostream>
/*对cout的定义可以解释为在名字空间
std中添加一个全局变量的定义:
*/
namespace std
{
//ostreamcout(???);
}
/*在全局对象的构造函数中要慎用cout等全局对象,因为可能 自定义的全局对象先于cout全局对象实例化出来。
class CB
{
public:
CB()
{
std::cout<<"CB();"<<std::endl;
}
};
CB g_ob;
*/
int main(void)
{
//对cout有两种使用方式:
//1:名字空间作用域再跟上cout
std::cout<<"abc"<<std::endl;
//2:把整个名字空间的所有成员全部引入
//这样就可以任意使用std中定义的成员了
usingnamespace std;
cout<<"xyz"<<endl;
return0;
}
ExampleTwelvec.cpp
#include <windows.h>
//在程序有一个缺省的全局的名字空间,它是一个无名的空间
//在全局空间添加变量定义
int g_iRef=0;
//在全局空间添加自定义数据类型CA,
//同时CA又是全局名字空间中划出的一个类类的子名字空间
class CA
{
private:
intm_i;
public:
voidMyFunc()
{
}
//在全局名字空间的CA名字空间内部添加NODE名字空间
//NODE
structNODE
{
intdata;
NODE*pNextNode;
};
//在类名字空间CA内部定义函数CA
CA()
{
for(inti=0;i<2;i++)
{ //在循环体空间内定义变量
intj=i*2;
}
}
};
//在全局名字空间定义函数Func,即定义全局函数
void Func()
{
//类名字空间的作用域已经覆盖到此处,所以此处可以使用CA
CAa;
//CA内部的名字空间必须显示地加上路径才能访问
CA::NODEnode;
node.data=0;
}
int main()
{
intiLocal;
//对于全局名字空间的类型定义、函数定义、变量定义等,可以直接使用
Func();
//对于全局名字空间的类型定义、函数定义、变量定义等,也可以
//显式地加上无名的作用域解析符
::Func();
//所有的WINAPI函数都是在全局空间定义,都是全局函数
::GetCurrentProcessId();
::g_iRef++;
return0;
}
依次编译运行程序,理解名字空间的定义和使用方法,同时留意代码中的注释行。
Win32结构化异常处理(SEH)
Windows 95、Windows98 和 Windows 2000(即以前的 Windows NT)支持一种称为结构化异常处理的可靠的异常处理方法,此方法涉及与操作系统的协作,并且在编程语言中具有直接支持。
"SEH异常"是意外的或是使进程不能正常进行的事件。硬件和软件都可以检测出异常。硬件异常包括被零除和数值类型溢出等。软件异常包括通过调用RaiseException 函数检测到并发出信号通知系统的情况,以及由Windows 检测到的特殊情况。
可以使用结构化异常处理编写更可靠的代码。可以确保资源(如内存块和文件)在发生意外终止事件时正常关闭。还可以利用简洁的、不依靠 goto 语句或详细测试返回代码的结构化代码来处理具体问题(如内存不足)。
注意:结构化异常处理文章描述 C 编程语言的结构化异常处理。虽然结构化异常处理也可用于 C++,但对于 C++ 程序应使用 C++ 异常处理。
SEH是系统一级的异常处理,当建立标准C++异常处理时,编译器最终将它翻译成SEH异常处理。
SEH实际包含两个主要功能,结束处理和异常处理。分别通过__try{}__finally{}结构和__try{}__except(){}结构实现。
n __try{}__finally{}结构
此结构能够确保__try{}块内的代码执行完毕后,进入__finally{}块中执行一些清理工作。
例7
_try{}__finally{}结构使用演示
n 建立控制台工程ExampleSeven,并加入一下代码ExampleSeven.cpp。
#include<stdio.h>
int main()
{
FILE*f=NULL;
__try
{
f=fopen("abc.txt","wb");
//执行一些文件处理工作
if(fwrite("aaa",1,3,f)!=3)
return-1;
inti=100;
if(fwrite(&i,sizeof(i),1,f)!=1)
return-2;
return0;
}
__finally
{
if(f)
fclose(f);
}
return0;
}
以上程序,能够确保文件操作完毕后,在函数返回之前进入__finally块执行文件的关闭动作。
__leave关键字
__try块的__leave语句能够使程序立即跳出__try块,进入到__finally块中执行。
例8
n __try{}__finally{}中__leave关键字结构使用演示
#include<stdio.h>
int main()
{
FILE*f=NULL;
__try{
f=fopen("abc.txt","wb");
//执行一些文件处理工作
if(fwrite("aaa",1,3,f)!=3)
__leave;
inti=100;
if(fwrite(&i,sizeof(i),1,f)!=2)
__leave;
//...
}
__finally{
if(f)
fclose(f);
}
return0;
}
编译运行程序,__leave关键字作用类似于break。
n __try{}__except(){}结构
__try{}__except(){}是SEH的异常处理结构,它一般用来捕捉系统一级的硬件异常,当然也可以捕捉由调用RaiseException()函数引发的软件异常,它的一般形式为:
__try
{
//程序执行块
}
__except(表达式或函数调用)
{
//异常处理程序
}
当__try语句块中发生异常时,将转入执行__except(表达式)括号内的表达式或函数调用,表达式或函数的返回值的值只能是如下三个标识符之一,这些标识符在excpt.h中定义。
标识符EXCEPTION_EXECUTE_HANDLER 定义为1
标识符EXCEPTION_CONTINUE_SEARCH 定义为0
标识符EXCEPTION_CONTINUE_EXECUTION 定义为-1
EXCEPTION_EXECUTE_HANDLER返回值
EXCEPTION_EXECUTE_HANDLER返回值将使程序进入__except语句块中执行异常处理程序,__except语句块执行完毕后,接着执行__except语句块后的语句。
例9
__try{}__except(){}结构EXCEPTION_EXECUTE_HANDLER使用演示。
#include <stdio.h>
#include <excpt.h>
#include <windows.h>
int Filter(DWORDdwError,_EXCEPTION_POINTERS *pep)
{
//用第一个参数获取ExceptionCode
if(dwError==EXCEPTION_ACCESS_VIOLATION)
printf("Exception_Access_Violation\n");
else
printf("UnknownError:%d\n",dwError);
//用第二个参数获取ExceptionCode
printf("Code=%08X,Address=%08X\n",
pep->ExceptionRecord->ExceptionCode,
pep->ExceptionRecord->ExceptionAddress);
returnEXCEPTION_EXECUTE_HANDLER;
}
int Func()
{
int*pi=NULL;
__try{
*pi=10;
//注意,GetExceptionCode()只能在__except()中调用。
}__except(Filter(GetExceptionCode(),GetExceptionInformation())){
printf("__exceptstatement\n");
}
printf("after__except statement\n");
return0;
}
int main(int argc, char* argv[])
{
inti=Func();
return0;
}
EXCEPTION_CONTINUE_EXECUTION 返回值
该返回值指示程序重新执行发生异常的语句(VC++上调试不能通过,暂搁)。
EXCEPTION_CONTINUE_SEARCH返回值
该返回值类似于标准C++的重新抛出异常。
例10
__try{}__except(){}结构EXCEPTION_CONTINUE_SEARCH使用演示。
#include <stdio.h>
#include <excpt.h>
#include <windows.h>
int Filter(DWORDdwError,_EXCEPTION_POINTERS *pep)
{
//用第一个参数获取ExceptionCode
if(dwError==EXCEPTION_ACCESS_VIOLATION)
printf("Exception_Access_Violation\n");
else
printf("UnknownError:%d\n",dwError);
//用第二个参数获取ExceptionCode
printf("Code=%08X,Address=%08X\n",
pep->ExceptionRecord->ExceptionCode,
pep->ExceptionRecord->ExceptionAddress);
returnEXCEPTION_CONTINUE_SEARCH;
}
int Func()
{
int*pi=NULL;
__try{
*pi=10;
//注意,GetExceptionCode()只能在__except()中调用。
}__except(Filter(GetExceptionCode(),GetExceptionInformation())){
//!!!此句将不会执行
printf("__exceptstatement\n");
}
printf("after__except statement\n");
return0;
}
int main(int argc, char* argv[])
{
__try{
inti=Func();
}__except(EXCEPTION_EXECUTE_HANDLER){
printf("catchexception in main()\n");
}
return0;
}
SEH到标准C++异常处理的转换
可以通过SEH到标准C++的转换,使catch语句能够识别操作系统的硬件异常代码。
例11
SEH到标准C++异常处理的转换演示
建立控制台空工程ExampleEleven,并加入一下代码ExampleEleven。
#include <stdio.h>
#include <excpt.h>
#include <windows.h>
#include <eh.h>
class CSE{
public:
staticvoid MapSE2CE()
{
_set_se_translator(TranslateSE2CE);
}
operatorDWORD()
{
returnm_er.ExceptionCode;
}
private:
CSE(PEXCEPTION_POINTERSpep)
{
m_er=*pep->ExceptionRecord;
m_context=*pep->ContextRecord;
}
staticvoid _cdecl TranslateSE2CE(UINT dwEC,PEXCEPTION_POINTERS pep)
{
throwCSE(pep);
}
private:
EXCEPTION_RECORDm_er;
CONTEXT m_context;
};
int main(int argc, char* argv[])
{
//注意,应该在每个线程的入口点调用该静态成员函数。
CSE::MapSE2CE();
try
{
int*pi=NULL;
*pi=0;
intx=0;
x=5/x;
}
catch(CSEse)
{
switch(se)
{
caseEXCEPTION_ACCESS_VIOLATION:
//...
break;
caseEXCEPTION_INT_DIVIDE_BY_ZERO:
//...
break;
default:
throw;
break;
}
}
return0;
}
更多推荐
C++知识文档十一_异常_命名空间
发布评论