首页 > 编程语言 >C、C++、Java语言中异常处理机制浅析

C、C++、Java语言中异常处理机制浅析

时间:2022-12-01 18:05:18浏览次数:34  
标签:Java void C++ throw try catch include 异常 浅析


C、C++、Java语言中异常处理机制浅析


一、     异常处理 (ExceptionalHandling)概述

1.    异常处理

异常处理又称异常错误处理,它提供了处理程序运行时出现任何意外或异常情况的方法。异常处理通常是防止未知错误的发生所采取的处理措施,对于某一类型的错误,异常处理应该提供相应的处理方法。例如,在设计程序时,如果可能会碰到除0错误或者数组访问越界错误,程序员应该在程序中设计相应的异常处理代码以便发生异常情况时,程序做出相应的处理。

2.    异常处理的两类模型

(1)终止模型

在这种模型中,异常是致命的,它一旦发生,将导致程序终止。这种模型被C++和Java语言所支持。

(2)恢复模型

当发生异常时,由异常处理方法进行处理,处理完毕后程序返回继续执行。

二、     C语言异常处理

1.  常用方法

(1)使用abort()和exit()两个函数,他们声明在<stdlib.h>中;

(2)使用assert宏调用,它位于<assert.h>中。assert(expression)当expression为0时,就好引发abort();

(3)使用全局变量errno,它由C语言库函数提供,位于<errno.h>中;

(4)使用goto语用局部跳转到异常处理代码处;

(5)使用setjmp和longjmp实现全局跳转,它们声明<setjmp.h>中,一般由setjmp保存jmp_buf上下文结构体,然后由longjmp跳回到此时。

2.  实例演示

实例一 :使用exit()终止程序运行

#include<stdio.h>

#include<stdlib.h>

 

voidDivideError(void)

{

   printf("divide 0 error!\n");

}

doubledivide(double x,double y)

{

   if(y==0) exit(EXIT_FAILURE);//此时EXIT_FAILURE=1

//也可以使用atexit()函数来注册异常处理函数,但此时异常处理函//数必须形如voidfun(void);

   else return x/y;

}

intmain()

{

   double x,y,res;

  printf("x=");

  scanf("%lf",&x);

  printf("y=");

  scanf("%lf",&y);

  atexit(DivideError);

   res=divide(x,y);

   printf("result=%lf\n",res);

   return 0;

}

实例二:使用assert(expression)

#include<stdio.h>

#include<assert.h>

 

intmain()

{

   int a,b,res;

 res=scanf("%d,%d",&a,&b);

 //scnaf函数返回从stdin流中成功读入的数据个数

  assert(res==2); //如果res!=2,则出现异常

   return 0;

}

实例三:使用全局变量errno来获取异常情况的编号

#include<stdio.h>

#include<errno.h>

 

intmain()

{

   char filename[80];

   errno=0;

   scanf("%s",filename);

   FILE* fp=fopen(filename,"r");

   printf("%d\n",errno); //如果此时文件打不开,那么errno=2

   return 0;

}

实例四:使用goto实现局部跳转

#include<stdio.h>

#include<stdlib.h>

intmain()

{

   double x,y,res;

   int tag=0;

   if(tag==1)

   {

       Error:

       printf("divide0 error!\n");

       exit(1);

   }

  printf("x=");

  scanf("%lf",&x);

  printf("y=");

  scanf("%lf",&y);

   if(y==0)

   {

      tag=1; 

      goto Error;

   }

   else

   {

     res=divide(x,y);

     printf("result =%lf\n",res);

   }

   return 0;

}

实例五:使用setjmp和longjmp实现全局跳转

#include<stdio.h>

#include<setjmp.h>

 

jmp_buf mark; //保存跳转点上下文环境的结构体

void DivideError()

{

   longjmp(mark,1);

}

 

intmain()

{

   double a,b,res;

   printf("a=");

   scanf("%lf",&a);

   printf("b=");

   scanf("%lf",&b);

   if(setjmp(mark)==0)

   {

      if(b==0) DivideError();

      else

      {

        res=a/b;

        printf("the result is%lf\n",res);

      }

   }

   else printf("Divide 0 error!\n");

   return 0;

}

三、     C++异常处理

1.     C++异常类的编写

#include<iostream>

#include<exception>

using namespacestd;

class DivideError:public exception //E从exception类派生而来

{

public:

      const char* what() //必须实现虚函数,它在exception类中定义,

//函数原型是 virtual const char* what() const throw()

      {

           return "除数为0错误\n";

      }

};

double divide(doublex,double y)

{

   if(y==0) throw DivideError(); //抛出异常

   else return x/y;

}

void main()

{

   double x,y;

   double res;

   try

      {

       cin>>x>>y;

       res=divide(x,y);

       cout<<res<<endl;

      }

      catch(DivideError& e)

      {

           cerr<<e.what();

      }

}

2.     对try与catch的说明

