您的位置:首页 > 编程语言 > C语言/C++

C++中异常处理的语法 try catch throw

2011-09-30 12:56 627 查看

// C++中异常处理的语法 try catch throw

 

C++中异常处理的语法。  

  关键字 

   

    1、 try 

    2、 catch 

    3、 throw 

    其中关键字try表示定义一个受到监控、受到保护的程序代码块;关键字catch与try遥相呼应,定义当try block(受监控的程序块)出现异常时,错误处理的程序模块,并且每个catch block都带一个参数(类似于函数定义时的数那样),这个参数的数据类型用于异常对象的数据类型进行匹配;而throw则是检测到一个异常错误发生后向外抛出一个异常事件,通知对应的catch程序块执行对应的错误处理。 

   

  语法 

   

    1、还是给一个例子吧!如下: 

   

  int  main() 

  { 

  cout  <<  "In  main." <<  endl; 

  //定义一个try  block,它是用一对花括号{}所括起来的块作用域的代码块 

  try 

  { 

  cout  <<  "在  try block  中,  准备抛出一个异常." <<  endl; 

  //这里抛出一个异常(其中异常对象的数据类型是int,值为1) 

  //由于在try  block中的代码是受到监控保护的,所以抛出异常后,程序的 

  //控制流便转到随后的catch  block中 

  throw  1; 

  cout  <<  "在 try  block  中,  由于前面抛出了一个异常,因此这里的代码是不会得以执行到的" <<  endl; 

  } 

  //这里必须相对应地,至少定义一个catch block,同样它也是用花括号括起来的 

  catch(  int&  value  ) 

  { 

  cout  <<  "在 catch  block  中,  处理异常错误。异常对象value的值为:"<< value  <<  endl; 

  } 

  cout  <<  "Back  in  main. Execution  resumes  here."  <<  endl; 

  return  0; 

  } 

    2、语法很简单吧!的确如此。另外一个try block可以有多个对应的catch  block,可为什么要多个catch block呢?这是因为每个catch  block匹配一种类型的异常错误对象的处理,多个catch block呢就可以针对不同的异常错误类型分别处理。毕竟异常错误也是分级别的呀!有致命的、有一般的、有警告的,甚至还有的只是事件通知。例子如下: 

   

  int  main() 

  { 

  try 

  { 

  cout  <<  "在 try  block  中,  准备抛出一个int数据类型的异常." <<  endl; 

  throw  1; 

  cout  <<  "在 try  block  中,  准备抛出一个double数据类型的异常." <<  endl; 

  throw  0.5; 

  } 

  catch(  int&  value  ) 

  { 

  cout  <<  "在 catch  block  中,  int数据类型处理异常错误。”<< endl; 

  } 

  catch(  double&  d_value  ) 

  { 

  cout  <<  "在 catch  block  中, double数据类型处理异常错误。”<< endl; 

  } 

  return  0; 

  } 

    3、一个函数中可以有多个trycatch结构块,例子如下: 

   

  int  main() 

  { 

  try 

  { 

  cout  <<  "在 try  block  中,  准备抛出一个int数据类型的异常." <<  endl; 

  throw  1; 

  } 

  catch(  int&  value  ) 

  { 

  cout  <<  "在 catch  block  中,  int数据类型处理异常错误。”<< endl; 

  } 

  //这里是二个trycatch结构块,当然也可以有第三、第四个,甚至更多 

  try 

  { 

  cout  <<  "在 try  block  中,  准备抛出一个double数据类型的异常." <<  endl; 

  throw  0.5; 

  } 

  catch(  double&  d_value  ) 

  { 

  cout  <<  "在 catch  block  中, double数据类型处理异常错误。”<< endl; 

  } 

  return  0; 

  } 

    4、上面提到一个try block可以有多个对应的catch  block,这样便于不同的异常错误分类处理,其实这只是异常错误分类处理的方法之一(暂且把它叫做横向展开的吧!)。另外还有一种就是纵向的,也即是分层的、trycatch块是可以嵌套的,当在低层的trycatch结构块中不能匹配到相同类型的catch block时,它就会到上层的trycatch块中去寻找匹配到正确的catch block异常处理模块。例程如下: 

   

  int  main() 

  { 

  try 

  { 

  //这里是嵌套的trycatch结构块 

  try 

  { 

  cout  <<  "在 try  block  中,  准备抛出一个int数据类型的异常." <<  endl; 

  throw  1; 

  } 

  catch(  int&  value  ) 

  { 

  cout  <<  "在 catch  block  中,  int数据类型处理异常错误。”<< endl; 

  } 

  cout  <<  "在 try  block  中,  准备抛出一个double数据类型的异常." <<  endl; 

  throw  0.5; 

  } 

  catch(  double&  d_value  ) 

  { 

  cout  <<  "在 catch  block  中,  double数据类型处理异常错误。”<< endl; 

  } 

  return  0; 

  } 

    5、讲到是trycatch块是可以嵌套分层的,并且通过异常对象的数据类型来进行匹配,以找到正确的catch block异常错误处理代码。这里就不得不详细叙述一下通过异常对象的数据类型来进行匹配找到正确的catch block的过程。 

   

    (1)  首先在抛出异常的trycatch块中查找catch block,按顺序先是与第一个catch  block块匹配,如果抛出的异常对象的数据类型与catch block中传入的异常对象的临时变量(就是catch语句后面参数)的数据类型完全相同,或是它的子类型对象,则匹配成功,进入到catch block中执行;否则到二步; 

   

      (2) 如果有二个或更多的catch block,则继续查找匹配第二个、第三个,乃至最后一个catch block,如匹配成功,则进入到对应的catch block中执行;否则到三步; 

   

      (3) 返回到上一级的trycatch块中,按规则继续查找对应的catch block。如果找到,进入到对应的catch block中执行;否则到四步; 

   

      (4) 再到上上级的trycatch块中,如此不断递归,直到匹配到顶级的trycatch块中的最后一个catch block,如果找到,进入到对应的catch  block中执行;否则程序将会执行terminate()退出。 

   

    另外分层嵌套的trycatch块是可以跨越函数作用域的,例程如下: 

   

  void  Func()  throw() 

  { 

  //这里实际上也是嵌套在里层的trycatch结构块 

  try 

  { 

  cout  <<  "在 try  block  中,  准备抛出一个int数据类型的异常." <<  endl; 

  //由于这个trycatch块中不能找到匹配的catch block,所以 

  //它会继续查找到调用这个函数的上层函数的trycatch块。 

  throw  1; 

  } 

  catch(  float&  value  ) 

  { 

  cout  <<  "在  catch block  中,  int数据类型处理异常错误。”<< endl; 

  } 

  } 

  int  main() 

  { 

  try 

  { 

  Func(); 

  cout  <<  "在 try  block  中,  准备抛出一个double数据类型的异常." <<  endl; 

  throw  0.5; 

  } 

  catch(  double&  d_value  ) 

  { 

  cout  <<  "在 catch  block  中, double数据类型处理异常错误。”<< endl; 

  } 

  catch(  int&  value  ) 

  { 

  //这个例子中,Func()函数中抛出的异常会在此被处理 

  cout  <<  "在 catch  block  中,  int数据类型处理异常错误。”<< endl; 

  } 

  return  0; 

  } 

    6、刚才提到,嵌套的trycatch块是可以跨越函数作用域的,其实这里面还有另外一层涵义,就是抛出异常对象的函数中并不一定必须存在trycatch块,它可以是调用这个函数的上层函数中存在trycatch块,这样这个函数的代码也同样是受保护、受监控的代码;当然即便是上层调用函数不存在trycatch块,也只是不能找到处理这类异常对象错误处理的catch block而已,例程如下: 

   

  void  Func()  throw() 

  { 

  //这里实际上也是嵌套在里层的trycatch结构块 

  //由于这个函数中是没有trycatch块的,所以它会查找到调用这个函数的上 

  //层函数的trycatch块中。 

  throw  1; 

  } 

  int  main() 

  { 

  try 

  { 

  //调用函数,注意这个函数里面抛出一个异常对象 

  Func(); 

  cout  <<  "在 try  block  中,  准备抛出一个double数据类型的异常." <<  endl; 

  throw  0.5; 

  } 

  catch(  double&  d_value  ) 

  { 

  cout  <<  "在  catch block  中,  double数据类型处理异常错误。”<< endl; 

  } 

  catch(  int&  value  ) 

  { 

  //这个例子中,Func()函数中抛出的异常会在此被处理 

  cout  <<  "在 catch  block  中,  int数据类型处理异常错误。”<< endl; 

  } 

  //如果这里调用这个函数,那么由于main()已经是调用栈的顶层函数,因此不能找 

  //到对应的catch  block,所以程序会执行terminate()退出。 

  Func(); 

  //  [特别提示]:在C++标准中规定,可以在程序任何地方throw一个异常对象, 

  //  并不要求一定只能是在受到try block监控保护的作用域中才能抛出异常,但 

  //  如果在程序中出现了抛出的找不到对应catch block的异常对象时,C++标 

  //  准中规定要求系统必须执行terminate()来终止程序。 

  //  因此这个例程是可以编译通过的,但运行时却会异常终止。这往往给软件 

  //  系统带来了不安全性。与此形成对比的是java中提供的异常处理模型却是不 

  //  永许出现这样的找不到对应catch block的异常对象,它在编译时就给出错误 

  //  提示,所以java中提供的异常处理模型往往比C++要更完善,后面的章节 

  //  会进一步对这两种异常处理模型进行一个详细的分析比较。 

  return  0; 

  } 


 

 

