前言

  1. 异常是一种程序控制机制,与函数机制独立和互补:
    函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.
  2. 异常设计目的:
    栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
    异常设计出来之后,却发现在错误处理方面获得了最大的好处。

异常基本语法

  1. 若有异常则通过throw操作创建一个异常对象并抛掷。
  2. 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
  3. 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
  4. catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
  5. 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。
  6. 处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。

这是一个C++中异常的抛出机制:

#include <iostream>
using namespace std;
void divde(int x, int y)
{if (y == 0){throw x;  // 当y == 0时,抛出异常}cout << "y/x的结果" << x/y << endl;
}int main()
{try{divde(10, 2);divde(10, 0);}catch (int e){cout << e << "被零整数" << endl;}catch (...) //其他种类的异常处理{cout << "未知异常" << endl;}return 0;
}

异常可以不处理,继续外抛。

#include <iostream>
using namespace std;
void divde(int x, int y)
{if (y == 0){throw x;  // 当y == 0时,抛出异常}cout << "y/x的结果" << x/y << endl;
}
void mydivde(int x, int y)
{try {divde(x, y);}catch (...){cout << "我不处理异常" << endl;throw;}
}
int main()
{try{mydivde(100, 0);}catch (int e){cout << e << "被零整数" << endl;}catch (...) //其他种类的异常处理{cout << "未知异常" << endl;}return 0;
}
#include <iostream>
using namespace std;void testFunction1() {if (1) throw 1;
}
void testFunction2() {try {testFunction1();}catch (char) /*异常时严格的按照类型匹配的*/{cout << "期待的异常char" << endl;}
}int main()
{testFunction2();return 0;
}

throw 1将穿透函数testFunction1,testFunction2和main,抵达系统的最后一道防线——激发terminate函数(C++中,异常不可以忽略,当异常找不到匹配的catch字句时,会调用系统的库函数terminate(),该函数调用引起运行终止的abort函数。)最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.
修改系统默认行为:

  • 可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:
  • void myTerminate(){cout<<“HereIsMyTerminate\n”;}
  • set_terminate(myTerminate);
  • set_terminate函数在头文件exception中声明,参数为函数指针void(*)().

栈解旋(unwinding)

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

#include <iostream>
using namespace std;class MyException {};class Test
{public:Test(int a = 0, int b = 0){this->a = a;this->b = b;cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;}void printT(){cout << "a:" << a << " b: " << b << endl;}~Test(){cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;}
private:int a;int b;
};void myFunc() throw (MyException)
{Test t1(1, 2);Test t2(2, 3);cout << "要发生异常" << endl;throw 1; }int main()
{//异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,//都会被自动析构。析构的顺序与构造的顺序相反。//这一过程称为栈的解旋(unwinding)try{myFunc();}catch (int e) {cout << "接收到int类型异常" << endl;}catch (...){cout << "未知类型异常" << endl;}return 0;
}

测试结果:这个也很好理解,throw异常相当这个函数要返回了,把异常信息返回给捕获函数。那么就要把在这个函数压栈产生的变量给进行栈解旋。

异常接口声明

  1. 为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
  2. 如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如: void func();
  3. 一个不抛掷任何类型异常的函数可以声明为:void func() throw();
  4. 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。

异常类型和异常变量的生命周期

  1. throw的异常是有类型的,可以使,数字、字符串、类对象。
  2. throw的异常是有类型的,catch严格按照类型进行匹配。
  3. 注意 异常对象的内存模型

下面我比较传统的错误处理机制和C++的异常处理机制
传统的错误处理机制

#include <iostream>
using namespace std;
// 传统的错误处理机制
int my_strcpu(char *dest, char *soucre)
{   if (dest == nullptr){   return 1;}if (soucre == nullptr){return 2;}// 比如做一个copy的数据限定if (*soucre == 'a'){return 3;}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';return 0;
}int main()
{   int  ret;char destBuff[1024] = {0};char sourceBuff[] = "123456789";ret = my_strcpu(destBuff,sourceBuff);if (ret != 0){switch (ret){case 1: cout << "destBuff is error" << endl;break;case 2: cout << "sourceBuff is error" << endl;break;case 3: cout << "copy is error" << endl;break;default:cout << "未知错误" << endl;break;}}cout << destBuff << endl;return 0;
}

异常处理机制

#include <iostream>
using namespace std;
// C++的异常错误处理机制
class  BadDest {};
class  BadSource {};
class  BadCopy {};
void my_strcpu(char* dest, char* soucre)
{if (dest == nullptr){throw BadDest();}if (soucre == nullptr){throw BadSource();}// 比如做一个copy的数据限定if (*soucre == 'a'){throw BadCopy();}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';
}int main()
{int  ret;char destBuff[1024] = { 0 };char sourceBuff[] = "a123456789";try{my_strcpu(destBuff, sourceBuff);}catch (BadDest e){cout << "destBuff is error" << endl;}catch (BadSource e){cout << "sourceBuff is error" << endl;}catch (BadCopy e){cout << "copy is error" << endl;}catch (...){cout << "未知错误" << endl;}cout << destBuff << endl;return 0;
}

这个异常会throw一个匿名对象,那么这个有一个问题就值得谈论?这个匿名对象是copy给e还是e就是这个匿名对象了?先说结论:这个匿名对象是拷贝给e的。
测试代码

#include <iostream>
using namespace std;
// C++的异常错误处理机制
class  BadDest {};
class  BadSource {};
class  BadCopy
{public:BadCopy() {cout << "BadCopy 构造函数 do" << endl;}BadCopy(const BadCopy &obj){cout << "BadCopy 拷贝构造函数 do" << endl;}~BadCopy(){cout << "BadCopy 析构函数 do" << endl;}
};
void my_strcpu(char* dest, char* soucre)
{if (dest == nullptr){throw BadDest();}if (soucre == nullptr){throw BadSource();}// 比如做一个copy的数据限定if (*soucre == 'a'){   cout << "开始throw BadCopy" << endl;throw BadCopy();}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';
}int main()
{int  ret;char destBuff[1024] = { 0 };char sourceBuff[] = "a123456789";try{my_strcpu(destBuff, sourceBuff);}catch (BadDest e){cout << "destBuff is error" << endl;}catch (BadSource e){cout << "sourceBuff is error" << endl;}catch (BadCopy e){cout << "copy is error" << endl;}catch (...){cout << "未知错误" << endl;}cout << destBuff << endl;return 0;
}

测试结果如下:

进一步讨论如果这个是引用的话,匿名对象是copy还是直接使用throw出来的那个对象。

#include <iostream>
using namespace std;
// C++的异常错误处理机制
class  BadDest {};
class  BadSource {};
class  BadCopy
{public:BadCopy() {cout << "BadCopy 构造函数 do" << endl;}BadCopy(const BadCopy &obj){cout << "BadCopy 拷贝构造函数 do" << endl;}~BadCopy(){cout << "BadCopy 析构函数 do" << endl;}
};
void my_strcpu(char* dest, char* soucre)
{if (dest == nullptr){throw BadDest();}if (soucre == nullptr){throw BadSource();}// 比如做一个copy的数据限定if (*soucre == 'a'){   cout << "开始throw BadCopy" << endl;throw BadCopy();}while (*soucre != '\0'){*dest++ = *soucre++;}*dest = '\0';
}int main()
{int  ret;char destBuff[1024] = { 0 };char sourceBuff[] = "a123456789";try{my_strcpu(destBuff, sourceBuff);}catch (BadDest e){cout << "destBuff is error" << endl;}catch (BadSource e){cout << "sourceBuff is error" << endl;}catch (BadCopy &e){cout << "copy is error" << endl;}catch (...){cout << "未知错误" << endl;}cout << destBuff << endl;return 0;
}

测试结果:

更进一步讨论如果这个是引用的话,匿名对象是copy还是直接使用throw出来的那个对象。

大家可以试试,你使用指针的类型接收上述代码throw的异常是接受不到,还有一个小细节,catch (BadCopy &e)catch (BadCopy e)不可以同时存在,但是catch (BadCopy &e)catch (BadCopy *e)可以同时存在,catch (BadCopy e)catch (BadCopy *e)也是可以的。那想要让catch (BadCopy *e)可以接受的到,那么throw就必须抛出一个地址,哪只要代码这么 throw &(BadCopy());改一下就可以了

异常生命周期总结:

结论1:如果在接受异常的时候使用一个异常变量,则copy构造异常变量。
结论2:如果在接受异常的时候使用一个异常引用,会使用throw的那个匿名对象,所以引用e还是那个匿名对象
结论3:指针和引用/变量可以同时存在,但是引用和变量不能写在一块。
结论4:若想使用指针接受异常,throw的值是一个地址,这样子写还可能导致野指针的问题!!!地址throw出来后析构了,但是指针还没有指向nullptr。好的方法是throw new (BadCopy());但是记得delete e在捕获中避免内存泄漏。

最好的方法:引用接,简单方便安全

异常的层次结构(继承在异常中的应用)

  • 异常是类 – 创建自己的异常类
  • 异常派生
  • 异常中的数据:数据成员
  • 按引用传递异常
  • 在异常中使用虚函数
    案例:
    案例:设计一个数组类 MyArray,重载[]操作,
    数组初始化时,对数组的个数进行有效检查
    1) index<0 抛出异常eNegative
    2) index = 0 抛出异常 eZero
    3) index>1000抛出异常eTooBig
    4) index<10 抛出异常eTooSmall
    5) eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
