C++异常学习(二)C++11新特性之前的异常总结

1. C的异常处理。

         在C语言的世界中,对错误的处理总是围绕着两种方法:一是使用整型的返回值标识错误;二是使用errno宏(可以简单的理解为一个全局整型变量)去记录错误。当然C++中仍然是可以用这两种方法的。

        这两种方法最大的缺陷就是会出现不一致问题。例如有些函数返回1表示成功,返回0表示出错;而有些函数返回0表示成功,返回非0表示出错。

        还有一个缺点就是函数的返回值只有一个,你通过函数的返回值表示错误代码,那么函数就不能返回其他的值。当然,你也可以通过指针或者C++的引用来返回另外的值,但是这样可能会令你的程序略微晦涩难懂。
 

2. c++异常机制比C语言异常处理的优势

  函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结果造成程序莫名其妙的终止或出现错误的结果。

  整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。

  整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。

  异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,不需要每级函数都处理。

 

3.C++异常的基本概念:

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

    在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容知道 “出现了什么错误”

 

异常基本语法:

  1. 将可能抛出异常的程序段放到try块之中。
  2. 若有异常则通过throw操作创建一个异常对象并抛出。(向上抛出,除非遇到catch,否则其他的代码不运行了)
  3. 如果在try段执行期间没有引起异常,那么跟在try后面的catch字句就不会执行。
  4. catch子句会根据出现的先后顺序被检查,匹配的catch语句捕获并处理异常(或继续抛出异常)

catch(对应捕获的类型),抛出的异常  可以是自定义的数据类型。任意的类型  catch(…)。

  1. 如果匹配的处理未找到,则运行函数terminate将自动被调用,其缺省功能调用abort终止程序。
  2. 处理不了的异常,或者当前位置不想去处理,catch(){}函数体中使用throw;向上抛。
  3. 异常机制和函数机制互不干涉,但是catch捕捉方式是通过严格类型匹配。

 

 

4. 栈的解旋(unwinding).

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).

//throw异常将栈区对象清空,不会运行throw后面的代码,之后直接跳转到上一个函数。直到遇到catch(){}函数体继续运行

//堆区程序员自己管理,堆区无效

//try之前栈上内容的不会析构

 

5.

  • 异常的接口声明,只允许抛出指定类型的异常
  • 函数声明后面 +  throw (类型)  
  • 如果()中空的,不允许抛出任何类型的异常,一个不抛任何类型异常的函数可声明为:void func() throw()
  • 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。

 

Qt and Linux 可以使用异常接口声明! visual studio中无效。

 

6.异常变量的生命周期

类信息如下:

class MyException

{

public:

    MyException()

    {

        cout << "默认构造函数调用" << endl;

    }

    MyException(const MyException& p)

    {

        cout << "拷贝构造函数调用" << endl;

    }

    ~MyException()

    {

        cout << "析构函数调用" << endl;

    }

};

 

调用方式一:catch(值){}

void doWork()

{

    throw  MyException();  //throw一个匿名对象

}

void test01()

{

    try

    {

        doWork();

        cout << sss << endl; //doWork()函数中有异常抛出,则这句话不会执行。

    }

    catch (MyException s)

    {

        cout << "MyException s 异常被捕获" << endl;

        cout << 111 << endl;

    }

    cout << 222 << endl;

}

默 认 构 造 函 数 调 用

拷 贝 构 造 函 数 调 用

MyException s 异 常 被 捕 获

111

析 构 函 数 调 用

析 构 函 数 调 用

222

1catch的是值(非引用或者非指针):

由结果可知,也就是throw的变量A生命周期是throw开始到catch的函数体结束,另外catch到的

变量是拷贝构造函数构造的副本B,继续走catch(){}的函数体,当函数体结束,则将throw的变量A析构,

同时析构刚才的副本B。

如果此时catch(){}函数体中没有处理异常而是继续抛出,则上一次throw的变量A会被析构,而刚才的副本B不会析构,

将刚才的副本B继续抛出,当遇到下一个catch(){}函数时,以副本B调用拷贝构造函数制造另一个副本C,catch(){}的函数体结束,则将throw的副本B析构,同时析构刚才的副本C。依次类推)

 

上面throw的是匿名对象,如果throw的是局部变量如:

void doWork()

