前言
"打牢基础,万事不愁" .C++的基础语法的学习."学以致用,边学边用",编程是实践性很强的技术,在运用中理解,总结.
引入
在编写程序时,可能会因为程序错误或者编译问题等出现异常,因此设计了异常处理机制来应对.以<C++ Prime Plus> 6th Edition(以下称"本书")内容做参考.C++基础语法:异常处理(一)-CSDN博客是本贴的前一篇
异常机制
下面介绍如何使用异常机制来处理错误。C++异常是对程序运行过程中发生的异常情况(例如被0除)的一种响应。异常提供了将控制权从程序的一个部分传递到另一部分的途径。对异常的处理有3个组成部分: 引发异常; 使用处理程序捕获异常; 使用try块。(黑体字是原话)
----解读: 异常的概念---对程序运行过程中发生异常情况的一种响应
有三个部分,try---catch---throw
程序在出现问题时将引发异常。例如,可以修改程序清单15.7中的 hmean( ),使之引发异常,而不是调用abort( )函数。throw语句实际上是跳转,即命令程序跳到另一条语句。throw关键字表示引发异常,紧随其后的值(例如字符串或对象)指出了异常的特征。
程序使用异常处理程序(exception handler)来捕获异常,异常处理程序位于要处理问题的程序中。catch关键字表示捕获异常。处理程序以关键字catch开头,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型;然后是一个用花括号括起的代码块,指出要采取的措施。catch关键字和异常类型用作标签,指出当异常被引发时,程序应跳到这个位置执行。异常处理程序也被称为catch块。
try块标识其中特定的异常可能被激活的代码块,它后面跟一个或多个catch块。try块是由关键字try指示的,关键字try的后面是一个由花括号括起的代码块,表明需要注意这些代码引发的异常。
要了解这3个元素是如何协同工作的,最简单的方法是看一个简短的例子,如程序清单15.9所示
----解读: 以上红色部分是概念,紫色部分点明学习的内核,不仅适用于异常的学习:学习最简单的方法是看简短的例子,然后总结出逻辑链并应用于相似场合.
程序清单15.9不写了.三个部分用伪代码表示:
1>try块
//try块的伪代码
try{
fun_Exception(); //调用产生异常的函数
}
========================
//书上实际代码
try{
z=hmean(x,y);
}
2>产生异常的函数含throw
//伪代码:产生异常的函数
ResultType fun_Exception(ParaType exception){ //可能异常的参数传入
if(expression){ //if后跟异常产生的表达式
throw"需要给出的信息"; //throw后面的内容将交给catch处理
}
}
=================================
//书上实际代码
double hmean(double a,double b){
if(a==-b)
throw"bad hmean() arguments:a=-b not allowed";
return 2.0*a*b/(a+b);
}
3>catch块
//伪代码
catch(参数){ //这里的参数类型和throw匹配,才能进入catch
语句;
}
=======================================
//书上代码
catch (const char * s) //匹配throw后面的字符串,所以形参类型是const char*
{
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue; //和外层的while语句结合使用,如果外面没有while不要这句
}
catch块点类似于函数定义,但并不是函数定义。关键字catch表明这是一个处理程序,而char*s则表明该处理程序与字符串异常匹配。s与函数参数定义极其类似,因为匹配的引发将被赋给s。另外,当异常与该处理程序匹配时,程序将执行括号中的代码。
执行完try块中的语句后,如果没有引发任何异常,则程序跳过try块后面的catch块,直接执行处理程序后面的第一条语句。
----解读: 当异常没有被触发,跳过catch执行后面语句.
接下来看将10和−10传递给hmean( )函数后发生的情况。If语句导致 hmean( )引发异常。这将终止hmean( )的执行。程序向后搜索时发现, hmean( )函数是从main( )中的try块中调用的,因此程序查找与异常类型匹配的catch块。程序中唯一的一个catch块的参数为char*,因此它与引发异常匹配。程序将字符串“bad hmean( )arguments: a = -b not allowed”赋给变量s,然后执行处理程序中的代码。处理程序首先打印s——捕获的异常,然后打印要求用户输入新数据的指示,最后执行continue语句,命令程序跳过while循环的剩余部分,跳到起始位置。continue使程序跳到循环的起始处,这表明处理程序语句是循环的一部分,而catch行是指引程序流程的标签
----解读: 当异常被触发,throw后内容传给catch块形参,执行代码.throw后的数据类型和catch块形参类型需匹配.
异常机制小结
整体来说,这部分内容就是个格式,把try-throw-catch三部分的写法记住就行了.书上写得有点复杂.
try---调用异常处理函数;throw---当传入参数产生异常时,转入catch;catch---异常出现时处理方案.
======================================内容分割线===========================
异常处理函数的编写思路(对应程序15.9中的hmean()):传入可能异常的参数,写出表达式(上个例子"if(a=-b)")交给throw.变量→形参→表达式→throw.
变量→形参→表达式→返回值(或函数内部逻辑),.这是编写函数的基本思路.也是从变量到表达逻辑的过程
======================================内容分割线===========================
异常对象
通常,引发异常的函数将传递一个对象。这样做的重要优点之一是,可以使用不同的异常类型来区分不同的函数在不同情况下引发的异常。另外,对象可以携带信息,程序员可以根据这些信息来确定引发异常的原因。同时,catch块可以根据这些信息来决定采取什么样的措施。(黑体字是原话)
----解读:不同的异常用不同的类对象作区分.对象的好处是可以带大量的信息.
前面异常机制理解了这里比较简单.把throw后面的内容改为异常类的构造函数,catch形参类型用对象引用.,两者匹配即表示可传递信息,具体见程序清单15.11
栈解退
1>函数调用过程的理解
首先来看一看C++通常是如何处理函数调用和返回的。C++通常通过将信息放在栈(参见第9章)中来处理函数调用。具体地说,程序将调用函数的指令的地址(返回地址)放到栈中。当被调用的函数执行完毕后,程序将使用该地址来确定从哪里开始继续执行。另外,函数调用将函数参数放到栈中。在栈中,这些函数参数被视为自动变量。如果被调用的函数创建了新的自动变量,则这些变量也将被添加到栈中。如果被调用的函数调用了另一个函数,则后者的信息将被添加到栈中,依此类推。当函数结束时,程序流程将跳到该函数被调用时存储的地址处, 同时栈顶的元素被释放。因此,函数通常都返回到调用它的函数,依此类推,同时每个函数都在结束时释放其自动变量。如果自动变量是类对象,则类的析构函数(如果有的话)将被调用
----解读:每当有程序被调用,则生成一个栈,里面的信息有:被调用函数地址,函数参数,函数中的自动变量,被调用函数中调用的其他函数的信息.假设有以下函数:
//伪代码
void fun1(){
fun2(a,b);
fun3();
}
void fun2(int a,double b){
int c;
fun4(a,b,c);
}
程序编译以后,被调用的函数fun2()在内存中有个地址,同时和fun2()有关的数据被压入栈中.
fun2栈区图示:
当fun2()被执行完毕后,栈区里的数据被释放.CPU返回地址0xa8276b1f(假设的地址),继续往下找到fun3()的地址,把fun3()有关的数据压入栈中,继续执行程序.main函数也是这样执行.简单的说,每执行完一个函数,释放函数栈区内数据
2>函数返回和栈解退的比较
现在假设函数由于出现异常(而不是由于返回)而终止,则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块(参见图15.3)中的返回地址。随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。这个过程被称为栈解退。引发机制的一个非常重要的特性是,和函数返回一样,对于栈中的自动类对象,类的析构函数将被调用。然而,函数返回仅仅处理该函数放在栈中的对象,而throw语句则处理try块和throw之间整个函数调用序列放在栈中的对象。如果没有栈解退这种特性,则引发异常后,对于中间函数调用放在栈中的自动类对象,其析构函数将不会被调用
----解读:只要发生了异常,把try和throw之间整个函数调用序列所有存放在栈中的对象释放.对比函数返回,执行完一个函数,删除该函数栈区的数据;异常除了删除所在函数栈区,还把所在调用序列中所有函数的栈区数据都删除.
3>栈解退的示例
文字描述此前的异常处理和栈解退里的异常:
情形A:此前的异常处理:
try里调用函数A,try后面有catch,函数A定义里有throw.throw和catch类型匹配
情形B:栈解退用的异常处理:
try里调用函数A,后面有catch;
函数A里有第二个try,第二个try调用函数B,函数B定义里有throw,函数后面接catch.当异常被触发后,首先匹配函数A里的catch,如果匹配不到,再匹配第一个try里的catch.
代码描述
//伪代码:情形A
try{
funA();
}
catch(ParaType pt){} //catch,参数类型与fun()中pt需匹配
void funA(){ //funA定义,含throw
if(expression表达式)
throw pt; //抛出pt的类型是ParaType
}
//伪代码:情形B
try{ //和上面的try代码块一样
funB();
}
catch(ParaType pt){}
void funB(){ //funB有try,catch
try{
fun1_in();
fun2_in();
}
catch(ParaType pt){} //throw产生的数据优先与这里的catch匹配
}
void fun1_in(){
if(expression表达式)
throw pt; //抛出pt的类型是ParaType
}
void fun2_in(){
if(expression表达式)
throw pt; //抛出pt的类型是ParaType
}
=============================内容分割线===================================
以下内容与代码无关
学习方法和思考模式,也是笔者在编码过程中一直追寻的.看见一个调查,说外国人学编程优越一些的原因,就在于编码的字符就是英文单词.所以用汉语文字,伪代码来对知识点说明(比如这里描绘的栈解退现象),也不要觉得"土",觉得文科化了,觉得很笨拙.主要目的是把程序的思想掌握牢靠.
=============================内容分割线===================================
情形B就是本书P626程序清单15.12 error5.cpp梳理出来的伪代码.
再来看栈解退解决异常的方式,像是"try的递进",每个处理异常的函数里只有一个throw,而函数的嵌套可以向前"推进",使其表达成嵌套的try.异常不止在初始函数里抓取,也可以在被初始函数调用的函数中.
小结
异常抓取,throw的内容优先查找离最近的try,与后面的catch匹配.如果匹配步成功,向上找离得最近的try,再与其后面的catch匹配