C++的异常处理机制(一) 柔情只为你懂 2022-06-02 06:38 180阅读 0赞 # 目录 # * 目录 * 前言 * 异常处理机制 * 异常处理的基本思想 * C的异常处理的实现 * 异常基本语法 * 代码举例 * 栈解旋 * 异常接口说明 * 总结 # 前言 # 在使用Qt的过程中,对于信号与槽,感觉定义返回值来判断程序出错好像没有用。我感觉信号与槽就是相当于中断处理程序似的,当信号发出的时候,会中断当前指向的程序,而转去执行槽函数,这样正文就无法执行了,相当于事件循环卡住了(主题偏了,不知道理解的对不对)。主要想如果在槽函数中有异常发生,是否可以有异常处理机制来对其进行处理呢?以此有了对C++的异常处理机制的学习!反正异常处理也算是C++中应该知道的部分。之后再看看Qt中有没有对槽函数异常处理的需要!!! 学习!分享!感谢! # 异常处理机制 # 异常是一种程序控制机制,与函数机制独立和互补。**函数是一种以栈结构展开的上下函数衔接的程序控制系统。异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为捕获条件,从而以类型匹配在栈机制中跳跃回馈。** 栈机制是一种高度节律性控制机制,**面向对象编程却要求对象之间有方向/有目的的控制传动**,从一开始,**异常就是冲着改变程序控制结构,以适应面向对象程序更有效的工作这一主题,而不是为了进行错误处理**。 ## 异常处理的基本思想 ## \*\*传统的错误处理机制,通过函数返回值来处理错误。\*\*C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。 异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试.如图所示: ![1][] 异常超脱于函数机制,决定了其对函数的跨越式回跳。 ## C++的异常处理的实现 ## ### 异常基本语法 ### ![2][] 1. 若有异常则通过throw操作创建一个异常对象并抛掷 2. 将可能抛出异常的程序段嵌在try块中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。 3. 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。 4. catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。 5. 如果匹配的处理器为找到,则则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。 6. 处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。 ### 代码举例 ### * 被0整除案例 int divide(int x, int y ) { if (y ==0) { throw x; } return x/y; } void main() { try { cout << "8/2 = " << divide(8, 2) << endl; cout << "10/0 =" << divide(10, 0) << endl; } catch (int e) { cout << "e" << " is divided by zero!" << endl; } catch(...) // ...表示默认的处理函数,如果前面的异常匹配都不成功,就匹配这个 { cout << "未知异常" << endl; } return ; } * 自定义terminate函数 #include <iostream> #include <exception> using namespace std; class A { }; void f() { throw A(); } void g() { try { f(); } catch(int i) { cout << "exception int i" << endl; } } void myTerminate() { cout << "Here Is MyTerminate" << endl; return; } int main() { // set_terminate函数在头文件exception中声明,参数为函数指针void(*)(). set_terminate(myTerminate); g(); } 如果抛出的异常,所有的函数都处理不了,就会抵达系统的最后一道防线——激发terminate函数. 该函数调用引起运行终止的abort函数。我们可以设置其终止前的行为。 修改系统默认行为:可以通过set\_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理。 * 构造函数报告运行状态 构造函数没有返回类型,无法通过返回值来报告运行状态,所以只通过一种非函数机制的途径,即异常机制,来解决构造函数的出错问题。 * 异常捕获基于类型匹配 注意:异常机制和函数机制互补干涉,但**捕捉方式是基于类型匹配**。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多个数据类型匹配问题。如果在一个catch语句中对应多个throw,比如int数据类型,throw 1, throw 2, … 感觉只能在catch语句中通过switch case 来处理了。 class A{}; class B{}; int main() { try { int j = 0; double d = 2.3; char str[20] = "Hello"; cout << "Please input a exception number: "; int a; cin>>a; switch(a) { case 1: throw d; case 2: throw j; case 3: throw str; case 4: throw A(); case 5: throw B(); default: cout << "No throws here." << endl; } } catch(int) { cout << "int exception." << endl; } catch(double) { cout << "double exception." << endl; } catch(char*) { cout << "char* exception." << endl; } catch(A) { cout << "class A exception." << endl; } catch(B) { cout << "class B exception." << endl; } } catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据。 总之:**异常捕捉严格按照类型匹配** ## 栈解旋 ## 异常被抛出后,从进入try起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序和构造的顺序相反。这一过程称为栈解旋。 其实很简单,就是说,在抛出异常之后,在对异常处理完之后,相当于函数就已经执行完成了,对于函数中定义的局部变量自然而然就会被析构掉。 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() { Test t1; Test t2; cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl; throw MyException(); } void main() { try { myFunc(); } //catch(MyException &e) // 可以执行异常对象中的函数 catch(MyException ) //不能访问异常对象 { cout << "接收到MyException类型异常" << endl; } catch(...) { cout << "未知类型异常" << endl; } } ## 异常接口说明 ## 1. 为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型。例如:`void func() throw (A, B, C , D);`// 这个函数func()能够且只能抛出类型A B C D及其**子类型的异常**,也就是父类指针指向子类对象(多态)。 2. 如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:`void func();` 3. 一个不抛掷任何类型异常的函数可以声明为:`void func() throw();` 4. 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,`unexpected`函数会被调用,该函数默认行为调用`terminate`函数中止程序。 # 总结 # 其实异常的处理机制和Qt的事件处理机制有很多相同的地方。这里就我的理解总结如下: 1. 异常可以由直接调用它的函数处理,如果我们没有在直接调用的函数(`funcDirect()`)中使用`try{} catch{};`结构,而是在上上层(`funcIndirect()`)使用了`try{} catch{};`结构,就会在上上层(`funcIndirect()`)对异常进行处理,因此也就跳过了直接调用函数(`funcDirect()`)。 而同样,Qt中的事件处理,比如鼠标事件,如果我们继承了一个`QLabel`,比如(`class vLabel;`),在这个继承的`vLabel`中,如果没有对鼠标事件进行处理,就会转到`widget`中处理。也就是说Qt中的事件处理也是跳跃性的。 2. 异常可以在直接调用的函数(`funcDirect()`)中处理之后,然后抛出,给上上层(`funcIndirect()`)进行处理。也就是可以在调用的函数的栈结构中,可以依次处理底层函数发生的异常。 同样,Qt中的事件也可以在`vLabel`中处理之后,传递给`widget`再次进行处理。这个特点可以用来在`vLabel`上使用鼠标画矩形框。 3. 当然异常的不同在于,使用`try{} catch{};`结构,需要判断异常的函数其实是在`try{ }`作用域的,感觉封装度可能没有Qt高,还是没有看到本质! 附上《传智播客扫地僧c++基础和进阶课堂讲义.docx》[下载地址][Link 1]。 [1]: /images/20220602/3184bc371f754362820fe98223ab6dc1.png [2]: /images/20220602/a2d60d714cc9479991900feb38d398e4.png [Link 1]: http://download.csdn.net/download/simonforfuture/10193105
还没有评论,来说两句吧...