程序员应该把可能会出现异常的代码段放入try { }中,当try { }语句块中出现异常时,编译器将找相应的catch(Exception& e )来捕获异常。注意不管是用throw Exception()主动抛出异常还是在try{ }语句块中出现异常,此时异常类型必须与相应的catch(Exception& e)中异常类型一致,或者定义catch(…) { }语句块,这表明编译器在本函数中找不到异常处理,则到catch(…) { }中按照相应的代码去处理。如果这些都没有,编译器会返回上一级调用函数寻找匹配的catch,这样一级一级往上找,都找不到,则系统调用terminate,terminate调用abort()终止整个程序。

实例:

void func1()

{

   throw 1;

}

void func2()

{

   throw “helloworld”;

}

void func3()

{

    throwException();

}

void main()

{

   try

   {

      func1();

      func2();

      func3();

}

catch(int e) //捕获func1()中异常

{

   //To do Something

}

catch(const char* str) //捕获func2()中异常

{

   //To do Something

}

catch(Exception& e) //捕获func3()中异常

{

   //To do Something

}

catch(…) //都不匹配则执行此处代码

{

  // To do Something

}

 

}

3.     对throw的理解

(1) 当我们在自己定义的函数中抛出(throw)一个异常对象时,如果此异常对象在本函数定义,那么编译器会拷贝此对象到某个特定的区域。因为当此函数返回时,原本在该函数定义的对象空间将被释放,对象也就不存在了。编译器拷贝了对象,在其他函数使用catch语句时可以访问到该对象副本。如:

void func()

{

   Exception e;

   throw e; //当func()返回时,e就不存在了

}

(2) 尽量避免throw对象的指针,如下例:

#include <iostream>

#include <exception>

using namespace std;

class Exception: public exception

{

public:

   constchar* what()

   {

          return "异常出现了\n";

   }

};

void func()

{

thrownew E(); //抛出一个对象指针

}

void main()

{

try

   {

            func();

   }

   catch(E *p)

   {

         cerr<<p->what();

          int x,y;

         x=1;

         y=0;

         x=x/y; //出现新的异常

         deletep; //delete p得不到执行,此时申请对象的空间不会被释放,

   }

}

解决方案之一:

在程序中定义一个异常处理函数,如void handler(void);

并且在main函数中加入代码:

catch(…)

{

    handler();

}

所以我们在抛出异常时,推荐使用throw Exception(参数),相应的catch(constException& e),这样在抛出异常时,编译器会对没有看到具体名字的临时变量做出一些优化措施,同时在catch中也避免了无谓的对象拷贝。

(3)不要在析构函数中throw异常,如下例:

#include <iostream>

#include <exception>

#include <string>

using namespace std;

class E

{

public:

    E( ) {     }

   ~E ()

   {

         throw string("123");

   }

};

void main()

{

      try

      {

            Ee;

             throwstring("abc"); //此时抛出的异常会被下面的catch捕获

      }

      catch(string& s)

      {

            cout<<s<<endl;

      }

} //对象e的生命周期结束,系统调用其析构函数释放空间,但却throw了异常,没有catch捕获,造成程序崩溃。

解决方案一:

增加一个异常处理函数

void handler()

{

   //To do Something

    abort( );

}

在main函数开始处加入代码:set_terminate(handler),这样在main函数结束前,系统调用handler处理异常。

解决方案二:

有时我们要编写建立数据库连接的程序,此时我们定义一个Database类来管理我们的数据库,在Database类的析构函数中,我们通常希望将打开的数据库连接关闭,如果数据库关闭时出现异常,那么我们就需要处理。如下例:

#include <iostream>

#include <exception>

using namespace std;

class Database

{

public:

    Database& CreateConn()

    { 

         //To do Something

          return*this;

    }

  ~Database()

  {

         if(isclosed)//数据库确实关闭

         {

               //Todo Something

         }

         else

         {

               try

                  {

                        close();

                  }

                  catch(...)

                  {

                        //做出处理,如写日志文件

                  }

      }

}

private:

      void close() //关闭连接

      {

    //To do Something

      }

   bool isclosed;

};

void main()

{

           Database db;

}

也就是说在析构函数中并不是抛出异常,取而代之的是处理异常。

(4)在构造函数中抛出异常

构造函数的主要作用是利用构造函数参数来初始化对象,如果此时给出的参数不合法,那么应该对其进行处理。我们信奉的原则是问题早发现,早解决。如下例:

#include <iostream>

#include <exception>

#include <string>

using namespace std;

const int max=1000;

class InputException: public exception

{

public:

      const char* what()

      {

                 return "输入错误!\n";

      }

};

class Point

{

private:

      int x,y;

public:

  Point(int _x,int _y)

  {

           if(_x<0|| _x>=max || _y<0 || _y>=max) throw InputException();

      else

        {

               x=_x;

                 y=_y;

            }

}

};

void main()