//try catchthrow

class   CMyexception 

  {};  

  try  

  {   

        int   nTemp   =  0;   

        if(   nTemp   ==   0  )   

        {   

              throw(nTemp);   //也可以写成 throw nTemp;

             //直接写成throw;抛出是这之前产生的异常,不要这么写,调试及运行的时候可能会出错!

            //这样写的方式最好只出现在catch() 语句块中!throw;重新抛出的原异常而非原异常的拷贝1

        }   

        CMyexception  myexception;   

        throw(myexception);  

  }  

  catch(int   nError)    //捕获int 类型

  {  

  }  

  catch(CMyexception   &Error)   //捕获CMyexception 类型,必须为引用

  {  

  }  

  catch(...)     //捕获其它何类型,但前面已经获的将再也看不到了

  {   

         throw; //不带任何参数的throw将不能处理的异常继续抛出               

  }  

  某些操作在某些条件下不能正确执行,只能抛出一些信息告诉上层执行失败,如果不用异常也可以用错误码之类的来表示(确切的说异常情况也是属于程序正常运行)  

  如上如要运用异常,必须将实际运行代码放在try块内,并在里面throw出各种类型的异常,比如int型,CMyexception型等(当然不会故意自己throw一些异常出来,一般在try块中都是调用了一些可能会抛出某种异常的函数,比如某些API函数),如果程序在执行过程中真的有异常发生,那么catch块就派上用场了,但是要注意catch块可以同时有很多(如上)但是只能捕获与catch内捕获类型一致的异常,比如catch(int   nError)只能捕获int型异常,catch(CMyexception  
&Error)只能捕获CMyexception型异常。最后,catch(...)可以捕获所有前面不能匹配的异常类型。

