zoukankan      html  css  js  c++  java
  • 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;
    }

    四 总结:

     

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

      

     

  • 相关阅读:
    常见的mysql查询命令
    Linux查看系统硬件信息命令汇总
    抓取页面上的email邮箱
    inotify+rsync实时同步【优化版本-转发】
    inotify+rsync做实时同步
    用配置文件里面的参数值替换yaml模板中的变量值【python】
    合并文件内容到另一个文件尾部[python]
    python farbric 主机/密钥列表配置
    python 项目环境包的名称和版本导出和导入
    sendmai.py
  • 原文地址:https://www.cnblogs.com/vimsk/p/1901698.html
Copyright © 2011-2022 走看看