栈解旋
异常抛出后,从进入try块起,到异常被处理,这期间在栈上的构造的所有对象都会被自动析构。析构的顺序与构造的顺序相反。这一过程叫做解旋。
【也就是说栈模型先进后出并没有被打破,当异常throw抛掷,栈上的对象会被析构】
#include <iostream>
using namespace std;
class MyException{};
class Test
{
public:
Test(int a=0,int b=0)
{
this->a = a;
this->b = b;
cout<<"构造函数被执行"<<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;
Test t2(1,2);
cout<<"定义了两个变量,异常抛出后测试栈变量如何被析构"<<endl;
throw MyException();
}
int main(void)
{
try
{
myFunc();
}
catch(MyException)
{
cout<<"正在处理异常类型 MyException"<<endl;
}
catch(...)
{
cout<<"未知异常类型"<<endl;
}
return 0;
}
异常接口声明
1.为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型。
例如:void func() throw(A,B,C,D)这个函数能够且只能抛出类型ABCD及其子类型的异常
2.如果在函数声明中没有包含异常接口声明,则此函数可以抛掷任何类型的异常
3.一个不抛出任何异常的的函数可以声明为
void func throw();
4.如果一个函数抛出了它的异常接口声明所不允许的异常,unexpected函数会被调用,该函数默认行为调用terminate函数终止程序
异常类型和异常变量的生命周期
1) throw的异常是有类型的,可以是数字、字符串、类对象...
2) throw的异常是有类型的,catch严格按照类型进行匹配
#include <iostream>
using namespace std;
//throe int
void filecopy02(char *filename2,char *filename1)
{
FILE *fp1 = nullptr;
FILE *fp2 = nullptr;
fp1 = fopen(filename1,"rb");
if(fp1 == nullptr)
{
throw 1;
}
fp2 = fopen(filename2,"wb");
if(fp2 == nullptr)
{
throw 2;
}
char buf[256];
int readlen,writelen;
readlen=fread(buf,1,256,fp1);
while(!feof(fp1))
{
writelen = fwrite(buf,1,readlen,fp2);
if(readlen != writelen)
{
throw 3;
}
}
fclose(fp1);
fclose(fp2);
}
//throw string
void filecopy03(char *filename2,char *filename1)
{
FILE *fp1 = nullptr;
FILE *fp2 = nullptr;
fp1 = fopen(filename1,"rb");
if(fp1 == nullptr)
{
throw "打开源文件失败";
}
fp2 = fopen(filename2,"wb");
if(fp2 == nullptr)
{
throw "打开目标文件失败";
}
char buf[256];
int readlen,writelen;
readlen=fread(buf,1,256,fp1);
while(!feof(fp1))
{
writelen = fwrite(buf,1,readlen,fp2);
if(readlen != writelen)
{
throw "文件拷贝过程失败";
}
}
fclose(fp1);
fclose(fp2);
}
//throw class
class BadSrcFile
{
public:
void toString()
{
cout<<"aaaaaa"<<endl;
}
};
class BadDestFile
{
};
class BadCpyFile
{
};
void filecopy04(char *filename2,char *filename1)
{
FILE *fp1 = nullptr;
FILE *fp2 = nullptr;
fp1 = fopen(filename1,"rb");
if(fp1 == nullptr)
{
throw BadSrcFile();
}
fp2 = fopen(filename2,"wb");
if(fp2 == nullptr)
{
throw BadDestFile();
}
char buf[256];
int readlen,writelen;
readlen = fread(buf,1,256,fp1);
while (!feof(fp1))
{
writelen = fwrite(buf,1,readlen,fp2);
if(writelen != readlen)
{
throw BadCpyFile();
}
}
fclose(fp1);
fclose(fp2);
}
int main(void)
{
try
{
filecopy02("01.txt","02.txt");
}
catch(int e)
{
cout<<"发生异常:"<<e<<endl;
}
catch(const char *e)
{
cout<<"发生异常:"<<e<<endl;
}
catch(BadSrcFile *e)
{
e->toString();
cout<<"发生异常,打开源文件失败"<<endl;
}
catch(BadSrcFile &e)
{
e.toString();
cout<<"发生异常,打开源文件失败"<<endl;
}
catch(BadDestFile e)
{
cout<<"发生异常,打开目的文件失败"<<endl;
}
catch(BadCpyFile e)
{
cout<<"拷贝文件时发生异常"<<endl;
}
catch(...)
{
cout<<"未知异常..."<<endl;
}
return 0;
}
C++编译器通过throw来产生对象,C++编译器在执行对应的catch分支,相当于一个函数应用,把实参传递给形参。【当然会通过解螺旋把栈清空】
不妨把catch看作一个模板函数,会进行严格的类型匹配
异常的层次结构(继承在异常中的应用)
- 异常是类(自己创建的异常类)
- 异常派生
- 异常中的数据:数据成员
- 按引用传递异常
- 在异常中使用虚函数
class eSize
{
public:
eSize(int index)
{
this->index = index;
}
virtual void printErr()
{
}
private:
int index;
};
class eNegative:public eSize
{
public:
eNegative(int x):eSize(x)
{
}
virtual void printErr()
{
cout<<"index<0"<<endl;
}
};
class eZero:public eSize
{
public:
eZero(int x):eSize(x)
{
}
virtual void printErr()
{
cout<<"index=0"<<endl;
}
};
class eTooBig:public eSize
{
public:
eTooBig(int x):eSize(x)
{
}
virtual void printErr()
{
cout<<"index>10000"<<endl;
}
};
class eTooSmall:public eSize
{
public:
eTooSmall(int x):eSize(x)
{
}
virtual void printErr()
{
cout<<"index<10"<<endl;
}
};
标准程序库异常
C++标准提供了一组标准异常类,这些类以基类Exception开始,标准程序库抛出的所有异常,都派生于该基类,这些类构成如下图的异常类的派生继承关系。该基类提供一个成员函数what()用于返回错误信息(返回类型为const char*)。在Exception类中,what()函数声明如下:
virtual const char * what()const throw();
上面包含了各个具体异常类的含义以及它们的头文件。runtime_error和logic_error是一些具体的异常类的基类,它们分别表示两大类异常。logic_error表示那些可以在程序中被预先检测到的异常,也就是说如果小心得编写程序,这些类异常能够避免,而runtime_error则表示那些难以被预先检测的异常。
一些编程语言规定只能抛掷某个类的派生类(例如java中允许抛掷的类必须派生自Exception类),C++虽然没有这项强制的要求,但仍然可以选择这样实践。例如:在程序中可以使得所有抛出的异常皆派生自Exception(或者直接抛出标准程序库提供的异常类型,或者从标准程序库提供的异常类派生出新的类),这样会带来很多方便.(因为多态)
logic_error和runtime_error两个类及其派生类,都有一个接收const string &型参数的改造函数。在构造异常对象时需要将具体的错误信息传递给该函数,如果调用该对象的what函数,就可以得到构造时提供的错误信息。
#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;
};
class Dog
{
public:
Dog()
{
p = new int[1024*1024*100]; //4MB
}
private:
int *p;
};
int main(void)
{
// try
// {
// Teacher t1(30);
// }
// catch(exception &e)
// {
// cout<<e.what()<<endl;
// }
try
{
Dog *pdog;
for(int i=1;i<=10000000;i++)
{
pdog = new Dog();
}
}
catch(exception& e)
{
std::cerr << e.what() << '\n';
}
return 0;
}