--------在函数名 或者 类名后面的throw-------------

throw() The function does not throw an exception. //如果程序中实际上有throw则编译会有警告

throw(...) The function can throw an exception.

throw(type) The function can throw an exception of type type. However, inVisual C++ .NET, this is equivalent to throw(...).

 

 

 

try,catch,throw的含义就很清楚,但怎么使用,我还是过了好多年才明白一些

先看一个不好的例子

//----------------- 不好的代码-----------------

class CTest1;

class CTest2;

class CTest3;

void BadCode()

{

  //new test1

  CTest1 * pTest1 = new CTest1;

  //do something

  bool bRet = DoSomething();

  if (!bRet)

  {

    delete pTest1;

    return;

  }

  //new CTest2

  CTest2 * pTest2 = new CTest2;

  //do something

  bRet = DoSomething();

  if (!bRet)

  {

    delete pTest1;

    delete pTest2;

    return;

  }

  //new CTest3

  CTest3 * pTest3 = new CTest3;

  //do something

  bRet = DoSomething();

  if (!bRet)

  {

    delete pTest1;

    delete pTest2;

    delete pTest3;

    return;

  }

  //release

  delete pTest1;

  delete pTest2;

  delete pTest3;

}

下面是我个人比较喜欢的写法

//--------- 好的例子 ---------------

class CTest1;

class CTest2;

class CTest3;

void BadCode()

{

  //define

  CTest1 * pTest1 = NULL;

  CTest2 * pTest2 = NULL;

  CTest3 * pTest3 = NULL;

  //使用try, catch, throw

  try

  {

    //new test1

    pTest1 = new CTest1;

    //do something

    bool bRet = DoSomething();

    if (!bRet)

      throw -1;

    //new CTest2

    pTest2 = new CTest2;

    //do something

    bRet = DoSomething();

    if (!bRet)

      throw -2;

    //new CTest3

    pTest3 = new CTest3;

    bRet = DoSomething();

    //do something

    if (!bRet)

      throw -3;

    //release

    delete pTest1;

    pTest1 = NULL;

    delete pTest2;

    pTest2 = NULL;

    delete pTest3;

    pTest3 = NULL;

  }

  catch(...)

  {

    if (pTest1)

      delete pTest1;

    if (pTest2)

      delete pTest2;

    if (pTest3)

      delete pTest3;

  }

}

简单说一下

第一种写法,需要在不同的地方delete 不同的变量

第二种写法,在catch里delete所有的变量,代码的结构看起来更容易读,也易于维护

//define

  CTest1 * pTest1 = NULL;

  CTest2 * pTest2 = NULL;

  CTest3 * pTest3 = NULL;

变量定义一定要在try之前定义,否则catch里找不到这些变量的定义

 

什么时候使用try,catch,什么时候不用;什么时候用throw,什么时候不用。工作了很多年才明白。

我个人的理解是:

1。在private或者protected的成员函数不使用try,catch,而只使用throw