{

int x,y;

  cout<<"x=";

  cin>>x;

  cout<<"y=";

  cin>>y;

  try

  {

        Point p(x,y);

  }

  catch(InputException& e)

  {

        cerr<<e.what();

   }

}

4.     异常使用的成本

在没有异常被抛出的情况下,使用try{ }语句块,整体代码大约膨胀了5%~10%,执行的速度也大约下降这个数。和正常函数返回相比,抛出异常导致的函数返回,其速度可能比正常情况慢三个数量级,所以在程序中使用异常处理有利有弊。

四、     Java异常处理

1.     try…catch…finally的使用

Java的异常处理与C++类似,try…catch子句与C++中的try…catch很相似,finally{ }表示无论是否出现异常,最终必须执行的语句块。

实例如下:

importjava.io.BufferedReader;

importjava.io.IOException;

importjava.io.InputStreamReader;

class Myclass

{

     publicstaticvoid main(String[]args)

     {

        InputStreamReaderisr=new InputStreamReader(System.in);

       BufferedReader inputReader=new BufferedReader(isr);

       String line = null;

         try

         {

              line=inputReader.readLine();

         }

         catch(IOException e)

         {

              e.printStackTrace();

         }

         finally

         {

              System.out.print(line);

         }

     }

}

2.     throw和throws的使用

这里的throw和C++中的throw是一样的,用于抛出异常,但Java的throw用在方法体内部,throws用在方法定义处,如下例:

void func() throws IOException

{

     thrownew IOException();

}

3.     Java异常类图

java.lang.Object

---java.lang.Throwable

 ---java.lang.Exception

  ---java.lang.RuntimeException java.lang.Errorjava.lang.ThreadDeath

4.     异常处理的分类

(1)可检测异常

此类异常属于编译器强制捕获类,一旦抛出,那么抛出异常的方法必须使用catch捕获,不然编译器就会报错。如sqlException,它是一个可检测异常,当程序员连接到JDBC,不捕捉到这个异常,编译器就会报错。

(2)非检测异常

当产生此类异常时,编译器也能编译通过,但要靠程序员自己去捕获。如数组越界或除0异常等。Error类和RuntimeException类都属于非检测异常。

标签:Java,void,C++,throw,try,catch,include,异常,浅析
From: https://blog.51cto.com/u_15899033/5903534

相关文章

  • 浅谈C++模板机制
    一、    什么是模板?1.模板(Template)可以看做成对于某一类问题一种通用的解决方案,而实现的具体细节则需要根据实际问题对模板做出调整和优化。2.如我们在使用Word进......
  • 浅析C++的多态机制
    一、    多态机制综述1.    回顾实例以前在编写C++程序时,我们曾实现过求某个数的绝对值的函数,当时我们做得是重载了三个函数,intfabs(intx), doublefabs(doub......
  • 探究一种定长队列操作(C ,C++版本)
    一问题来源:南京烽火通信面试二问题:现有一种定长队列,长度为2的n次方,此队列可被多线程访问,但必须确保线程级安全,即在任意时刻,队列的长度保持不变。三笔者分析1.队列的存储......
  • java安全组放行
    使用JAVA虚拟机,被安全阻止   解决:控制面板找到JAVA应用,配置JAVA   选择安全属性,编辑站点列表,并点击确定完成修改  ......
  • JAVA整合阿里云OSS/VUE上传阿里云OSS
    前言今天码帮帮系统已经整合到上传项目案例的功能了,这里有一些图片资源需要上传,之前做项目对接过七牛云,也有使用过阿里云的视频点播服务,不过这次对接阿里云的OSS确实有点......
  • 最新java面试题及答案(基础篇)
     如今IT仍是热门行业,面试程序员的人也非常多,那么,怎样才能顺利通过面试呢?2021最新java面试题及答案(基础篇),为你的面试助攻!1、Java中的内存溢出是如何造成的?OutOfMemoryEr......
  • JAVA实现数据脱敏(结合Spring)
    前言这个数据脱敏是最近做的一个权限系统的功能,我们这个权限系统包含菜单权限,操作权限,数据权限,字段权限,其中字段权限就是控制字段根据不同的角色隐藏或者显示,这个和数据......
  • Java面试题大汇总
    Java面试题及答案(2023版),适用于应届生、用工作经验的程序员,每道都是认真筛选出的高频面试题,助力大家能找到满意的工作!下载链接:全部面试题及答案PDF1:阿里巴巴Java面试题......
  • Java面试题及答案整理3
    Java面试题及答案(2023版),适用于应届生、有工作经验的程序员,每道都是认真筛选出的高频面试题,助力大家能找到满意的工作!Java多线程下载链接:全部面试题及答案PDF其他互联......
  • Java设计模式面试题及答案(2023)
    Java面试题及答案(2023版),每道都是认真筛选出的高频面试题,助力大家能找到满意的工作!Java设计模式面试题及答案下载链接:全部面试题及答案PDF其他互联网大厂面试题1:阿里......