C语⾔中的异常处理
⼀前⾔:
异常处理,对于做⾯向对象开发的开发者来说是再熟悉不过了,例如在C#中有
try
{
...
}
catch( Exception e){...}
finally{
.....
}
在C++中,我们常常会使⽤
try{}
...
catch(){}
块来进⾏异常处理。
说了那么多,那么到底什么是异常处理呢?
异常处理(⼜称为错误处理)功能提供了处理程序运⾏时出现的任何意外或异常情况的⽅法。
异常处理⼀般有两种模型,⼀种是"终⽌模型",⼀种是"恢复模型"
"终⽌模型":在这种模型中,将假设错误⾮常关键,将以致于程序⽆法返回到异常发⽣的地⽅继续执⾏.⼀旦异常被抛出,就表明错误已⽆法挽回,也不能回来继续执⾏.
"恢复模型":异常处理程序的⼯作是修正错误,然后重新尝试调动出问题的⽅法,并认为的⼆次能成功. 对于恢复模型,通常希望异常被处理之后能继续执⾏程序.在这种情况下,抛出异常更像是对⽅法
的调⽤--可以在Java⾥⽤这种⽅法进⾏配置,以得到类似恢复的⾏为.(也就是说,不是抛出异常,⽽是调⽤⽅法修正错误.)或者,把try块放在while循环⾥,这样就可以不断的进⼊try块,直到得到满意的结果.
⼆⾯向对象中的异常处理
⼤致了解了什么是异常处理后,由于异常处理在⾯向对象语⾔中使⽤的⽐较普遍,我们就先以C++为例,做⼀个关于异常处理的简单例⼦:问题:求两个数相除的结果。
这⾥,隐藏这⼀个错误,那就是当除数为0时,会出现,所以,我们得使⽤异常处理来捕捉这个异常,并抛出异常信息。
具体看代码:
1 #include <iostream>
2 #include <exception>
3using namespace std;
4class DivideError:public exception
5 {
6public:
7          DivideError::DivideError():exception(){}
8const char* what(){
9return"试图去除⼀个值为0的数字";
10        }
11
12 };
13double quotion(int numerator,int denominator)
14 {
15if(0==denominator)          //当除数为0时,抛出异常
16throw DivideError();
17return static_cast<double>(numerator)/denominator;
18 }
19int main()
20 {
21int number1;            //第⼀个数字
22int number2;            //第⼆个数字
23double result;
24    cout<<"请输⼊两个数字:" ;
25while(cin>>number1>>number2){
26try{
27            result=quotion(number1,number2);
28            cout<<"结果是 :"<<result<<endl;
29
30        }    //end try
31catch(DivideError &divException){
unknown怎么处理32            cout<<"产⽣异常:"
33                <<divException.what()<<endl;
34        }
35    }
36
37 }
38
在这个例⼦中,我们使⽤了<expection>头⽂件中的exception类,并使DivideError类继承了它,同时重载了虚⽅法what(),以给出特定的异常信息。
⽽C#中的异常处理类则封装的更有全⾯,⾥⾯封装了常⽤的异常处理信息,这⾥就不多说了。
三 C语⾔中的异常处理
在C语⾔中异常处理⼀般有这么⼏种⽅式:
1.使⽤标准C库提供了abort()和exit()两个函数,它们可以强⾏终⽌程序的运⾏,其声明处于<stdlib.h>头⽂件中。
2.使⽤assert(断⾔)宏调⽤,位于头⽂件<assert.h>中,当程序出错时,就会引发⼀个abort()。
3.使⽤errno全局变量,由C运⾏时库函数提供,位于头⽂件<errno.h>中。
4.使⽤goto语句,当出错时跳转。
5.使⽤setjmp,longjmp进⾏异常处理。
接下来,我们就依次对这⼏种⽅式来看看到底是怎么做的:
我们仍旧以前⾯处理除数为0的异常为例⼦。
1.使⽤exit()函数进⾏异常终⽌:
1 #include <stdio.h>
2 #include <stdlib.h>
3double diva(double num1,double num2)        //两数相除函数
4 {
5double re;
6    re=num1/num2;
7return re;
8 }
9int main()
10 {
11double a,b,result;
12  printf("请输⼊第⼀个数字:");
13  scanf("%lf",&a);
14  printf("请输⼊第⼆个数字:");
15  scanf("%lf",&b);
16if(0==b)                                //如果除数为0终⽌程序
17  exit(EXIT_FAILURE);
18 result=diva(a,b);
19    printf("相除的结果是: %.2lf\n",result);
20return0;
21 }
其中exit的定义如下:
_CRTIMP void __cdecl __MINGW_NOTHROW exit (int) __MINGW_ATTRIB_NORETURN;
exit的函数原型:void exit(int)由此,我们也可以知道EXIT_FAILURE宏应该是⼀个整数,exit()函数的传递参数是两个宏,⼀个是刚才看到的EXIT_FAILURE,还有⼀个是EXIT_SUCCESS从字⾯就可以看出⼀个是出错后强制终⽌程序,⽽⼀个是程序正常结束。他们的定义是:
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
到此,当出现异常的时候,程序是终⽌了,但是我们并没有捕获到异常信息,要捕获异常信息,我们可以使⽤注册终⽌函数atexit(),它的原型是这样的:int atexit(atexit_t func);
具体看如下程序:
1 #include <stdio.h>
2 #include <stdlib.h>
3void Exception(void)                          //注册终⽌函数,通过挂接到此函数,捕获异常信息
4 {
5    printf("试图去除以⼀个为0的数字,出现异常!\n");
6 }
7int main()
8 {
9double a,b,result;
10  printf("请输⼊第⼀个数字:");
11  scanf("%lf",&a);
12  printf("请输⼊第⼆个数字:");
13  scanf("%lf",&b);
14if(0==b)                    //如果除数为0终⽌程序 ,并挂接到模拟异常捕获的注册函数
15  {
16
17  atexit(Exception);
18  exit(EXIT_FAILURE);
19  }
20    result=diva(a,b);
21    printf("相除的结果是: %.2lf\n",result);
22return0;
23 }
这⾥需要注意的是,atexit()函数总是被执⾏的,就算没有exit()函数,当程序结束时也会被执⾏。并且,可以挂接多个注册函数,按照堆栈结构进⾏执⾏。abort()函数与exit()函数类似,当出错时,能使得程序正常退出,这⾥就不多说了。
2.使⽤assert()进⾏异常处理:
assert()是⼀个调试程序时经常使⽤的宏,切记,它不是⼀个函数,在程序运⾏时它计算括号内的表达式,如果表达式为FALSE  (0),  程序将报告错误,并终⽌执⾏。如果表达式不为0,则继续执⾏后⾯的语句。这个宏通常原来判断程序中是否出现了明显⾮法的数据,如果出现了终⽌程序以免导致严重后果,同时也便于查错误。
另外需要注意的是:assert只有在Debug版本中才有效,如果编译为Release版本则被忽略。
我们就前⾯的问题,使⽤assert断⾔进⾏异常终⽌操作:构造可能出现出错的断⾔表达式:assert(number!=0)这样,当除数为0的时候,表达式就为false,程序报告错误,并终⽌执⾏。
代码如下:
代码
#include <stdio.h>
#include <assert.h>
double diva(double num1,double num2)        //两数相除函数
{
double re;
re=num1/num2;
return re;
}
int main()
{
printf("请输⼊第⼀个数字:");
scanf("%lf",&a);
printf("请输⼊第⼆个数字:");
scanf("%lf",&b);
assert(0!=b);                                //构造断⾔表达式,捕获预期异常错误
result=diva(a,b);
printf("相除的结果是: %.2lf\n",result);
return0;
}
3.使⽤errno全局变量,进⾏异常处理:
errno全局变量主要在调式中,当系统API函数发⽣异常的时候,将errno变量赋予⼀个整数值,根据查看这个值来推测出错的原因。其中的各个整数值都有⼀个相应的宏定义,表⽰不同的异常原因:
代码
#define EPERM        1    /* Operation not permitted */
#define    ENOFILE        2    /* No such file or directory */
#define    ENOENT        2
#define    ESRCH        3    /* No such process */
#define    EINTR        4    /* Interrupted function call */
#define    EIO        5    /* Input/output error */
#define    ENXIO        6    /* No such device or address */
#define    E2BIG        7    /* Arg list too long */
#define    ENOEXEC        8    /* Exec format error */
#define    EBADF        9    /* Bad file descriptor */
#define    ECHILD        10    /* No child processes */
#define    EAGAIN        11    /* Resource temporarily unavailable */
#define    ENOMEM        12    /* Not enough space */
#define    EACCES        13    /* Permission denied */
#define    EFAULT        14    /* Bad address */
/* 15 - Unknown Error */
#define    EBUSY        16    /* strerror reports "Resource device" */
#define    EEXIST        17    /* File exists */
#define    EXDEV        18    /* Improper link (cross-device link?) */
#define    ENODEV        19    /* No such device */
#define    ENOTDIR        20    /* Not a directory */
#define    EISDIR        21    /* Is a directory */
#define    EINVAL        22    /* Invalid argument */
#define    ENFILE        23    /* Too many open files in system */
#define    EMFILE        24    /* Too many open files */
#define    ENOTTY        25    /* Inappropriate I/O control operation */
/* 26 - Unknown Error */
#define    EFBIG        27    /* File too large */
#define    ENOSPC        28    /* No space left on device */
#define    ESPIPE        29    /* Invalid seek (seek on a pipe?) */
#define    EROFS        30    /* Read-only file system */
#define    EMLINK        31    /* Too many links */
#define    EPIPE        32    /* Broken pipe */
#define    EDOM        33    /* Domain error (math functions) */
#define    ERANGE        34    /* Result too large (possibly too small) */
/* 35 - Unknown Error */
#define    EDEADLOCK    36    /* Resource deadlock avoided (non-Cyg) */
#define    EDEADLK        36
/* 37 - Unknown Error */
#define    ENAMETOOLONG    38    /* Filename too long (91 in Cyg?) */
#define    ENOLCK        39    /* No locks available (46 in Cyg?) */
#define    ENOSYS        40    /* Function not implemented (88 in Cyg?) */
#define    ENOTEMPTY    41    /* Directory not empty (90 in Cyg?) */
#define    EILSEQ        42    /* Illegal byte sequence */
这⾥我们就不以前⾯的除数为0的例⼦来进⾏异常处理了,因为我不知道如何定义⾃⼰特定错误的errno,如果哪位知道,希望能给出⽅法。我以⼀个⽹上的例⼦来说明它的使⽤⽅法:
代码
#include <errno.h>
#include <math.h>
#include <stdio.h>
int main(void)
{
errno = 0;
if (NULL == fopen("d:\\1.txt", "rb"))
{
printf("%d", errno);
}
else
{
printf("%d", errno);
}
return0;  }
这⾥试图打开⼀个d盘的⽂件,如果⽂件不存在,这是查看errno的值,结果是2、
当⽂件存在时,errno的值为初始值0。然后查看值为2的错误信息,在宏定义那边#define    ENOFILE        2    /* No such file or directory */便知道错误的原因了。
4.使⽤goto语句进⾏异常处理:
goto语句相信⼤家都很熟悉,是⼀个跳转语句,我们还是以除数为0的例⼦,来构造⼀个异常处理的例⼦:
代码
#include <stdio.h>
double diva(double num1,double num2)        //两数相除函数
{
double re;
re=num1/num2;
return re;
}
int main()
{
int tag=0;
double a,b,result;
if(1==tag)
{
Throw:
printf("除数为0,出现异常\n");
}
tag=1;
printf("请输⼊第⼀个数字:");
scanf("%lf",&a);
printf("请输⼊第⼆个数字:");
scanf("%lf",&b);
if(b==0)                                  //捕获异常(或许这么说并不恰当,暂且这么理解)
goto Throw;                                //抛出异常
result=diva(a,b);
printf("%d\n",errno);
printf("相除的结果是: %.2lf\n",result);
return0;
}
5.使⽤setjmp和longjmp进⾏异常捕获与处理:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。