1)异常是一种程序控制机制,与函数机制独立和互补
函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.
2) 异常设计目的:
栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。
异常设计出来之后,却发现在错误处理方面获得了最大的好处。
1.异常处理的基本思想
1.1传统的错误处理机制
通过函数返回值来处理错误。
1.2异常的错误处理机制
1.jpg1)C++的异常处理机制使得异常的引发和异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。
2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图
3)异常超脱于函数机制,决定了其对函数的跨越式回跳。
4)异常跨越函数
#include <iostream>
using namespace std;
void divide(int x,int y)
{
if (y == 0)
{
//出现了非法的算术
cout<<"发现y==0"<<endl;
throw 'Z';//throw divide就退出了。相当于return
}
cout<<"x / y = "<<x/y<<endl;
}
void use_divide(int x,int y)
{
try{
divide(x,y);
}
catch(...){
cout<<"use_divide 捕获到了未知异常,向上抛"<<endl;
throw;//中间层,捕获到异常,不做最终处理,直接向上抛
}
}
//1.异常的捕获是严格按照类型匹配的
//2.异常可以不做处理,继续向上抛,中间层可以不需要处理,用一个统一处理异常的函数统一处理
//3.如果说异常一直向上抛,没有函数处理,最终操作系统会将正常错误处理(该崩就崩)
int main(void)
{
divide(10,2);
try{
use_divide(10,0);//抛出错误
}
catch(int e)
{
cout<<"捕获到了异常e = "<<e<<endl;
cout<<"对异常进行处理"<<endl;
}
catch(char e)
{
cout<<"捕获到了异常e = "<<e<<endl;//捕获到e = 'Z'
}
catch(...)
{
cout<<"捕获到了未知异常 "<<endl;//捕获到e
}
return 0;
}
2.C++异常处理的实现
3.jpg1)若有异常则通过throw操作创建一个异常对象并抛掷。
2)将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。
3)如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。
4)catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。
5)如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功
能是调用abort终止程序。
6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔
2.1栈解旋(unwinding)
异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a,int b){
this->a = a;
this->b = b;
cout<<"Test(int ,int)...."<<endl;
}
~Test(){
cout<<"a = "<<a<<" b = "<<b<<endl;
cout<<"~Test()..."<<endl;
}
private:
int a;
int b;
};
void divide(int x,int y)
{
Test t1(1,2);
Test t2(10,20);
if (y == 0)
{
throw x;//所有在throw之前在栈上开辟的空间,都会被释放掉
}
cout<<"x / y = "<< x / y <<endl;
}
int main(void)
{
try{
divide(10,0);
}
catch(int e){
cout<<"捕获到了异常e = "<<e<<endl;
}
return 0;
}
4.png
2.2异常接口声明
1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D);
//这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func();
3)一个不抛掷任何类型异常的函数可以声明为:
void func() throw();
4)如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。
void func2()//什么异常都可以
{
}
void func() throw()//代表此函数不会抛出异常
{
throw 1;//想抛它也不拦你,只报警告
}
void divide(int x,int y) throw(char)//报警告了,因为抛出一个int型
{
Test t1(1,2);
Test t2(10,20);
if (y == 0)
{
throw x;
}
cout<<"x / y = "<< x / y <<endl;
}
2.3异常类型和异常变量的生命周期
1)throw的异常是有类型的,可以使,数字、字符串、类对象。
2)throw的异常是有类型的,catch严格按照类型进行匹配。
3)注意异常对象的内存模型。
//异常类型
class BadDstAddrType{};
class BadSrcAddrType{};
class BadProcessAddrType{
public:
BadProcessAddrType(){
cout<<"BadProcessAddrType()..."<<endl;
}
BadProcessAddrType(const BadProcessAddrType& obj){
cout<<"BadProcessAddrType(const BadProcessAddrType& obj)..."<<endl;
}
void print(){
cout<<"copy过程出现了异常"<<endl;
}
~BadProcessAddrType(){
cout<<"~BadProcessAddrType()..."<<endl;
}
};
void my_strcpy3(char *dst,char* from)
{
if (dst == NULL)
{
throw BadDstAddrType();//直接抛无参构造对象
}
else if(from == NULL)
{
throw BadSrcAddrType();
}
if (*from == 'a')
{
throw BadProcessAddrType();
//在抛出这个BadProcessAddrType对象的时候回创建一个匿名对象
//这个BadProcessAddrType()和下面的BadProcessAddrType e是同一个东西???
//不是同一个东西,调用了拷贝构造的
}
while(*from != '\0'){
*dst = *from;
dst++;
from++;
}
*dst = '\0';
}
int main(void)
{
char buf1[] = "1234567":
char buf2[128] = {0};
try{
my_strcpy3(buf2,buf1);
}
catch(int e){
cout<<"捕获到异常,代码e = "<<e<<endl;
}
catch(char* e){
cout<<"捕获到char*异常e = "<<e<<endl;
}
catch(BadProcessAddrType e){
//BadProcessAddrType e = 匿名对象temp,调用了拷贝构造
//在捕获的时候,如果用一个元素捕获,会发生拷贝构造,异常对象e和被抛出的匿名对象不是同一个对象
//会有深拷贝和浅拷贝的风险
cout<<"捕获到了BadProcessAddrType 异常类型 e"<<endl;
e.print();
}
catch(BadProcessAddrType *e){
//throw &(BadProcessAddrType());如果返回地址
//那么这个地址是一个野指针!
//为了接收这个指针,应该是:
//throw new BadProcessAddrType();
cout<<"捕获到了BadProcessAddrType* 异常类型 e"<<endl;
delete e;//如果抛出的异常类型是new出来的,需要显示的delete掉
}
catch(BadProcessAddrType &e){//用引用接收!
//throw BadProcessAddrType();
cout<<"捕获到了BadProcessAddrType & 异常类型"<<endl;
//报错!和catch(BadProcessAddrType e)重复
//注销上面的,如果是引用接收的话,不会构造两次!只构造了一次,编译器给我们优化了
//编译器发现使用引用捕获的,那么不会立刻释放掉匿名对象,而是在异常处理完之后,才把e释放
//可以理解为 BadProcessAddrType &e = BadProcessAddrType();
//没有野指针,匿名对象的风险,所以引用是最终的写法
//1.普通元素类型的异常捕获,不能够跟引用捕获同时存在
}
catch(...){
cout<<"捕获到未知异常"<<e<<endl;
}
cout<<"buf2="<<buf2<<endl;
return 0;
}
3.标准程序库异常
5.png每个类所在的头文件在图下方标识出来.
标准异常类的成员:
① 在上述继承体系中,每个类都有提供了构造函数、复制构造函数、赋值操作符重载。
② logic_error类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述;
③ 所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息。
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
//自定义的MyException的类,也加入标准的异常库
class MyException:public exception
{
public:
MyException(char *str){
this->m_s = str;
}
virtual const char * what() const{//what()必须重写,自己去看!
cout<<"MyException 的异常"<<endl;
return this->m_s;
}
private:
char *m_s;
}
class Teacher
{
public:
Teacher(string name,int id){
this->name = name;
if (id > 100)
{
string str = "id超出范围";
throw out_of_range(str);
}
this->id = id;
}
private:
int id;
string name;
};
int main(void)
{
try{
Teacher t1("zhang3",10001);
}
catch(exception &e){//exception所有都能接!
cout<<e.what()<<endl;//const char *what()
}
try{
throw MyException("出现异常错误");
}
catch(exception &e){//父类的e通过多态接收子类的MyException
cout<<e.what()<<endl;
}
return 0;
}
网友评论