{

    MyException m;  //创建一个局部变量

    throw  m;     //throw的时候会调用拷贝构造函数,throw的其实是m的副本,原m在函数体结束后就会被释放(局部变量同时也是栈解旋)

}

值传递总结:如果throw的是一个匿名对象A,该匿名对象的生命周期为throw到遇到的第一个catch(){}函数体结束。如果throw的是一个局部变量,如上面,则其实throw的是局部变量的副本B,这个副本的生命周期为遇到的第一个catch(){}函数体结束。所以throw可以延长被抛掷的变量的生命周期,比如刚才的匿名对象A和局部变量的副本B;

catch(){}中()里面的变量,形参生命周期是创建(拷贝构造函数)到函数体结束。

 

调用方式二:catch(引用){}

void doWork()

{

    throw  MyException();

}

void test01()

{

    try

    {

        doWork();

        cout << 123 << endl;  //doWork()函数中有异常抛出,则这句话不会执行。

    }

    catch (MyException& s)

    {

        cout << "MyException s 异常被捕获" << endl;

    }

}

默 认 构 造 函 数 调 用

MyException s 异 常 被 捕 获

析 构 函 数 调 用

2catch的是引用:

不会产生副本,catch(){}函数体结束后throw的变量被析构。最好的catch方式。也是系统的异常catch采用的方式。

 

调用方式三:catch(指向栈区的指针){}

void doWork()

{

    throw  &MyException();  //throw一个栈区地址。本函数体结束之后就会被释放

}

void test01()

{

    try

    {

        doWork();

        cout << 123 << endl;  //doWork()函数中有异常抛出,则这句话不会执行。

    }

    catch (MyException* s)

    {

        //s->(...)  由于被析构了,此时利用s指针调用就是非法操作,虽然可以调用成功

        //是因为系统做了优化,不推荐这样做。

        cout << "MyException s 异常被捕获" << endl;

    }

}

默 认 构 造 函 数 调 用

析 构 函 数 调 用

MyException s 异 常 被 捕 获

3当catch是指向栈区的指针,会导致匿名对象提前被释放,之后操作指向栈区的指针就是非法操作,虽然可以成功原因是系统做了优化,不推荐这样做。

 

调用方式四:catch(指向堆区的指针){};

//指向堆区的指针

void doWork()

{

    throw  new MyException();  //throw一个堆区地址。需要自己管理释放

}

void test01()

{

    try

    {

        doWork();

        cout << 123 << endl;  //doWork()函数中有异常抛出,则这句话不会执行。

    }

    catch (MyException* s)

    {

        //s->(...) 合法操作

        cout << "MyException s 异常被捕获" << endl;

        delete s;  //缺点需要自己释放堆区空间

    }

}

默 认 构 造 函 数 调 用

我 的 异 常 捕 获

析 构 函 数 调 用

4当catch是指向堆区的指针,缺点是需要自己管理堆区指针,所以最好的方法是catch(引用){} ;

 

 

7.异常变量的多态使用

  • 写一个异常的基类  基类中有个纯虚函数  printError
  • 派生类中 重写 纯虚函数 ,在抛出异常的时候 可以抛出各种类型的异常对象
  • 捕获异常的时候,用父类的指针或者引用接受即可

 

 

8. 使用系统提供的标准异常类

  • 引入头文件   stdexcept
  • throw  out_of_range(“aaaa”); //抛出异常
  • catch(out_of_range & e )    cout << e.what(); //类型相同
  • catch ( exception & e){cout << e.what() << endl;}  //相互继承类型,多态的使用

 

 

9.编写自己的异常类

  • 继承  exception 类
  • 重写  虚析构函数    虚what函数
  • 内部维护一个字符串     用于保存用户输入的字符串
  • Stirng变量调用.c_str()可以将string转化为char*类型数据
  • 将char*转换为string,可以用string(char*)调用string的有参构造函数

class myException :public exception

{

public:

 

    myException( const string str)

    {

        this->m_Error = str;

    }

    myException(const char * str)

    {

        this->m_Error = string( str);   //利用char*调用string的构造函数

    }  

    virtual  ~myException()

    {

   

    }

    virtual const char *  what() const

    {

        // string  转 char *

        return this->m_Error.c_str();

    }

    string m_Error; //保存用户提供的字符串

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值