zoukankan      html  css  js  c++  java
  • (C/C++学习笔记) 二十一. 异常处理

    二十一. 异常处理

    异常的概念

    程序的错误通常包括:语法错误、逻辑错误、运行异常。

    语法错误指书写的程序语句不合乎编译器的语法规则,这种错误在编译、连接时由编译器指出。

    逻辑错误是指程序能顺利运行,但是没有实现预期的功能,这类错误通过调试与测试发现。

    运行异常(exception是指程序在运行的过程中由于意外的结果,运行环境问题造成程序异常终止,如内存空间不足、打开文件不存在、文件读些不成功、执行了除0操作等。

     

    异常处理: 程序运行异常虽然是无法避免,但是可以预料,为了保证程序的健壮性,必须要在程序中对运行异常进行预见性处理,对运行异常进行预见性处理称为异常处理

    处理异常的基本思想是:在底层发生的问题,逐级上报,直到有能力可以处理异常的那级为止。或者说,在应用程序中,如果某个函数发现了错误并引发异常,这个函数就将该异常向上级调用者传递,请求调用者捕获该异常并处理该错误。如果调用者不能处理该错误,就继续向上级调用者传递,直到异常被捕获错误被处理为止。

    如果程序最终没有相应的代码处理该异常,那么该异常最后被C++系统所接受,C++系统就简单地终止程序运行。 

    处理异常的方法很多,其中最直接的办法是调用C++中的exit()abort()函数终止程序的执行,exit() abort()函数原型在头文件stdlib.h中声明.

    两者的区别是exit()在中止程序运行前,会关闭被程序打开的文件、调用全局和static类型对象的析构函数等;而abort()直接结束进程, 什么都不做。

    使用exit()abort()来处理异常显得很机械,有的异常需要进行更复杂的处理。

     

    • exit(x)(x不为0)都表示异常退出, 但一般用exit(-1)exit(1); exit(0)表示正常退出. 另外, stdio.h, EXIT_SUCCESS表示正常退出,EXIT_FAILURE表示异常退出.
    • abort()函数没有参数.

    ※ 用if语句处理异常的问题:

    以往的异常捕获方式是利用if语句检查调用函数的返回值,或者在函数调用之前检查,如在求两个数的商时就需要在函数前检查除数是否为0来捕获、防止异常:

        float quotient(int a, int b) { return a/(float)b; }

            …

        cin>>a>>b;

        if (b==0) //捕获异常

            cout<<"Divide 0 !"<<endl;

        else

            cout<<a<<"/"<<b<<"="<<quotient(a,b);

       

    这种处理机制有如下缺点:

    (1) 每使用quotient()一次, 就必须利用if语句检查一次,使得程序对正常执行过程的描述与对异常的处理交织在一起,程序的易读性不好。

    (2) 若异常信息在函数中返回,会破坏程序的逻辑性。如:原来没有返回值的函数,要定义成返回值;对原来有返回值的函数无法定义异常信息返回;象构造函数、析构函数这类由程序自动调用,又没有返回值的特殊函数,就没有办法利用返回值返回异常。

    为此,C++提供了异常处理解决方案。

     

    ● 异常处理的语法

    抛掷异常的程序段

     

    ......

    throw异常类型表达式;     //也可以写成: throw(异常类型表达式); 这一表达式就是引发异常的东西

    ......

     

    捕获并处理异常的程序段

    try

    可能产生错误的语句

    catch(异常类型1

    {异常处理语句块1}

    catch(异常类型2

    {异常处理语句块1}

    catch(异常类型n

    {异常处理语句块n}

     

    //异常处理机制包括4部分:

    throw 表达式

    try 语句块

    catch 语句块

    • 异常本身

     

    //异常处理的执行过程如下:

    (1) 执行try块中的程序语句序列;

    (2) 如果执行期间没有执行到throw()(没有引起异常),跳过异常处理区的catch语句块,程序向下执行;

    (3) 若执行期间引起异常,执行throw()语句抛出异常,进入异常处理区,将throw() 抛出的异常类型表达式(对象)依次与catch()中的类型匹配,获得匹配的catch子句将捕获并处理异常。继续执行异常处理区后的语句;

    (4) 如果未找到匹配(异常未捕获到),自动调用结束函数terminate(),其缺省功能是调用abort()终止程序。

     

    ● 处理除零异常的几种形式

    #include <iostream>

    using namespace std;

     

    int divide(int x, int y)

    {

        if (y == 0)

            throw y;    //用throw抛出异常, 即除数为0的异常(error of the divisor being zero)     

        return x / y;

    }

    int main()

    {

        try                //用try定义异常

        {    

            cout << "5 / 2 = " << divide(5, 2) << endl;

            cout << "8 / 0 = " << divide(8, 0) << endl;    //发生异常, 函数divide()被退栈处理, 返回地址(即下一语句的地址)也被退栈,

            cout << "7 / 1 = " << divide(7, 1) << endl;    //故这一语句不再被执行

        }

        catch (int e)    //用catch捕获并处理异常; 异常类型为int; 定义一个int型变量e(0), 并将捕获的异常的值赋给e

        {

            cout << e << " can't be a divisor!" << endl;

        }

        cout << "That is ok." << endl;

        return 0;

    }

     

    #include<iostream.h>                                 //包含头文件

    #include<stdlib.h>

     

    double fuc(double x, double y)                            //定义函数

    {

        if(y==0)

        {

            throw y; //也可以是throw x, 因为C++使用数据类型来捕获不同的异常(这里的x, y都是double型, 所以x和y都可以作为被抛出的异常), 但因为这里引发异常的是y, 所以最好抛出y

        }

        return x/y;                                        //否则返回两个数的商

    }

    void main()

    {

        double res;

        try                                            //定义异常

        {

            res=fuc(2,3);

            cout<<"The result of x/y is : "<<res<<endl;

            res=fuc(4,0);                                //出现异常

        }

        catch(double)        //捕获并处理异常, 也可以写成catch(double y), 即catch的是类型, 而不是某个具体变量

        {

            cerr<<"error of diviso being zero. ";        //cerr是接受标准错误输出的对象

            exit(1);                                    //异常退出程序

        }

    }

     

    # include <iostream>

    using namespace std;

    float quotient(int a,int b) throw(char *) //throw(char *)表示只抛出char *类型的异常

    {

    if (b==0)

         throw "Divided by 0!";

    else

         return a/(float)b;

    }

    void main()

    {

        int a,b;

        cout<<"Input a, b: ";

        cin>>a>>b;

        try

        {

            cout<<a<<"/"<<b<<"="<<quotient(a,b);

        }

    catch(char *ErrS) //定义一个char*型变量ErrS, 并将捕获的异常的值(即字符串"Divide 0!")赋给ErrS

        {

            cerr<<ErrS<<endl;    

        }

    }

     

     

    ● 异常接口声明

    可以在函数的声明中列出这个函数可能抛掷的所有异常类型。

    例如:

    void fun() throw(A,BCD);    //表示fun只能抛出ABCD类型的异常

    若无异常接口声明,或者throw(...),则此函数可以抛掷任何类型的异常。

    ※ 直接写throw(...), 这里的省略号不代表其它内容.

    不抛掷任何类型异常的函数声明如下:

    void fun() throw();

     

    ● 异常处理中的构造和析构

    #include<iostream.h>

    class expt                                                    //定义类expt

    {

    public:                                                    //定义公有成员

        expt()                                                //定义构造函数

        {

            cout<<"structor of expt"<<endl;

        }

        ~ expt()                                                //定义析构函数

        {

            cout<<"destructor of expt"<<endl;

        }

    };

    class demo                                                //定义类demo

    {

    public:

        demo()                                                //定义构造函数

        {

            cout<<"structor of demo"<<endl;

        }

        ~demo()                                                //定义析构函数

        {

            cout<<"destructor of demo"<<endl;

        }

    };

    void fuc1()                                                //定义函数

    {

        int s=0;

        demo d;                                                //声明demo类的对象

        throw s;    //抛出异常, 这里的异常抛出没有条件, 反正就是有异常

    }

    void fuc2()

    {

        expt e;                                                //声明expt类的对象

        fuc1();                                                //调用函数fuc1

    }

    void main()

    {

        try                                                    //定义异常

        {

            fuc2();                                            //调用函数

        }

        catch(int)                                                //定义异常处理

        {

            cout<<"catch int exception"<<endl;

        }

        cout<<"continue main()"<<endl;

    }

    //在抛出异常前, ed这两个对象先后被创建, 在抛出异常后, 两个对象按与创建的相反顺序调用析构函数而被销毁.

     

    ● 异常处理综合案例

    求一元二次方程(ax²+bx+c=0 (a0))的实根, 要求加上异常处理, 判断b*b-4*a*c是否大于0, 成立则求两个实根, 否则要求重新输入.

    注意, 一元二次方程求根公式为:

    #include <iostream>

    #include <math.h>

    using namespace std;

    double sqrt_delta(double d)

    {

        if(d < 0)

            throw 1;

        return sqrt(d);

    }

    double delta(double a, double b, double c)

    {

        double d = b * b - 4 * a * c;

        return sqrt_delta(d);

    }

    void main()

    {

        double a, b, c;

        cout << "please input a, b, c" << endl;

        cin >> a >> b >> c;        //接收输入

        while(true)        //无限循环, 这样如果delta小于0, 我们可以重新输入系数a, b, c

        {

            try

            {

                double d = delta(a, b, c);

                cout << "x1: " << (d - b) / (2 * a);

                cout << endl;

                cout << "x2: " << -(b + d) / (2 * a);

                cout << endl;

                break;    //跳出循环

            }

            catch(int)

            {

                cout << "delta < 0, please reenter a, b, c.";

                cin >> a >> b >> c;

            }

        }

    }

     

    标准异常类

    C++提供了标准异常处理类库,它用来抛出C++标准库中函数执行时的异常。C++标准异常处理类的层次结构图如下图所示:

    • exception是标准程序库异常类的公共基类, 下面的都是子类

    例如: logic_error表示可以在程序中被预先检测到的异常(如果小心地编写程序,这类异常能够避免); runtime_error表示难以被预先检测的异常

    ※ 基类exception提供了一个成员函数what(), 用于返回错误信息(返回类型为const char*)该函数在exception的派生类中可以被重载.

     

    ● 标准异常类的使用

    # include <iostream>

    //# include <new> //Visual C++6.0中可以不包含

    # include <string>

    //# include <stdexcept> //Visual C++6.0中可以不包含

     

    using namespace std;

    void main()

    {

        string* S;    //S是对象

        try 

        {

            S=new string("ABCD"); //可能抛出bad_alloc异常

            cout<<S->substr(5,2); //可能抛出out_of_range异常

        }

    catch(bad_alloc& NoMemory)    //bad_alloc&是异常类型, 异常的值传给了对象NoMemory

        {

            cout<<"Exception occurred: "<<NoMemory.what()<<endl;    //what()是对象NoMemory的成员函数

        }

        catch(out_of_range& OutOfRange) // out_of_range&是异常类型, 异常的值传给了对象OutOfRange

        {

            cout<<"Exception occurred: "<<OutOfRange.what()<<endl;        //what()是对象OutOfRange的成员函数

        }

    }

     

  • 相关阅读:
    leetcode 13. Roman to Integer
    python 判断是否为有效域名
    leetcode 169. Majority Element
    leetcode 733. Flood Fill
    最大信息系数——检测变量之间非线性相关性
    leetcode 453. Minimum Moves to Equal Array Elements
    leetcode 492. Construct the Rectangle
    leetcode 598. Range Addition II
    leetcode 349. Intersection of Two Arrays
    leetcode 171. Excel Sheet Column Number
  • 原文地址:https://www.cnblogs.com/ArrozZhu/p/8378068.html
Copyright © 2011-2022 走看看