#include <iostream>
using namespace std;class MyArrary
{public:MyArrary(int arrayLength);~MyArrary();int& operator [](int index);int getArraryLength();
//异常处理类
public:class eSize{public:eSize(int size){mySize = size;}virtual   void printError() {cout << "size" << mySize << endl;}protected:int mySize;};class eNegative : public eSize{public:eNegative(int size) : eSize(size){}virtual    void printError(){cout << "eNegative is error ArrayLength is " << mySize << endl;}};class eZero : public eSize{public:eZero(int size) : eSize(size){}virtual    void printError(){cout << "eZero is error ArrayLength is " << mySize << endl;}};class eTooBig : public eSize{public:eTooBig(int size) : eSize(size){}virtual    void printError(){cout << "eTooBig is error ArrayLength is " << mySize << endl;}};class eTooSmall : public eSize{public:eTooSmall(int size) : eSize(size){}virtual  void printError(){cout << "eTooSmall is error ArrayLength is " << mySize << endl;}};protected:
private:int arrayLength;int* mySpace;
};MyArrary::MyArrary(int arrayLength)
{if (arrayLength < 0){throw eNegative(arrayLength);}else if (arrayLength == 0){throw eZero(arrayLength);}else if (arrayLength > 1000){throw eTooBig(arrayLength);}else if (arrayLength < 10){throw eTooSmall(arrayLength);}this->arrayLength = arrayLength;mySpace = new int[arrayLength];
}
MyArrary::~MyArrary()
{if (mySpace != nullptr){delete[] mySpace;mySpace = nullptr;arrayLength = 0;}arrayLength = 0;}
int& MyArrary::operator [](int index)
{return mySpace[index];
}int MyArrary::getArraryLength()
{return arrayLength;
}int main()
{//1)    index < 0 抛出异常eNegative//    2)   index = 0 抛出异常 eZero// 3) index>1000抛出异常eTooBig//    4) index < 10 抛出异常eTooSmall// 5) eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。try{MyArrary a(0);for (int i = 0; i < a.getArraryLength(); i++){a[i] = i;cout << a[i] << endl;}}catch (MyArrary::eSize & e){e.printError(); // 利用多态}catch (...){cout << "未知异常" << endl;}return 0;
}

