异常捕获

 

 


 

调用 abort 函数终止程序

调用 abort() 函数来终止发现异常的程序. abort() 函数将直接终止程序而不是首先返回到主函数(在 VC 下的入口函数 main[控制台] 或 WinMain[窗体程序])中

例子:

 #include 
 #include 
 
 double hmean(double a,double b)
 {
     if(a == -b)
     {
         std::cout<<"untenable arguments to hmean()"<>x>>y)
     {
         z = hmean(x,y);
         std::cout<<"Barmonic mean of "<:";
     }
     std::cout<<"Bye!"<

当输入为 12 -12 时候输出结果( 编译器为 VS2005 ):

异常捕获_第1张图片

 [返回目录]


 

 

throw-catch 异常捕获

[工作原理][声明原型][工作流程][异常机制与函数返回机制区别][优点][注意问题][将对象作为异常类型][异常规范][exception类][bad_alloc类]

工作原理:

  • throw 语句实际上是跳转即命令跳转到另一条语句. throw 关键字表示引发异常紧随其后的值指出了异常的特征,catch 关键字表示捕获异常处理程序,以关键字 catch 开头随后位于括号中的类型声明它指出了处理程序要响应的异常类型(即响应对应的 throw 抛出的异常类型)然后用一个花括号括起代码块指定要采取的措施. catch 关键字和异常类型作为标签指出异常被 throw 抛出以后程序根据异常类型找到对应的 catch 的异常处理并跳转到此位置开始执行try语句块表示其中特定的异常可能被激活的代码,此后它后面跟着一个或多个 catch 块,在 try 块中的代码段表明需要这里注意在这些代码块可能会引发的异常.
  • 在 throw 语句后写上异常对象时, throw先通过 copy构造函数构造一个新对象,再把该新对象递给 catch. 那么当异常抛出后新对象如何释放?异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁.所有从 try 到 throw 语句之间构造起来的对象的析构函数将被自动调用.但如果一直上溯到 main 函数后还没有找到匹配的 catch 块,那么系统调用 terminate() 终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁.
  • 引发的异常对象将被第一个与之匹配的 catch块捕获,这个意味着 catch 块的排列顺序应该与派生顺序相反.所以只有通过真气地排列 catch 块的顺序,才能让程序在处理异常方面有选择余

声明原型:

try{
  ... //try block
  throw typeX_argX;//抛出异常
  ... //try block
}
catch(type1 arg1){
  ... //catch block
}
catch(type2 arg2){
  ... //catch block
}
...
catch(typeN argN) {
  ... //catch block
}
catch(...) //用于捕获任何异常,所以切记要把它放最后一个.
{
  ... //catch block
}

工作流程:

throw-catch 块是可以嵌套分层的,并通过异常对象的数据类型来进行匹配,以找到正确的 catch-block 异常错误处理代码,过程如下:

  • 首先在抛出异常的 throw-catch 块中查找 catch block,按顺序先是与第一个 catch block 块匹配,如果抛出的异常对象的数据类型与 catch block 中传入的异常对象的临时变量(就是catch语句后面参数)的数据类型完全相同,或是它的子类型对象,则匹配成功,进入到catch block中执行,否则到二步;
  • 如果有二个或更多的 catch block,则继续查找匹配第二个、第三个,乃至最后一个 catch block,如匹配成功,则进入到对应的 catch block 中执行;否则到三步;
  • 返回到上一级的 throw-catch 块中,按规则继续查找对应的 catch block.如果找到,进入到对应的 catch block 中执行,否则到四步.
  • 再到上上级的 throw-catch 块中,如此不断递归,直到匹配到顶级的 throw-catch 块中的最后一个catch block,如果找,进入到对应的catch block中执行,否则程序将会执行 terminate()退出.

[返回子目录]

流程图如下:

在一个异常捕获的中的 try-catch 搜索过程:
(所谓的第一个 catch-block表示的是在 try 语句块后第一个紧跟的 catch-block,第二第三 catch-block 也是根据 try 位置而定)

异常捕获_第2张图片

在多个异常捕获的嵌套中的 try-catch 搜索过程:

异常捕获_第3张图片

 [返回子目录]

异常机制与函数返回机制区别:

虽然 throw-catch 机制类似于函数参数和函数返回机制,但是还是有写不同之处.其中之一是函数中的返回语句将控制权返回到调用函数的函数,但是 throw 语句将函数控制权向上返回到第一个这样的函数:包含能够捕获相应异常的 try-catch 组合.

流程图:

异常捕获_第4张图片

[返回子目录]

异常捕获的优点:

  • 把错误处理和真正的工作分开来
  • 代码更易组织更清晰复杂的工作任务更容易实现
  • 毫无疑问更安全了不至于由于一些小的疏忽而使程序意外崩溃了
  • 由于C++中的try catch可以分层嵌套所以它提供了一种方法使得程序的控制流可以安全的跳转到上层(或者上上层)的错误处理模块中去.(不同于return语句异常处理的控制流是可以安全地跨越一个或多个函数 ) 
  • 还有一个重要的原因就是由于目前需要开发的软件产品总是变得越来越复杂、越来越庞大如果系统中没有一个可靠的异常处理模型那必定是一件十分糟糕的局面

[返回子目录]

异常处理中需要注意的问题:

  • 如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止.
  • 一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源.
  • 异常处理仅仅通过类型而不是通过值来匹配的,所以 catch 块的参数可以没有参数名称,只需要参数类型.
  • 函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突.
  • catch 块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对 象的多态性.另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获.
  • 编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊.

 [返回子目录]

将对象作为异常类型:

可以使用不同的异常类型来区分不同的函数在不同情况下引发的异常另外对象可以携带信息程序员可以根据这些信息来确定引发异常的原因.并且建议传递异常类型用基类的引用或者指针.因为基类的引用或者指针有一个重要的特性:基类引用或者指针可以执行派生类对象.也就是说当有一个异常类层次结构,并要分别处理不同的异常类型,则使用基类引用将能够捕获基类和由基类派生出的派生类异常对象,而使用派生类对象只能捕获他所属类以及这个类派生而来的类的对象.

[返回子目录]

异常规范:

说明:

指出它将可能引发那些类型的异常为此可在函数定义后面加上异常规范

声明形式:

返回类型 函数名(形参列表)throw(异常类型1异常类型2 ... )

注意:

    • 它告诉编译器该函数引发那些类型的异常.但是如果以后该函数引发了其他类型异常程序(最终)将调用 abort 函数对这种越权做出反应
    • 使用异常规划提醒阅读该原型的人该函数可能会引发异常应提供 try 块和处理程序
    • 如果异常规范中的括号为空则表明该函数不会引发异常

[返回子目录]

exception 类:

位于 exception 头文件中,它可以作用于其他异常类的基类,并且有一个名为 what() 的成员函数,它返回一个字符串,该字符串的特性随派生类的实现而异.

stdexcept 异常类

头文件 stdexcept 定义了其他几个异常类.

首先该类定义了 logic_error 和 runtime_error 类

logic_error 异常系列描述了典型的逻辑错误.其派生类有:

    • domain_error:    数学函数有定义域(domain)和值域(range).定义域有参数的可能取值组成,值域有函数可能返回值组成,当参数值域不在定义域中便会引发 domain_error 异常
    • invalid_argument:   指出给函数传递一个意料之外的值
    • length_error:   用于指出没有足够的空间来执行所需要的操作
    • out_of_bounds:  通常用于指示索引错误

runtime_error 异常系列描述了可能在运行期间发生但难以预见和防范的错误.其派生类有:

    • range_error :   没有发生上溢和下溢的情况下,计算结果不在范围之内的错误
    • overflow_error:  上溢错误
    • underflow_error:  下溢错误

[返回子目录]

bad_alloc 异常类:

在处理使用 new 是可能出现的内存分配问题. C++ 提供了两种可能选择的方式.实现只能有一种方式.但也可以使用编译器开关或者其他一些方法,让编程者能够选择喜欢的方式

  • 让 new 在无法满足内存请求时返回一个空指针.
  • 让 new 引发 bad_alloc 异常,头文件 new (以前名为 new.h) 中包含 bad_alloc 类声明,它是从 exception 类公有派生而来的

例子:

try-catch 使用例子:

 #include   
 using namespace std;  
 
 void main()  
 {
     double x(0),y(0),z(0);
     cout<<"enter two number:"<>x>>y)
     {
         try{
             if(x == -y)
                 throw "x == -y isn't allowed!";
             z = 2.0*x*y/(x+y);
         }
         catch(const char* s)
         {
             cout<:\n";
     }
     cout<<"Bye!\n";
     system("pause");  
 }

输出结果:

异常捕获_第5张图片

函数的异常规范:

 #include   
 using namespace std;  
 
 double hmean(double x,double y)throw(const char*)
 {
     if(x == -y)
         throw "x == -y isn't allowed!";
     return 2.0*x*y/(x+y);
 }
 void main()  
 {
     double x(0),y(0),z(0);
     cout<<"enter two number:"<>x>>y)
     {
         try{
             z = hmean(x,y);
         }
         catch(const char* s)
         {
             cout<:\n";
     }
     cout<<"Bye!\n";
     system("pause");  
 }

 输出结果同上个例子一样

一个函数引发多个异常:

 #include   
 using namespace std;  
 
 void hmean(double x,double y)throw(const char*,int)
 {
     if(x > y)
         throw "x > y isn't allowed!";
     if(x<0 || y<0)
         throw 0;
 }
 void main()  
 {
     double x(0),y(0),z(0);
     cout<<"enter two number:"<>x>>y)
     {
         try{
             hmean(x,y);
             cout<<"these numbers is ok!"<:\n";
     }
     system("pause");  
 }

输出结果:

异常捕获_第6张图片

捕获异常顺序:

例1 -- 基类在子类之前

 #include   
 using namespace std;  
 
 class A
 {
 public:
     void show(){cout<<"this is class A"<>i)
     {
         try{
             switch(i)
             {
             case 0:
                 throw a;
                 break;
             case 1:
                 throw b;
                 break;
             case 2:
                 throw c;
                 break;
             default:
                 cout<<"the number is invalid!"<

 输出结果:

异常捕获_第7张图片

例2 -- 基类在子类之后

 #include   
 using namespace std;  
 
 class A
 {
 public:
     void show(){cout<<"this is class A"<>i)
     {
         try{
             switch(i)
             {
             case 0:
                 throw a;
                 break;
             case 1:
                 throw b;
                 break;
             case 2:
                 throw c;
                 break;
             default:
                 cout<<"the number is invalid!"<

输出结果:

异常捕获_第8张图片

嵌套异常捕获:

 #include   
 using namespace std;  
 
 void main()  
 {
     int i;
     cout<<"enter a number(0 is throw_int; 1 is throw_double; 2 is throw_char):\n";
     while(cin>>i)
     {
         try{
             try{
                 switch(i)
                 {
                 case 0:
                     throw 0;
                     break;
                 case 1:
                     throw 1.1;
                     break;
                 case 2:
                     throw 'a';
                     break;
                 default:
                     cout<<"the number is invalid!"<

输出结果:

异常捕获_第9张图片

[返回目录]

你可能感兴趣的:(C++,C++语法)