一 前言:

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

try

{

...

}

catch( Exception e){...}

finally{

.....

}

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

try{}

...

catch(){}

块来进行异常处理。

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

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

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

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

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


二 面向对象中的异常处理

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

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

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

具体看代码:


 #include <iostream> #include <exception> usingnamespace std;  class DivideError:public exception  {  public:            DivideError::DivideError():exception(){}           constchar* what(){              return"试图去除一个值为0的数字";          }   };  double quotion(int numerator,int denominator)  {      if(==denominator)          //当除数为0时,抛出异常      throw DivideError();      return static_cast<double>(numerator)/denominator;     }  int main()  {      int number1;             //第一个数字     int number2;             //第二个数字     double result;      cout<<"请输入两个数字:" ;      while(cin>>number1>>number2){          try{              result=quotion(number1,number2);              cout<<"结果是 :"<<result<<endl;                       }     //end try         catch(DivideError &divException){              cout<<"产生异常:"                 <<divException.what()<<endl;          }      }       } 

在这个例子中,我们使用了<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()函数进行异常终止:


 #include <stdio.h> #include <stdlib.h> double diva(double num1,double num2)         //两数相除函数  {      double re;      re=num1/num2;      return re;  }  int main()  {     double a,b,result;   printf("请输入第一个数字:");    scanf("%lf",&a);    printf("请输入第二个数字:");    scanf("%lf",&b);    if(==b)                                //如果除数为0终止程序    exit(EXIT_FAILURE);  result=diva(a,b);     printf("相除的结果是: %.2lf\n",result);     return;  }

其中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);

具体看如下程序:


 #include <stdio.h> #include <stdlib.h> void Exception(void)                           //注册终止函数,通过挂接到此函数,捕获异常信息  {      printf("试图去除以一个为0的数字,出现异常!\n");  }  int main()  {     double a,b,result;    printf("请输入第一个数字:");    scanf("%lf",&a);    printf("请输入第二个数字:");    scanf("%lf",&b);    if(==b)                    //如果除数为0终止程序 ,并挂接到模拟异常捕获的注册函数   {           atexit(Exception);                             exit(EXIT_FAILURE);    }     result=diva(a,b);     printf("相除的结果是: %.2lf\n",result);     return;  }

这里需要注意的是,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(!=b);                                //构造断言表达式,捕获预期异常错误   result=diva(a,b);    printf("相除的结果是: %.2lf\n",result);       return; }

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 =;  if (NULL == fopen("d:\\1.txt", "rb"))  {  printf("%d", errno);  }  else  {  printf("%d", errno);  }  return;  }

这里试图打开一个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=;   double a,b,result;   if(==tag)   {       Throw:     printf("除数为0,出现异常\n");   }    tag=;   printf("请输入第一个数字:");   scanf("%lf",&a);   printf("请输入第二个数字:");   scanf("%lf",&b); if(b==)                                   //捕获异常(或许这么说并不恰当,暂且这么理解)  goto Throw;                                //抛出异常   result=diva(a,b);    printf("%d\n",errno);    printf("相除的结果是: %.2lf\n",result);   
return; }

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,); }  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)==)   {    scanf("%lf",&b);    if(==b)    Exception(); result=diva(a,b);     printf("相除的结果是: %.2lf\n",result);   }   else   printf("试图除以一个为0的数字\n"); return; }

四 总结:

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

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

C语言中的异常处理的更多相关文章

  1. Java语言中的异常处理

    Java语言中的异常处理包括声明异常.抛出异常.捕获异常和处理异常四个环节.   throw用于抛出异常.   throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异 ...

  2. 【C++】异常简述(一):C语言中的异常处理机制

    人的一生会遇到很多大起大落,尤其是程序员. 程序员写好的程序,论其消亡形式无非三种:无疾而终.自杀.他杀. 当然作为一名程序员,最乐意看到自己写的程序能够无疾而终,因此尽快的学习异常处理机制是非常重要 ...

  3. C语言中的异常处理机制

    #define try if(!setjmp(Jump_Buffer)) 返回try现场后重新执行判断,所以有两次执行. http://blog.csdn.net/tian_dao_chou_qin/ ...

  4. 021_go语言中的异常处理

    代码演示 package main import ( "errors" "fmt" ) // Go语言里面约定错误代码是函数的最后一个返回值, // 并且类型是 ...

  5. go语言中使用defer、panic、recover处理异常

    go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...

  6. C中的异常处理

    1,C 语言崇尚简洁高效,因此语言本身并没有异常处理的相关语法规则,但是异常处理在 C 语言中 是存在的,我们有必要从 C 语言开始先看一看 C 语言中的异常处理是怎样, 然后对比 C++ 里面的异常 ...

  7. Go语言中异常处理painc()和recover()的用法

    Go语言中异常处理painc()和recover()的用法 1.Painc用法是:用于抛出错误.Recover()用法是:将Recover()写在defer中,并且在可能发生panic的地方之前,先调 ...

  8. [R]R语言里的异常处理与错误控制

    之前一直只是在写小程序脚本工具,几乎不会对异常和错误进行控制和处理. 随着脚本结构和逻辑更复杂,脚本输出结果的准确性验证困难,同时已发布脚本的维护也变得困难.所以也开始考虑引入异常处理和测试工具的事情 ...

  9. java 中的异常处理

    一. 异常的概念和Java异常体系结构  异常是程序运行过程中出现的错误.本文主要讲授的是Java语言的异常处理.Java语言的异常处理框架,     是Java语言健壮性的一个重要体现. Java把 ...

随机推荐

  1. Python语言程序设计:Lab4

    Programming 1.Analysing a Text File Look at the file xian_info.txt which is like this: Xi'an China 8 ...

  2. Linux:入门基础

    一.操作Linux必知必会基础知识 二.在Linux命令下查看命令帮助信息 三.Linux挂机重启注销命令 四.Linux显示系统IP地址 一.操作Linux必知必会基础知识 1.Linux命令行组成 ...

  3. C#ThreadPool类—多线程

    标题:ThreadPool Class 地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.threadpool?redir ...

  4. IIS 自动化发布工具实现【一】

    [持续更新中啦] 过去一年,有在尝试做.net 这块的开发运维工作.基于现在的开发场景,写了一套差异发布工具.后面用python重写了一套,现学现卖. 主要功能: 差异打包.自动发布.自动回滚 实现架 ...

  5. prometheus监控redis,redis-cluster

    Prometheus监控redis使用的是redis_exporter, 作者GitHub: https://github.com/oliver006/redis_exporter 需要说明的是: r ...

  6. wsgiref 与 Django框架初识

    前戏: Web框架的本质 我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,这样我们就可以自己实现Web框架 软件开发架构: c/s架构 客 ...

  7. STM32的指令周期

    在keil中编程时,写了一行代码,然后就想知道,执行这句C代码需要多长时间. 时钟周期在这就不解释了,频率的倒数. 指令周期,个人理解就是cpu执行一条汇编指令所需要的时间. 我们知道cm3使用的三级 ...

  8. Linux命令的详解

           cat /etc/passwd文件中的每个用户都有一个对应的记录行,记录着这个用户的一下基本属性.该文件对所有用户可读.               /etc/shadow  文件正如他 ...

  9. [GraphQL] Reuse GraphQL Selection Sets with Fragments

    Fragments are selection sets that can be used across multiple queries. They allow you to refactor re ...

  10. LeetCode 273. Integer to English Words

    原题链接在这里:https://leetcode.com/problems/integer-to-english-words/description/ 题目: Convert a non-negati ...