可以使用多态和异常相结合大大的提高异常处理的灵活性。

标准程序库异常



使用标准异常库的案例:

#include <iostream>
#include <stdexcept>
using namespace std;class Teacher
{public:Teacher(int age){if (age > 100){throw out_of_range("年龄太大");}this->age = age;}
private:int age;};int main()
{try{Teacher t1(101);}catch (out_of_range e){cout << e.what() << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

C++的异常处理机制相关推荐

  1. Go语言的错误异常处理机制及其应用

    一.背景 在日常编写golang程序或阅读别人的golang代码时,我们总会看到如下的一堆代码块: xx, err = func(xx) if err != nil {//do sth. to tac ...

  2. recover 没有捕获异常_GO语言异常处理机制panic和recover分析

    本文实例分析了GO语言异常处理机制panic和recover.分享给大家供大家参考.具体如下: Golang 有2个内置的函数 panic() 和 recover(),用以报告和捕获运行时发生的程序错 ...

  3. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  4. java异常详细讲解_Java异常处理机制的详细讲解和使用技巧

    一起学习 1. 异常机制 1.1 异常机制是指当程序出现错误后,程序如何处理.具体来说,异常机制提供了程序退出的安全通道.当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器. 1.2 ...

  5. java提供两种处理异常的机制_浅析Java异常处理机制

    关于异常处理的文章已有相当的篇幅,本文简单总结了Java的异常处理机制,并结合代码分析了一些异常处理的最佳实践,对异常的性能开销进行了简单分析. 博客另一篇文章<[译]Java异常处理的最佳实践 ...

  6. java异常处理机制详解

    java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.

  7. 【Java面试题】21 Java中的异常处理机制的简单原理和应用。

    [Java面试题]21 Java中的异常处理机制的简单原理和应用. 参考文章: (1)[Java面试题]21 Java中的异常处理机制的简单原理和应用. (2)https://www.cnblogs. ...

  8. SpringMVC异常处理机制详解[附带源码分析]

    SpringMVC异常处理机制详解[附带源码分析] 参考文章: (1)SpringMVC异常处理机制详解[附带源码分析] (2)https://www.cnblogs.com/fangjian0423 ...

  9. c语言c2182是什么错误,C语言中一种更优雅的异常处理机制

    上一篇文章对C语言中的goto语句进行了较深入的阐述,实际上goto语句是面向过程与面向结构化程序语言中,进行异常处理编程的最原始的支持形式.后来为了更好地.更方便地支持异常处理编程机制,使得程序员在 ...

  10. Laravel 5.5 的错误异常处理机制以及应用实例

    一.前言 我们在开发项目中,难免会因为逻辑上的失误而报错,这些报错的展现形式也就是框架封装好的异常处理机制.在项目上线之前,我们还可以根据框架提供的报错信息来锁定错误代码的位置.但是项目上线之后我们是 ...

最新文章

  1. mysql 实验论证 innodb表级锁与行级锁
  2. _LVM——让Linux磁盘空间的弹性管理
  3. 遨游3.0 RC 版公布
  4. python外卷(7)--glob
  5. va_list/va_start/va_end的使用
  6. 这 10 个云计算错误,会让你的业务一蹶不振!
  7. at24c16如何划分出多个读写区_漫话:如何给女朋友解释为什么Windows上面的软件都想把自己安装在C盘...
  8. python 在线培训费用-在线Python编程培训哪家机构比较好?
  9. 深度学习 --- 卷积神经网络CNN(LeNet-5网络学习算法详解)
  10. 为何MAC的JDK/JRE大小这么小?
  11. 网页版微博HTML解析和提取,爬虫聚焦——以新浪微博为例
  12. 大小端转换定义结构体的技巧
  13. echarts实现平面3D柱状图
  14. matlab求一维热传导方程数值解代码,一维热传导方程数值解法及matlab实现
  15. 计算机c盘如何扩容,C盘空间不足怎么办?4种方法获得更多空间!
  16. 潇洒老师分享的小知识:注塑模具“压模”的原因和预防措施
  17. 小胡的第一篇Blog
  18. 听见丨戴森召回逾10万台进口空气净化暖风器 沃尔沃开始在普通家庭展开自动驾驶项目
  19. css和html的用法,HTML与CSS之CSS的基本使用
  20. 实现多个文件夹名同时重命名的操作

热门文章

  1. AP微积分考试备考重点
  2. SafeNet呼吁采用KMIP,推出首款基于硬件的企业密钥管理平台
  3. android源码预置apk
  4. ☀️在爬完一周的朋友圈后,我发现了.......惊人⚠️秘密
  5. 如何解决安装ESXI 5.5出现紫屏或者红屏
  6. 商用车车队管理系统FMS
  7. mingw编译FFmpeg32位和64位dll
  8. thesis; dissertation; treatise; paper 几种论文你分得清么?别用错场合
  9. 什么是 Wireframe线框图
  10. Flash动画学习指引六:操作动作补间