• C++异常


    21 C++异常

    21.1 什么时候会发生异常

    • 1.打开一个不存在的文件

    • 2.请求存储空间失败

    • 3.数组越界等等

    21.2 使用abort()函数

    • 1.包含在cstdlib头文件中,包含在std命名空间中

    • 2.当调用abort()函数时,会引发异常并中断程序(Visual Studio 2019);

    • 3.abort()函数是否刷新缓冲区取决于实现,程序员可以调用exit()以刷新缓冲区 相关函数:double hmean(double a, double b);

    21.3 程序员手动处理

    • 1.使用指针或引用计算值,如果遇到错误则返回一个错误的计算结果(一般是永远不会用到的值)

    • 2.程序员偏向于使用指针,因为这样可以做出区分,如果是引用的话赋值语句语法一样就没什么区分 相关函数:bool hmean(double a, double b, double* ans);

    21.4 异常处理机制

    try catch语句:try检查是否会出现异常,catch捕获异常并处理。 首先执行到try,如果try语句块中的语句不引发异常,则直接跳过所有catch语句块;如果try语句块中的语句引发了异常,则检查异常是否与catch语句块括号里的类型一致,如果一致,则执行该catch语句块,如果不一致,则继续检查异常是否与catch语句块括号里的类型一致,以此类推,直到找到一致的catch语句块并执行该catch语句块;如果找不到合适的catch语句块,则使用默认异常处理方法。

    本段程序使用的是 throw一个字符串,原则上throw任意数据类型都可,但是一般情况下程序员喜欢throw异常类 相关函数:double hmean1(double a, double b);

    21.5 抛出异常类

    选择throw异常类的原因:

    • 1.可以使用不同的异常类型去区分不同的异常;

    • 2.异常类可以携带异常信息

    • 3.catch块可以使用异常类携带的异常信息 相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean 相关函数:double hmean2(double a, double b); double gmean(double a, double b);

     

    21.6 异常规范(Exception Specifications)

    c++11被取消了

    格式:throw()部分将出现在原型和函数定义中。

    double harm(double a) throw(bad_thing); // may throw bad_thing exception
    double marm(double) throw(); // doesn't throw an exception

    使用异常规范的原因:

    • 1.异常规范的目的时提醒用户可能需要一个try块。

    • 2.允许编译器添加代码来执行运行时检查,以查看是否违反了异常规范。

    为什么被取消?marm()不引发异常,但可能在它调用的函数里面引发异常,也可能现在不引发异常但是将来系统更新后可能引发异常。

    在C++程序员一致认为这种异常规范应该取缔。

    但是C++11允许一种特别的规范:关键词 noexcept

    double marm() noexcept; // marm() doesn't throw an exception

    这表明该函数不会抛出异常,程序员认为知道函数不会抛出异常可以帮助编译器优化代码。

    如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。

    noexcept()操作符可以报告其操作数是否会引发异常。

    21.7 展开堆栈

    程序将调用函数指令的地址(返回地址)放在堆栈上,是先进后出的原则。函数参数、函数生成的自动变量、函数调用新函数的信息都被存储在堆栈上;当函数执行完成(return或正常执行完毕)时,其相关的变量、地址都将被弹出,以此类推,最终返回到最初调用函数的地方,每个函数调用都释放掉了占用的内存。 堆栈展开:考虑如果函数由于引发异常终止执行,堆栈的内容会发生什么变化?同样,程序从堆栈中释放内存。但是,程序不会在堆栈上的第一个返回地址处停止,而是继续释放堆栈,直到到达位于try块中的返回地址。

    如果在函数调用的函数里面没有处理异常,那么要将异常抛到调用该函数的函数中(throw;)处理,直到处理为止,不然会引发断点。

    相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean

    相关函数:double hmean2(double a, double b); double gmean(double a, double b); double means(double a, double b);

    21.8 try...catch注意事项

    • 1.throw会将异常传递到第一个能够处理该异常的try catch中

    • 2.在throw异常时,编译器总是创建临时copy,原因是异常被抛出后,抛出异常的函数终止,该异常类也就不复存在;为什么catch(参数)中的参数是引用;因为引用可以使用一个基类引用接收继承类的对象,这就允许一个基类引用处理各个继承类的异常了。

    class problem {...};
    ...
    void super() throw (problem)
    {
         ...
        if (oh_no)
        {
            problem oops;   // construct object
            throw oops;     // throw it
        ...
    }
    ...
    try {
        super();
    }
    catch(problem & p)//此处的p是个引用,而且指向异常的临时对象。
    {
    // statements
    }
    • 3.使用基类引用处理继承类异常时,如果需要一对一处理(每个继承类的处理方式不一样),则应该将最小的孩子(继承类)放在最前面的catch中,而将最基类的放在最后的catch中。

    • 4.当不知道异常的类型时,可以使用默认catch异常。

    try {
        duper();
    }
    catch (...)          // catch whatever is left
    { // statements 
    }

    21.9 异常类

    • 1.exception类:在头文件exception.h或except.h中;这个类是C++中最基础的类,其他异常类都可以继承exception。 如果不想单独处理每个类,则可以使用exception类的引用catch该异常类。 基类exception有个what()方法,专门用于返回描述异常类的字符串,每次捕获异常是可以手动显示。

    • 2.logic_error类:在头文件stdexcept中,继承exception类 logic_error是以下类的基类:

      • domain_error:引发函数中关于定义域或值域的异常;比如说sin()函数的定义域为(-00,+00),值域为[-1,1],如传递给sin()的值超过定义域,则可以引发域异常

      • invalid_argument:是告诉程序员一个意外的参数传递给了函数。比如只要求传递'0'或'1',如果传递其他字符,则可以引发非法异常。

      • length_error:指示没有足够的空间执行当前操作。比如将一个长度为10的字符串传递给长度为8的字符串,可以引发长度异常。

      • out_of_range:指示索引越界异常

    • 3.runtime_error类:在头文件stdexcept中,继承exception类 runtime_error是以下类的基类:

      • range_error:数据超出指定范围引发range_error异常

      • overflow_error:主要用于整型或浮点类,当计算超过数据类型可表示的最大值时引发overflow_error异常

      • underflow_error:主要用于浮点类型数据,原因是浮点类型数据类型有最小可表示的数据,如果计算超过最小值,则引发underflow_error异常

    • 4.bad_alloc异常类:用于指示在内存分配时可能发生的问题,共有继承自exception类 如果不想抛出bad_alloc异常,则可以使用new(std::nothrow)的方式,使用方法见示例

    21.10 异常与继承

    见头文件sales.h和实现文件sales.cpp

    • 1.你可以继承一个异常类

    • 2.你可以将异常类嵌套到别的类中

    • 3.嵌套类也可以被继承

    21.11 当异常无法控制的时候

    主要是针对异常规范来说的,由于它在C++11中已经被取消了,所以可能用处不是很大(了解即可)

    21.11.1 意外的异常

    意外的异常:就是在异常规范中没有匹配的异常就叫做意外的异常

    1.默认unexpected()处理意外的异常:首先调用unexpected()-->调用terminate()-->调用abort().

    2.set_unexpected()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中:

    typedef void (*unexpected_handler)();
    unexpected_handler set_unexpected(unexpected_handler f) throw(); // C++98
    unexpected_handler set_unexpected(unexpected_handler f) noexcept; // C++11
    void unexpected(); // C++98
    void unexpected() noexcept; // C+0x

    (1)形参:set_unexpected()的参数unexpected_handler是一个函数指针,该指针指向的函数没有形参也没有返回值; ​ (2)作用:使用set_unexpected()后terminate()将会调用set_unexpected()设置的函数而不再使用默认的terminate()函数 ​ (3)注意事项:如果调用set_unexpected()多次,则terminate()采纳最后调用的set_unexpected() ​ (4)举例:myUnexpected()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019)

    3.比set_terminate()更多的规则: unexpected_handler有两种选择: (1)使用默认terminate()终止程序 (2)抛出新异常 选择抛出新异常的结果取决于被unexpected_handler替换的异常和异常规范 (1)如果新抛出的异常与异常规范相匹配,则程序可以正常执行。 (2)如果新抛出的异常与异常规范不匹配,如果异常规范不包含std::bad_exception类型,则调用terminate() (3)如果新抛出的异常与异常规范不匹配,如果异常规范包含std::bad_exception(继承自exception类型并且声明在exception头文件中)类型,则不匹配的异常将会替换为std::bad_exception并处理

    21.11.2 未捕获的异常

    意外的异常逃过第一层阻碍,后面再无try catch可捕获该异常的即为未捕获的异常 1.默认terminate()处理未捕获异常:未捕获的异常会导致程序的终止,程序终止的过程是:首先调用terminate()-->默认情况下terminate()会调用abort() 2.set_terminate()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中:

    typedef void (*terminate_handler)();
    terminate_handler set_terminate(terminate_handler f) throw(); // C++98
    terminate_handler set_terminate(terminate_handler f) noexcept; // C++11
    void terminate(); // C++98
    void terminate() noexcept; // C++11

    (1)形参:set_terminate()的参数terminate_handler是一个函数指针,该指针指向的函数没有形参也没有返回值; ​ (2)作用:使用set_terminate()后terminate()将会调用set_terminate()设置的函数而不再使用默认的terminate()函数 ​ (3)注意事项:如果调用set_terminate()多次,则terminate()采纳最后调用的set_terminate() ​ (4)举例:myQuit()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019)

    21.12 关于异常的注意事项

    1.异常应该嵌入到程序中,而不是附加到程序中 2.使用异常增加了程序所占的存储空间,减慢了程序的运行,但是依然在一定程度上帮助程序员调试程序,减少错误。 3.将异常应用于模板不会很好因为不同的类型可能引发不同的异常,因此在使用时要小心。 4.将异常应用于动态内存分配也会出现一定的问题,因此在使用时要小心。 5.学习C++语言本身会帮助我们学习异常,学习异常也能帮助我们理解C++本身。

    21.13 举例

    代码:

    main.h

    #pragma once
    ​
    #ifndef _MAIN_H
    #define _MAIN_H
    #include "demo.h"
    #include "exc_mean.h"
    #include "sales.h"
    double hmean(double a, double b);
    bool hmean(double a, double b, double* ans);
    double hmean1(double a, double b);
    double hmean2(double a, double b);
    double gmean(double a, double b);
    ​
    // function prototypes
    double hmean(double a, double b);
    double gmean(double a, double b);
    double means(double a, double b);
    void myQuit();
    void my_func() throw(int);
    ​
    void Argh(int*, int) throw(std::out_of_range, std::bad_exception);
    void myUnexpected();
    ​
    double hmean(double a, double b)
    {
        if (a == -b)
        {
            std::cout << "untenable arguments to hmean()\n";
            std::abort();
        }
        else
            return 2.0 * a * b / (a + b);
    }
    ​
    bool hmean(double a, double b, double* ans)
    {
        if (a == -b)
        {
            *ans = DBL_MAX;//返回错误的值
            return false;
        }
        else
        {
            *ans = 2.0 * a * b / (a + b);
            return true;
        }
    }
    ​
    double hmean1(double a, double b)
    {
        if (a == -b)//这里抛出了一个异常
            throw "bad hmean() arguments: a = -b not allowed";
        return 2.0 * a * b / (a + b);
    }
    ​
    double hmean2(double a, double b)
    {
        if (a == -b)
            throw bad_hmean(a, b);
        return 2.0 * a * b / (a + b);
    }
    double gmean(double a, double b)
    {
        if (a < 0 || b < 0)
            throw bad_gmean(a, b);
        return std::sqrt(a * b);
    }
    ​
    double means(double a, double b)
    {
        double am, hm, gm;
        demo d2("found in means()");
        am = (a + b) / 2.0; // arithmetic mean
        try
        {
            hm = hmean2(a, b);
            gm = gmean(a, b);
        }
        catch (bad_hmean& bg) // start of catch block
        {
            bg.mesg();
            std::cout << "Caught in means()\n";
            throw; // rethrows the exception
        }
        d2.show();
        return (am + hm + gm) / 3.0;
    }
    ​
    void myQuit()
    {
        std::cout << "Terminating due to uncaught exception\n";
        exit(5);
    }
    ​
    void my_func() throw(int)
    {
        std::cout << "throw 1************************************" << std::endl;
        throw 1;
    }
    ​
    void myUnexpected()
    {
        throw std::bad_exception(); //or just throw;
    }
    ​
    void Argh(int* a, int b) throw(bad_hmean, std::bad_exception)
    {
        if (a[0] == b)
        {
            throw bad_hmean(1, -1);
        }
        else
        {
            std::cout << "else***" << std::endl;
            throw 1;
        }
    }
    ​
    #endif

    demo.h

    #pragma once
    class demo
    {
    private:
        std::string word;
    public:
        demo(const std::string& str)
        {
            word = str;
            std::cout << "demo " << word << " created\n";
        }
        ~demo()
        {
            std::cout << "demo " << word << " destroyed\n";
        }
        void show() const
        {
            std::cout << "demo " << word << " lives!\n";
        }
    };

    exc_mean.h

    #pragma once
    /*
    这是定义的两个异常类
    */
    #include 
    class bad_hmean
    {
    private:
        double v1;
        double v2;
    public:
        bad_hmean(double a = 0, double b = 0) : v1(a), v2(b) {}
        void mesg();
    };
    inline void bad_hmean::mesg()
    {
        std::cout << "hmean(" << v1 << ", " << v2 << "): "
            << "invalid arguments: a = -b\n";
    }
    ​
    class bad_gmean
    {
    public:
        double v1;
        double v2;
        bad_gmean(double a = 0, double b = 0) : v1(a), v2(b) {}
        const char* mesg();
    };
    inline const char* bad_gmean::mesg()
    {
        return "gmean() arguments should be >= 0\n";
    }

    sales.h

    #pragma once
    // sales.h -- exceptions and inheritance
    #include 
    #include 
    class Sales
    {
    public:
        enum { MONTHS = 12 }; // could be a static const
        class bad_index : public std::logic_error
        {
        private:
            int bi; // bad index value
        public:
            explicit bad_index(int ix,
                const std::string& s = "Index error in Sales object\n");
            int bi_val() const { return bi; }
            virtual ~bad_index() throw() {}
        };
        explicit Sales(int yy = 0);
        Sales(int yy, const double* gr, int n);
        virtual ~Sales() { }
        int Year() const { return year; }
        virtual double operator[](int i) const;
        virtual double& operator[](int i);
    private:
        double gross[MONTHS];
        int year;
    };
    class LabeledSales : public Sales
    {
    public:
        class nbad_index : public Sales::bad_index
        {
        private:
            std::string lbl;
        public:
            nbad_index(const std::string& lb, int ix,
                const std::string& s = "Index error in LabeledSales object\n");
            const std::string& label_val() const { return lbl; }
            virtual ~nbad_index() throw() {}
        };
        explicit LabeledSales(const std::string& lb = "none", int yy = 0);
        LabeledSales(const std::string& lb, int yy, const double* gr, int n);
        virtual ~LabeledSales() { }
        const std::string& Label() const { return label; }
        virtual double operator[](int i) const;
        virtual double& operator[](int i);
    private:
        std::string label;
    };

    main.cpp

    /*
    Project name :          _17Exceptions
    Last modified Date:     2022年3月28日16点31分
    Last Version:           V1.0
    Descriptions:           异常
    什么时候会发生异常:  
        1.打开一个不存在的文件
        2.请求存储空间失败
        3.数组越界等等
    */
    #include
    #include
    #include 
    #include 
    #include
    #include
    #include "main.h"
    ​
    int main()
    {
        /*
        使用abort()函数:
            1.包含在cstdlib头文件中,包含在std命名空间中
            2.当调用abort()函数时,会引发异常并中断程序(Visual Studio 2019);
            3.abort()函数是否刷新缓冲区取决于实现,程序员可以调用exit()以刷新缓冲区
        相关函数:double hmean(double a, double b);
        */
        std::cout << "abort()***********************************************************"<> x >> y)
        {
            z = hmean(x, y);
            std::cout << "Harmonic mean of " << x << " and " << y
                << " is " << z << std::endl;
            std::cout << "Enter next set of numbers : ";
        }
        std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin
        std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁
        /*
        程序员手动处理:
            1.使用指针或引用计算值,如果遇到错误则返回一个错误的计算结果(一般是永远不会用到的值)
            2.程序员偏向于使用指针,因为这样可以做出区分,如果是引用的话赋值语句语法一样就没什么区分
            相关函数:bool hmean(double a, double b, double* ans);
        */
        std::cout << "程序员手动处理****************************************************" << std::endl;
        std::cout << "Enter two numbers: ";
        while (std::cin >> x >> y)
        {
            if (hmean(x, y, &z))
                std::cout << "Harmonic mean of " << x << " and " << y
                << " is " << z << std::endl;
            else
                std::cout << "One value should not be the negative "
                << "of the other - try again.\n";
            std::cout << "Enter next set of numbers : ";
        }
        /*
        * 异常处理机制:
        try catch语句:try检查是否会出现异常,catch捕获异常并处理
        首先执行到try,如果try语句块中的语句不引发异常,则直接跳过所有catch语句块;如果try语句块中的语句引发了异常,
        则检查异常是否与catch语句块括号里的类型一致,如果一致,则执行该catch语句块,如果不一致,则继续检查异常
        是否与catch语句块括号里的类型一致,以此类推,直到找到一致的catch语句块并执行该catch语句块;如果找不到合适
        的catch语句块,则使用默认异常处理方法。
    ​
        本段程序使用的是 throw一个字符串,原则上throw任意数据类型都可,但是一般情况下程序员喜欢throw异常类
        相关函数:double hmean1(double a, double b);
        */
        std::cout << "try catch************************************************************" << std::endl;
        std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin
        std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁
        std::cout << "Enter two numbers: ";
        while (std::cin >> x >> y)
        {
            try { // start of try block
                z = hmean1(x, y);
            } // end of try block
            catch (const char* s) // start of exception handler
            {
                std::cout << s << std::endl;
                std::cout << "Enter a new pair of numbers: ";
                continue;
            } // end of handler
            std::cout << "Harmonic mean of " << x << " and " << y
                << " is " << z << std::endl;
            std::cout << "Enter next set of numbers : ";
        }
        /*
        抛出异常类:
            选择throw异常类的原因:
                1.可以使用不同的异常类型去区分不同的异常;
                2.异常类可以携带异常信息
                3.catch块可以使用异常类携带的异常信息
            相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean
            相关函数:double hmean2(double a, double b);
                      double gmean(double a, double b);
        */
        std::cout << "抛出异常类*********************************************************" << std::endl;
        std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin
        std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁
        std::cout << "Enter two numbers: ";
        while (std::cin >> x >> y)
        {
            try { // start of try block
                z = hmean2(x, y);
                std::cout << "Harmonic mean of " << x << " and " << y
                    << " is " << z << std::endl;
                std::cout << "Geometric mean of " << x << " and " << y
                    << " is " << gmean(x, y) << std::endl;
                std::cout << "Enter next set of numbers : ";
            }// end of try block
            catch (bad_hmean& bg) // start of catch block
            {
                bg.mesg();
                std::cout << "Try again.\n";
                continue;
            }
            catch (bad_gmean& hg)
            {
                std::cout << hg.mesg();
                std::cout << "Values used: " << hg.v1 << ", "
                    << hg.v2 << std::endl;
                std::cout << "Sorry, you don't get to play any more.\n";
                break;
            } // end of catch block
        }
        /*
        异常规范(Exception Specifications):c++11被取消了
        格式:double harm(double a) throw(bad_thing); // may throw bad_thing exception
              double marm(double) throw(); // doesn't throw an exception
        为什么被取消?harm()可能会引发bad_thing异常,也可能是在它调用的函数里面引发的异常,也可能现在不引发异常但是将来系统更新后可能引发异常。
        在C++程序员俱乐部大家一致认为这种异常规范应该取缔。
    ​
        但是C++11允许一种特别的规范:关键词 noexcept
        double marm() noexcept; // marm() doesn't throw an exception
        这表明该函数不会抛出异常。
        
        noexcept()操作符可以报告其操作数是否会引发异常。
        */
        /*
        Unwinding the Stack(展开堆栈):
        程序将调用函数指令的地址(返回地址)放在堆栈上,是先进后出的原则。函数参数、函数生成的自动变量、函数调用新函数的信息都被存储在堆栈上;
        当函数执行完成(return或正常执行完毕)时,其相关的变量、地址都将被弹出,以此类推,最终返回到最初调用函数的地方,每个函数调用都释放掉了占用的内存。
        
        考虑如果函数由于引发异常终止执行,堆栈的内容会发生什么变化,堆栈上关于try与throw之间生成的所有参数、函数地址、自动变量等等都会被弹出;
        这就叫做展开堆栈(Unwinding the Stack);这个机制是为了保证try与throw之间占用的内存被正常释放。
    ​
        如果在函数调用的函数里面没有处理异常,那么要将异常抛到调用该函数的函数中(throw;)处理,直到处理为止,不然会引发断点。
        相关文件:exc_mean.h;相关类:bad_hmean and bad_gmean
        相关函数:double hmean2(double a, double b);
                  double gmean(double a, double b);
                  double means(double a, double b);
        */
        /*
        注意事项:
            1.throw会将异常传递到第一个能够处理该异常的try catch中
            2.在throw异常时,编译器总是创建临时copy,原因是catch(参数)中的参数是引用;
            但是为什么要使用引用而不用对象呢?因为引用可以使用一个基类引用接收继承类的对象,这就允许一个基类引用处理各个继承类的异常了。
            3.使用基类引用处理继承类异常时,如果需要一对一处理(每个继承类的处理方式不一样),则应该将最小的孩子(继承类)放在最前面的catch中,而将最基类的放在最后的catch中。
        */
        std::cout << "Unwinding the Stack******************************************************" << std::endl;
        std::cin.clear();//由于输入类型不匹配然后cin被锁了,所以重置cin
        std::cin.get();//读取最后的那个回车符,防止cin后面继续被锁
        demo d1("found in block in main()");
        std::cout << "Enter two numbers: ";
        while (std::cin >> x >> y)
        {
            try { // start of try block
                z = means(x, y);
                std::cout << "The mean mean of " << x << " and " << y
                    << " is " << z << std::endl;
                std::cout << "Enter next pair: ";
            } // end of try block
            catch (bad_hmean& bg) // start of catch block
            {
                bg.mesg();
                std::cout << "Try again.\n";
                continue;
            }
            catch (bad_gmean& hg)
            {
                std::cout << hg.mesg();
                std::cout << "Values used: " << hg.v1 << ", "
                    << hg.v2 << std::endl;
                std::cout << "Sorry, you don't get to play any more.\n";
                break;
            } // end of catch block
        }
        d1.show();
        /*
        异常类:
            1.exception类:在头文件exception.h或except.h中;这个类是C++中最基础的类,其他异常类都可以继承exception。
                如果不想单独处理每个类,则可以使用exception类的引用catch该异常类。
                基类exception有个what()方法,专门用于返回描述异常类的字符串,每次捕获异常是可以手动显示。
            2.logic_error类:在头文件stdexcept中,继承exception类
            logic_error是以下类的基类:
                    domain_error:引发函数中关于定义域或值域的异常;比如说sin()函数的定义域为(-00,+00),值域为[-1,1],如传递给sin()的值超过定义域,则可以引发域异常
                    invalid_argument:是告诉程序员一个意外的参数传递给了函数。比如只要求传递'0'或'1',如果传递其他字符,则可以引发非法异常。
                    length_error:指示没有足够的空间执行当前操作。比如将一个长度为10的字符串传递给长度为8的字符串,可以引发长度异常。
                    out_of_range:指示索引越界异常
            3.runtime_error类:在头文件stdexcept中,继承exception类
            runtime_error是以下类的基类:
                range_error:数据超出指定范围引发range_error异常
                overflow_error:主要用于整型或浮点类,当计算超过数据类型可表示的最大值时引发overflow_error异常
                underflow_error:主要用于浮点类型数据,原因是浮点类型数据类型有最小可表示的数据,如果计算超过最小值,则引发underflow_error异常
            4.bad_alloc异常类:用于指示在内存分配时可能发生的问题,共有继承自exception类
                如果不想抛出bad_alloc异常,则可以使用new(std::nothrow)的方式,使用方法见示例
        */
        std::cout << "异常类*********************************************************" << std::endl;
        std::cout << "throw bad_alloc************************************************" << std::endl;
        struct Big
        {
            double stuff[20000];
        };
        Big* pb;
        try {
            std::cout << "Trying to get a big block of memory:\n";
            pb = new Big[10000]; // 1,600,000,000 bytes
            std::cout << "Got past the new request:\n";
        }
        catch (std::bad_alloc& ba)
        {
            std::cout << "Caught the exception!\n";
            std::cout << ba.what() << std::endl;
            exit(EXIT_FAILURE);
        }
        std::cout << "Memory successfully allocated\n";
        pb[0].stuff[0] = 4;
        std::cout << pb[0].stuff[0] << std::endl;
        delete[] pb;
        std::cout << "new(std::nothrow)************************************************" << std::endl;
        pb = new(std::nothrow) Big[10000]; // 1,600,000,000 bytes
        if (pb == 0)
        {
            std::cout << "Could not allocate memory. Bye.\n";
            exit(EXIT_FAILURE);
        }
        /*
        异常与继承:见头文件sales.h和实现文件sales.cpp
            1.你可以继承一个异常类
            2.你可以将异常类嵌套到别的类中
            3.嵌套类也可以被继承
        */
        std::cout << "异常与继承*********************************************************" << std::endl;
        double vals1[12] =
        {
        1220, 1100, 1122, 2212, 1232, 2334,
        2884, 2393, 3302, 2922, 3002, 3544
        };
        double vals2[12] =
        {
        12, 11, 22, 21, 32, 34,
        28, 29, 33, 29, 32, 35
        };
        Sales sales1(2011, vals1, 12);
        LabeledSales sales2("Blogstar", 2012, vals2, 12);
        std::cout << "First try block:\n";
        try
        {
            int i;
            std::cout << "Year = " << sales1.Year() << std::endl;
            for (i = 0; i < 12; ++i)
            {
                std::cout << sales1[i] << ' ';
                if (i % 6 == 5)
                    std::cout << std::endl;
            }
            std::cout << "Year = " << sales2.Year() << std::endl;
            std::cout << "Label = " << sales2.Label() << std::endl;
            for (i = 0; i <= 12; ++i)
            {
                std::cout << sales2[i] << ' ';
                if (i % 6 == 5)
                    std::cout << std::endl;
            }
            std::cout << "End of try block 1.\n";
        }
        catch (LabeledSales::nbad_index& bad)
        {
            std::cout << bad.what();
            std::cout << "Company: " << bad.label_val() << std::endl;
            std::cout << "bad index: " << bad.bi_val() << std::endl;
        }
        catch (Sales::bad_index& bad)
        {
            std::cout << bad.what();
            std::cout << "bad index: " << bad.bi_val() << std::endl;
        }
        std::cout << "\nNext try block:\n";
        try
        {
            sales2[2] = 37.5;
            sales1[20] = 23345;
            std::cout << "End of try block 2.\n";
        }
        catch (LabeledSales::nbad_index& bad)
        {
            std::cout << bad.what();
            std::cout << "Company: " << bad.label_val() << std::endl;
            std::cout << "bad index: " << bad.bi_val() << std::endl;
        }
        catch (Sales::bad_index& bad)
        {
            std::cout << bad.what();
            std::cout << "bad index: " << bad.bi_val() << std::endl;
        }
        /*
        当异常无法控制的时候:主要是针对异常规范来说的,由于它在C++11中已经被取消了,所以可能用处不是很大(了解即可)
            意外的异常:就是在异常规范中没有匹配的异常就叫做意外的异常
                1.默认unexpected()处理意外的异常:首先调用unexpected()-->调用terminate()-->调用abort().
                2.set_unexpected()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中:
                    typedef void (*unexpected_handler)();
                    unexpected_handler set_unexpected(unexpected_handler f) throw(); // C++98
                    unexpected_handler set_unexpected(unexpected_handler f) noexcept; // C++11
                    void unexpected(); // C++98
                    void unexpected() noexcept; // C+0x
                    (1)形参:set_unexpected()的参数unexpected_handler是一个函数指针,该指针指向的函数没有形参也没有返回值;
                    (2)作用:使用set_unexpected()后terminate()将会调用set_unexpected()设置的函数而不再使用默认的terminate()函数
                    (3)注意事项:如果调用set_unexpected()多次,则terminate()采纳最后调用的set_unexpected()
                    (4)举例:myUnexpected()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019)
                3.比set_terminate()更多的规则:
                    unexpected_handler有两种选择:
                    (1)使用默认terminate()终止程序
                    (2)抛出新异常
                    选择抛出新异常的结果取决于被unexpected_handler替换的异常和异常规范
                    (1)如果新抛出的异常与异常规范相匹配,则程序可以正常执行。
                    (2)如果新抛出的异常与异常规范不匹配,如果异常规范不包含std::bad_exception类型,则调用terminate()
                    (3)如果新抛出的异常与异常规范不匹配,如果异常规范包含std::bad_exception(继承自exception类型并且声明在exception头文件中)类型,则不匹配的异常将会替换为std::bad_exception并处理
            未捕获的异常:意外的异常逃过第一层阻碍,后面再无try catch可捕获该异常的即为未捕获的异常
                1.默认terminate()处理未捕获异常:未捕获的异常会导致程序的终止,程序终止的过程是:首先调用terminate()-->默认情况下terminate()会调用abort()
                2.set_terminate()修改terminate()函数:这两个函数都在头文件exception中,在std命名空间中:
                    typedef void (*terminate_handler)();
                    terminate_handler set_terminate(terminate_handler f) throw(); // C++98
                    terminate_handler set_terminate(terminate_handler f) noexcept; // C++11
                    void terminate(); // C++98
                    void terminate() noexcept; // C++11
                    (1)形参:set_terminate()的参数terminate_handler是一个函数指针,该指针指向的函数没有形参也没有返回值;
                    (2)作用:使用set_terminate()后terminate()将会调用set_terminate()设置的函数而不再使用默认的terminate()函数
                    (3)注意事项:如果调用set_terminate()多次,则terminate()采纳最后调用的set_terminate()
                    (4)举例:myQuit()函数作为terminate()调用的函数---不管怎么样都会引发异常导致程序终止(Visual 2019)
        */
        //对于下面的语句,我的系统就是会引发“0x765EB922 处(位于 _17Exceptions.exe 中)有未经处理的异常: Microsoft C++ 异常: int,位于内存位置 0x00FDF42C 处。”异常,我也不知道为什么
        /*std::set_terminate(myQuit);
        my_func();*/
        //对于下面的语句,我的系统也引发了“0x765EB922 处(位于 _17Exceptions.exe 中)有未经处理的异常: Microsoft C++ 异常: int,位于内存位置 0x0038F490 处。”异常,我也不知道什么
        /*set_unexpected(myUnexpected);
        try {
            int a[2] = { 1,2 };
            Argh(a, 2);
        }
        catch (bad_hmean& ex)
        {
            std::cout << "bad_hmean*****************************" << std::endl;
        }
        catch (std::bad_exception& ex)
        {
            std::cout << "bad_exception*****************************" << std::endl;
        }*/
    ​
        /*
        关于异常的注意事项:
            1.异常应该嵌入到程序中,而不是附加到程序中
            2.使用异常增加了程序所占的存储空间,减慢了程序的运行,但是依然在一定程度上帮助程序员调试程序,减少错误。
            3.将异常应用于模板不会很好因为不同的类型可能引发不同的异常,因此在使用时要小心。
            4.将异常应用于动态内存分配也会出现一定的问题,因此在使用时要小心。
            5.学习C++语言本身会帮助我们学习异常,学习异常也能帮助我们理解C++本身。
        */
        return 0;
    }

    运行结果:

    abort()***********************************************************
    Enter two numbers: 99 88
    Harmonic mean of 99 and 88 is 93.1765
    Enter next set of numbers : q
    程序员手动处理****************************************************
    Enter two numbers: 99 88
    Harmonic mean of 99 and 88 is 93.1765
    Enter next set of numbers : q
    try catch************************************************************
    Enter two numbers: 99 88
    Harmonic mean of 99 and 88 is 93.1765
    Enter next set of numbers : q
    抛出异常类*********************************************************
    Enter two numbers: 99 88
    Harmonic mean of 99 and 88 is 93.1765
    Geometric mean of 99 and 88 is 93.3381
    Enter next set of numbers : q
    Unwinding the Stack******************************************************
    demo found in block in main() created
    Enter two numbers: 99 88
    demo found in means() created
    demo found in means() lives!
    demo found in means() destroyed
    The mean mean of 99 and 88 is 93.3382
    Enter next pair: q
    demo found in block in main() lives!
    异常类*********************************************************
    throw bad_alloc************************************************
    Trying to get a big block of memory:
    Caught the exception!
    bad allocation
    ​
    D:\Prj\_C++Self\_17Exceptions\Debug\_17Exceptions.exe (进程 8964)已退出,代码为 1。
    要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
    按任意键关闭此窗口. . .
  • 相关阅读:
    Flutter App混淆加固、保护与优化原理
    [附源码]java毕业设计巴音学院公共体育课选课系统
    【3D建模制作技巧分享】zbrush贴图映射小技巧
    软件架构风格
    JavaScript高级
    Android 12.0 Launcher3单层app列表页排序功能实现
    2021年中国大学生程序设计竞赛女生专场-ADGIK
    Table ‘mysql.proc‘ doesn‘t exist
    一加疑违反GPL协议,迟迟不公布OxygenOS 12源码惹怒网友
    在ubuntu20下构建rtpengine
  • 原文地址:https://blog.csdn.net/weixin_44410704/article/details/128002186