2。如果在private或者protected的成员函数需要使用try,catch,那么就要使用rethrow

3。在public成员函数里使用try,catch

4。如果该类相对于整个项目来说是属于被调用层,那么public成员函数也可以不使用try,catch

5。如果调用第三方的代码,我一般都会用try,catch

我个人的习惯是把private或者protected成员函数的名字使用前缀__,public函数不用

先看一个我不喜欢的写法

//------------- try, catch, throw 例子,不喜欢的写法 ------------

class CTest

{

public:

  int Init();

private:

  int __InitA();

  int __InitB();

  int __InitC();

}

//--------- Init ------------

int CTest:Init()

{

  try

  {

    int err;

    err = __InitA();

    if (err != 1)

        throw -1;

    err = __InitB();

    if (err != 1)

        throw -2;

    err = __InitC();

    if (err != 1)

        throw 3;

    return 1;

  }

  catch(int & err)

  {

    return err;

  }

}

//---------- __InitA ----------

int CTest::__InitA()

{

  try

  {

    int err;

    err = DoSomething1();

    if (err != 1)

        throw -1;

    err = DoSomething2();

    if (err != 1)

        throw -2;

     

    return 1;

  }

  catch(int & err)

  {

    return err;

  }

}

__InitB, ___InitC和___InitA类似

下面是我个人比较喜欢的写法

//------------- try, catch, throw 例子,喜欢的写法 ------------

class CTest

{

public:

  int Init();

private:

  int __InitA();

  int __InitB();

  int __InitC();

}

//--------- Init ------------

int CTest:Init()

{

  try

  {

    __InitA();

    __InitB();

    __InitC();

    return 1;

  }

  catch(int & err)

  {

    return err;

  }

}

//---------- __InitA ----------

int CTest::__InitA()

{

    int err;

    err = DoSomething1();

    if (err != 1)

        throw -1;

    err = DoSomething2();

    if (err != 1)

        throw -2;

     

    return 1;

}

__InitB, ___InitC和___InitA类似

 

 

 

//try,catch,throw异常捕捉
 try,catch,throw:

      try包含你要防护的代码,称为防护块.

             防护块如果出现异常,会自动生成异常对象并抛出.

             catch捕捉特定的异常,并在其中进行适当处理.

             throw可以直接抛出/产生异常,导致控制流程转到catch块.

             重要观点:

             C++中异常是用对象来表示的,称为异常对象.

             基本格式:

             try

             {

             your code;

             }

            catch(T1 t1)//T1可以是任意类型,int,char, CException...

             { //T1指定了你要捕捉的异常的类型,t1指定了异常

             //对象的名称,当有异常抛出,异常对象将被复制到t1

             //中,这样你就可以在本处理块中使用该对象,获取相关

             //信息,进行适当处理.

              处理代码;

             }

            catch(T2* pt1) //上面的catch是值传递,这里使用指针传递.

             {

              处理代码;

             }

            catch(...)//...是捕捉任意类型的异常.

             {

              处理代码;

             }

             其他代码; //某个catch执行完,就跳转到这里继续执行.

             //在没有使用C++异常处理的情况下,如果在

             //此之前出现异常,则这里的其他代码不会被执行

             //从而造成问题.请考虑在这里放置: delete pobj1;

             //如果不使用用try,catch机制,内存泄漏是必然的,

             //因为出现问题后,执行流程无法跳转到这里.

             说明:

             try{}之后可以跟任意个catch块.

             发生异常后,会生成临时的异常对象,进行一些自动处理之后,程序流程跳转到后面的catch(),逐个检查这些catch(),如果与catch()

             中指定的类型一致,则将对象拷贝给catch参数中的对象,

             接着执行该catch块中的代码,然后跳过其他所有剩下的catch,

             继续执行后续的代码.

             *上面所说的自动处理指的是堆栈回退,说白了就是为函数中的

             局部对象调用析构函数,保证这些局部对象行为良好.

            catch()的顺序通常按照:从特殊到一般的顺序:

            catch(Tsub o){}

            catch(Tbase o){}

            catch(...){}

             如果第一个catch为catch(Tbase){},则它将捕捉其所有派生类的

             异常对象.

             如果第一个catch为catch(...){},则其后的所有catch永远不可能

             被执行.

             重新抛出异常:

             从上面的处理机制可以看到,只有一个catch可能被执行,

             如果一个catch被执行,其他后续的catch就会被跳过了.

             有时候一个catch中可能无法完成异常的全部处理,需要将

             异常提交给更高的层,以期望得到处理.重新抛出异常实现

             了这种可能性.

             语法:

             throw; //空的throw语句,只能在catch中使用.

             //它重新抛出异常对象,其外层的catch可能可以

             //捕捉这个重新抛出的异常并做适当处理.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息