C语言中的异常处理

一 前言:

异常处理,对于做面向对象开发的开发者来说是再熟悉不过了,例如在C#中有

try

{

     ...

}

catch( Exception e){...}

finally{

.....

}

在C++中,我们常常会使用

try{}

...

catch(){}

块来进行异常处理。

说了那么多,那么到底什么是异常处理呢?

异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常情况的方法。

异常处理一般有两种模型,一种是"终止模型",一种是"恢复模型"

"终止模型":在这种模型中,将假设错误非常关键,将以致于程序无法返回到异常发生的地方继续执行.一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行.

"恢复模型":异常处理程序的工作是修正错误,然后重新尝试调动出问题的方法,并认为的二次能成功. 对于恢复模型,通常希望异常被处理之后能继续执行程序.在这种情况下,抛出异常更像是对方法的调用--可以在Java里用这种方法进行配置,以得到类似恢复的行为.(也就是说,不是抛出异常,而是调用方法修正错误.)或者,把try块放在while循环里,这样就可以不断的进入try块,直到得到满意的结果.


二 面向对象中的异常处理

大致了解了什么是异常处理后,由于异常处理在面向对象语言中使用的比较普遍,我们就先以C++为例,做一个关于异常处理的简单例子:

问题:求两个数相除的结果。

这里,隐藏这一个错误,那就是当除数为0时,会出现,所以,我们得使用异常处理来捕捉这个异常,并抛出异常信息。

具体看代码:

 

复制代码
1 #include < iostream > 2 #include < exception > 3 using namespace std; 4 class DivideError: public exception 5 { 6 public : 7           DivideError::DivideError():exception(){} 8          const char * what(){ 9             return " 试图去除一个值为0的数字 " ; 10         } 11 12 }; 13 double quotion( int numerator, int denominator) 14 { 15     if ( 0 == denominator)          // 当除数为0时,抛出异常 16     throw DivideError(); 17     return static_cast < double > (numerator) / denominator;    18 } 19 int main() 20 { 21     int number1;             // 第一个数字 22     int number2;             // 第二个数字 23     double result; 24     cout << " 请输入两个数字: " ; 25     while (cin >> number1 >> number2){ 26         try { 27             result = quotion(number1,number2); 28             cout << " 结果是 : " << result << endl; 29             30         }     // end try 31         catch (DivideError & divException){ 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 > 3 double diva( double num1, double num2)         // 两数相除函数 4 { 5     double re; 6     re = num1 / num2; 7     return re; 8 } 9 int main() 10 { 11    double a,b,result; 12 printf( " 请输入第一个数字: " ); 13   scanf( " %lf " , & a); 14   printf( " 请输入第二个数字: " ); 15   scanf( " %lf " , & b); 16   if ( 0 == b)                                // 如果除数为0终止程序 17   exit(EXIT_FAILURE); 18 result = diva(a,b); 19    printf( " 相除的结果是: %.2lf\n " ,result);    20 return 0 ; 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 > 3 void Exception( void )                           // 注册终止函数,通过挂接到此函数,捕获异常信息 4 { 5     printf( " 试图去除以一个为0的数字,出现异常!\n " ); 6 } 7 int main() 8 { 9    double a,b,result; 10   printf( " 请输入第一个数字: " ); 11   scanf( " %lf " , & a); 12   printf( " 请输入第二个数字: " ); 13   scanf( " %lf " , & b); 14   if ( 0 == b)                    // 如果除数为0终止程序 ,并挂接到模拟异常捕获的注册函数 15   { 16       17   atexit(Exception);                          18   exit(EXIT_FAILURE); 19   } 20    result = diva(a,b); 21    printf( " 相除的结果是: %.2lf\n " ,result);    22 return 0 ; 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);       return 0 ; }
复制代码

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);  }  return 0 ;  }  

这里试图打开一个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);   
return 0 ; }
复制代码

5.使用setjmp和longjmp进行异常捕获与处理:

setjmp和longjmp是非局部跳转,类似goto跳转作用,但是goto语句具有局限性,只能在局部进行跳转,当需要跳转到非一个函数内的地方时就需要用到setjmp和longjmp。setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。异常处理基本方法:

使用setjmp设置一个跳转点,然后在程序其他地方调用longjmp跳转到该点(抛出异常).

代码如下所示:

 

复制代码
#include < stdio.h > #include < setjmp.h > jmp_buf j; void Exception( void ) {    longjmp(j, 1 ); } double diva( double num1, double num2)         // 两数相除函数 {     double re;      re = num1 / num2;     return re; } int main() {     double a,b,result;
       printf(
" 请输入第一个数字: " );    scanf( " %lf " , & a);    printf( " 请输入第二个数字: " );   if (setjmp(j) == 0 )   {    scanf( " %lf " , & b);    if ( 0 == b)    Exception(); result = diva(a,b);     printf( " 相除的结果是: %.2lf\n " ,result);   }   else   printf( " 试图除以一个为0的数字\n " ); return 0 ; }
复制代码

 

 

四 总结:

 

除了以上几种方法之外,另外还有使用信号量等等方法进行异常处理。当然在实际开发中每个人都有各种调式的技巧,而且这文章并不是说明异常处理一定要这样做,这只是对一般做法的一些总结,也不要乱使用异常处理,如果弄的不好就严重影响了程序的效率和结构,就像设计模式一样,不能胡乱使用。

 

http://www.cnblogs.com/vimsk/archive/2010/12/11/1901698.html#top

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值