C++异常处理机制

来源:互联网 发布:svg.js 对比 编辑:程序博客网 时间:2024/06/09 19:47

1.所谓异常是指在程序运行的过程中发生的一些错误、异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等)

2.Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:
让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)
调用者能够处理这个问题。也就是《C++ primer》中说的:将问题检测和问题处理相分离。

异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的try块,依次匹配catch语句中的异常对象(只进行类型匹配,catch参数有时在catch语句中并不会使用到)。若匹配成功,则执行catch块内的异常处理语句,然后接着执行try…catch…块之后的代码。如果在当前的try…catch…块内找不到匹配该异常对象的catch语句,则由更外层的try…catch…块来处理该异常;如果当前函数内所有的try…catch…块都不能匹配该异常,则递归回退到调用栈的上一层去处理该异常。如果一直退到主函数main()都不能处理该异常,则调用系统函数terminate()终止程序。

一个最为简单的try...catch...例子:void test(){    int age;    while (cin>>age)    {        try         {            if (age<0 ||age > 200)            {                throw age;                cout<<"有异常发生"<<endl;/*上面的cout<<"有异常发生"<<endl;语句并不会被执行到;当执行一个throw语句时,跟在throw语句之后的语句将不再被执行,throw语句的语法有点类似于return,因此导致在调用栈上的函数可能提早退出。*/            }        //其他操作......                cout<<"当前输入的年龄是:"<<age<<endl;        }catch(int a)        {            cout<<"a = "<<a<<endl;        }       }}throw是个关键字,与抛出表达式构成了throw语句。其语法为:throw 表达式;throw语句必须包含在try块中,也可以是被包含在调用栈的外层函数的try块中,如下面写的这些异常处理机制采用的都是这种形式;`try-catch用法格式try{    // 可能抛出异常的语句}catch(exceptionType variable){    // 处理异常的语句}

如果不对运行的程序进行异常机制处理,则程序则会执行默认的操作,终止程序(也就是所谓的
程序奔溃)、不终止程序,但是程序的最终执行结果不是我们预期的或所想要的结果;

如:#include <iostream>#include<stdlib.h>using namespace std;void myFunc(int a,int b){    /*希望得到a/b的结果(若b!=0,则正常运行,    若b=0,则发生致命的错误)*/    cout<<a/b<<endl;}void test(){    int a = 10;    int b = 0;    myFunc(a,b);}int main(){    test();    system("pause");    return 0;}/*执行该程序后则会出现程序直接奔溃,因为b=0;不满足除数不等于0条件,因此程序执行的默认动作就是直接奔溃、终止程序;若一旦程序有bug或重大错误就采取终止程序,直接奔溃,这不是最好最理想的情况;最为合理而又恰当的措施和手段是当程序中出现错误和异常的时候,抛出该异常,然后通过捕获该异常并进行处理,而不是一言不合就奔溃宕机;*//*若将该程序采用异常处理机制,当b=0时,程序不会奔溃,而是捕获该异常并且打印出异常错误的值,cout<<"b:"<<b<<endl; 得到b = 0;如下:*/void myFunc(int a,int b){    /*希望得到a/b的结果(若b!=0,则正常    运行,若b=0,则发生致命的错误)*/    if (b == 0)    {        throw 0;    }    cout<<a/b<<endl;}void test(){    int a = 10;    int b = 0;    try    {        myFunc(a,b);    }catch(int b)    {        cout<<"b = "<<b<<endl;    }}

3.异常的基本语法
若有异常则通过throw操作创建一个异常对象并抛出。
将可能抛出异常的程序段放到try块之中。
如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)
如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
处理不了的异常,可以在catch的最后一个分支,使用throw,向上抛。

c++异常处理使得异常的引发和异常的处理不必在一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。

异常机制和函数机制互不干涉,但是捕捉方式是通过严格类型匹配。

如:void myFunc(const char *s){    if (NULL == s)    {        throw "字符串为空"; //为字符常量    }    cout<<s<<endl;}void test(){    char *src = NULL;    try     {        myFunc(src);    }catch(cosnt char s)    {        cout<<s<<endl;    }}/*该示例中,当执行myFunc函数时候检测到字符串为空,所以抛出一个字符串常量的异常,因此捕捉函数中也必须是const char */char *类型,若为char 类型,则程序也会直接奔溃、终止;*///修改为char *后程序完美运行:void myFunc(const char *s){    if (NULL == s)    {        throw "字符串为空"; //为字符常量    }    cout<<s<<endl;}void test(){    char *src = NULL;    try     {        myFunc(src);    }catch(cosnt char *s)    {        cout<<s<<endl;    }}

4.异常接口说明

· 为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型,如:void func() throw(int ,char,double);这个函数只能抛出类型为int,char,double的异常;void test() throw(int ,char ,double){    int age;    while (cin>>age)    {        try         {            if (age<0 ||age > 200)            {                throw age;                cout<<"hahdljff"<<endl;            }        //其他操作......        cout<<"当前输入的年龄是:"<<age<<endl;        }catch(int a)        {            cout<<"抛出int类型异常:a = "<<a<<endl;        }catch(char b)        {            cout<<"抛出char类型异常:b = "<<b<<endl;        }catch(double c)        {            cout<<"抛出double类型异常:c = "<<c<<endl;        }    }}· 一个不抛任何类型异常的函数可声明为;void func()throw();· 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。· 可以抛出任何异常;void func() throw(...);· 可以捕获任何异常;catch(...){}

5.处理定义抛出用户自定义的类型外,C++标准库中也提供了很多的异常类,它们是通过类继承组织起来的。异常类继承层级结构如下图:
这里写图片描述

exception为所有异常类的基类;其类形式如下://exception类为于#include<exception>头文件中;class exception{public:        //构造函数    exception() throw();            //拷贝构造函数/复制构造函数    exception(const exception &) throw();           //操作符重载    exception& operator=(const exception& e) throw();        //虚析构函数  --->是很多异常类的基类,支持多态    virtual ~exception() throw();        //虚函数--虚函数what能够返回和识别异常的字符串    virtual const char *what() const throw();   protected:};/**********************************/exception基类异常名称                说明logic_error         逻辑错误。runtime_error       运行时错误。bad_alloc           使用 newnew[ ] 分配内存失败时抛出的异常。bad_typeid          使用 typeid 操作一个 NULL 指针,而且该指针是带有虚函数的类,这时抛出 bad_typeid 异常。bad_cast            使用 dynamic_cast 转换失败时抛出的异常。ios_base::failure   io 过程中出现的异常。bad_exception       这是个特殊的异常,如果函数的异常列表里声明了 bad_exception 异常,当函数内部抛出了异常列表中没有的异常时,如果调用的 unexpected() 函数中抛出了异常,不论什么类型,都会被替换为 bad_exception 类型。 /**********************************/ logic_error    exception的派生类: 异常名称            说  明length_error    试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作。domain_error    参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数。out_of_range    超出有效范围。invalid_argument    参数不合适。在标准库中,当利用string对象构造 bitset 时,而 string 中的字符不是 01 的时候,抛出该异常。/**********************************/runtime_error       exception的派生类: 异常名称                说明range_error         计算结果超出了有意义的值域范围。overflow_error      算术计算上溢。underflow_error     算术计算下溢。---------------------------------------------------------------------------------------------------------示例代码说明out_of_range异常的使用:class People{public:    People(){}    People(string naem,int age)    {        this->m_name = m_name;        if (age <0 || age>150)        {            throw out_of_range("年龄应该在0~150之间");        }        this->m_age = age;    }    ~People(){}protected:    string m_name;    int m_age;};void test(){    try    {        People p1("hello",-1);    }catch(out_of_range &o)//可以使用out_of_range o形式,但是这里会影响程序的效率,以为会执行一次拷贝构造函数    {        cout<<o.what()<<endl;    }}

6.用户编写自己的异常类
① 标准库中的异常是有限的;
② 在自己的异常类中,可以添加自己的信息。(标准库中的异常类值允许设置一个用来描述异常的字符串)。
2. 如何编写自己的异常类?
① 建议自己的异常类要继承标准异常类。因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。
② 当继承标准异常类时,应该重载父类的what函数和虚析构函数。
③ 因为栈展开的过程中,要复制异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数。

#include <stdlib.h>#include <exception>#include <stdexcept>#include <iostream>using namespace std;class Myout_of_range{public:    Myout_of_range(){}     Myout_of_range(const char *name)    {        this->pName = new char[strlen(name)+1];        strcpy(this->pName,name);    }    ~Myout_of_range()     {        if (NULL != this->pName)        {            delete[] this->pName;            this->pName = NULL;        }    }    virtual const char* what() const {return this->pName;} private:    char *pName;};class Student{public:    Student(string name,int age)    {        this->m_name = name;        if (age < 0||age > 150)        {            throw Myout_of_range("年龄应该在0~150之间");        }        this->m_age = age;    }    Student(){}    ~Student(){}private:    string m_name;    int m_age;};void test(){    try    {        Student s1("hello",-10);    }catch(Myout_of_range& my)    {        cout<<my.what()<<endl;    }}int main(){    test();    system("pause");    return 0;}
1 0
原创粉丝点击