zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

C++笔记(9) 异常

2023-02-18 16:27:15 时间

程序有时会遇到运行阶段错误,导致程序无法正常走下去。对于这种问题,处理方法主要有:

1.调用abort()

Abort()函数原型位于头文件cstdlib,其典型实现是向标准错误流(即cerr使用的错误流)发送消息abnormal program termination(程序异常终止),然后中止程序。它还返回一个随实现而异的值,告诉操作系统,处理失败。abort()是否刷新文件缓存区取决于实现。如果愿意还可以用exit(),该函数刷新文件缓存区,但不显示消息。

2. 使用函数的返回值来指出问题

例如,ostream类的get(void)成员,通常返回下一个输入字符的ASCII码,但到达文件尾时,将返回特殊值EOF。

3.异常机制

对异常的处理有3个组成部分:

  • 引发异常
  • 使用处理程序捕获异常
  • 使用try块

下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常。

#include <iostream>
using namespace std;
 
double division(int a, int b)
{
  
if( b == 0 ) { throw "Division by zero condition!";//抛出异常 } return (a/b); } int main () { int x = 50; int y = 0; double z = 0;
try {//start of try block z = division(x, y); cout << z << endl; }//end of try block
catch (const char* msg) {//start of exception handler
     cerr << msg << endl;
   }//end of exception handler
return 0; }

由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:

Division by zero condition!

如果在执行完try块中的语句后,没有引发任何异常,则程序跳过try后面的catch块,直接执行处理程序后面的第一条语句。

异常的特性:

1. 执行throw语句类似于执行返回语句,因为它也将中止函数的执行。但还是有些不同之处:

1)throw不是将控制权返回给调用程序,而是导致程序沿调用序列后退,直到找到包含try块的函数

2)引发异常时,编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用

class problem{...}
...
void super() throw (problem)
{
    ...
    if(oh_no)
    {
        problem oops;
        throw oops;//抛出的是oops的副本
        ...
    }
}
try{
    super();
}
catch(problem & p)//p是oops副本的引用,在函数super()执行完毕后,oops将不复存在
{
    ...
}

既然throw语句将生成副本,为什么代码中还要使用引用呢?

  • 将引用作为返回值的通常原因是避免创建副本以提高效率
  • 基类引用可以执行派生类对象

2. 基类引用能够捕获任何异常对象,而派生类对象只能捕获它所属类及从这个类派生而来的类的对象。引发的异常对象往往被第一个与之匹配的catch块捕获,所以,catch块的排列顺序应该与派生顺序相反

3. 程序进行栈解退以回到能够捕捉异常的地方时。将释放栈中的自动存储变量。如果变量是类对象。将为该对象调用析构函数。但是在有 new 分配内存的程序中,因为异常而使函数终止,没能执行对应的delete,通常需要程序员在对应的catch块中补上对应delete。

C++ 标准的异常

C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

下表是对上面层次结构中出现的每个异常的说明:

异常描述
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。

异常何时会迷失方向

1. 意外异常:在带异常规范的函数中引发,但没有与规范列表中的某种异常匹配。

会先调用unexpected(),unexpected()调用terminate(),terminate()调用abort(),利用可以修改unexpected()的行为的set_unexpected()函数。

这些函数在头文件<exception>中声明:

typedef void (*unexpected_handler) ();//定义一个函数指针
unexpected_handler set_unexpected(unexpected_handler f) throw;//一个无参数 无返回 函数
void unexpected();

解决方案:
1)首先

#include <exception>
using namespace std;

2)自己定义一个替代函数

void myUnexpected()
{
  throw std::bad_exception();  //bad_exception是exception派生
}

3)再程序中定义

set_unexpected(myUnexpected);

4)然后把bad_exception类型规范到函数中

double Argh (double,double)  throw(out_of_bounds,bad_exception);
…

try
{
 x=Argh(a,b);
}

catch (out_of_bounds & ex)
{……}

catch (bad_exception & ex)
{……}

 

2. 未捕获异常:不是在函数中引发/函数没有异常规范,由于没有try块或匹配的catch块导致没有捕获。

未捕获异常不会直接导致程序终止,程序会先调用terminate(),默认情况下terminate()调用abort()。

也可以指定terminate()调用的函数,利用set_terminate()函数。

这些函数在头文件<exception>中声明:

set_terminate():
typedef void (*terminate_handler) ();//定义一个函数指针
terminate_handler set_terminate(terminate_handler f) throw;//一个无参数 无返回 函数
void terminate();

解决方案:

1)首先

#include <exception>
using namespace std;

2)写个自定义函数

void myQuit()
{
    cout<<"..."<<endl;
    exit(5);    
}

3)在程序开头,将中止操作指定为调用该函数

set_terminate(myQuit);

 

 

源自:

1. 《C++ Primer Plus》15.3节

2. https://www.runoob.com/cplusplus/cpp-exceptions-handling.html