一,异常机制
1.1,异常机制的作用
C++异常是对程序在运行过程中发生异常情况的一种响应,异常提供了将程序的控制权从程序的一部分传递到另一部分的响应。异常的抛出和处理主要使用了以下三个关键字: try、 throw 、 catch 。
try:放在try块中的代码是可能发生异常的代码。
throw:这个关键字引发异常,紧随其后的值指出了异常的特征。throw语句实际上是跳转,即命令程序跳到另一语句。throw语句必须包含在try块中,也可以是被包含在外层函数的try块中。
catch:捕获异常,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型。try-catch语句形式如下:
ry{
包含可能抛出异常的语句;
}
catch(类型名 [形参名]){ // 捕获特定类型的异常
//异常处理程序
}
catch(...) { // 三个点则表示捕获所有类型的异常
//异常处理程序
}
1.2,使用异常机制处理异常
double fun(double x, double y){
if(y == 0){
throw y; //除数为0,抛出异常
}
return x / y;
}
int main()
{
double res;
try{
res = fun(2, 0);
} catch(double val){
cout<<"除数为:"<<val<<endl;
exit(1);
}
return 0;
}
1.3,使用自定义的对象作为异常类型
使用自定义的对象作为异常类型的优点:
- 可以使用不同的异常类型来区分引发不同的异常。
- 对象可以携带信息,程序员可以根据这些信息来确定引发异常的信息。
class bad_func{
private:
int v1;
public:
bad_func(int v1){
this->v1 = v1;
}
void mesg(){
cout<<"除数不能为:"<<v1<<endl;
}
};
double fun(double x, double y){
if(y == 0){
throw bad_func(0); //除数为0,抛出异常
}
return x / y;
}
int main()
{
double res;
try{
res = fun(2, 0);
} catch(bad_func &exp){
exp.mesg();
exit(1);
}
return 0;
}
1.4,栈解退
假设在try块没有直接调用引发异常的函数,而是调用了另外一个会引发异常的函数,则程序的流程将从引发异常的函数直接跳到包含try块和处理程序的函数,这涉及到栈解退。栈解退的过程如下:如果一个函数由于异常而终止,程序将进行出栈的操作,但是不会在回退到栈的第一个返回地址后停止,而是继续回退栈,直到找到一个位于try块中的返回地址。然后控制权交给块尾的处理程序,而不是函数调用后的第一条语句,这个过程称为栈解退。
void funcA();
void funcB();
void funcC();
void funcA(){
funcB();
}
void funcB(){
funcC();
}
void funcC(){
throw 0; //抛出一个异常
}
int main()
{
double res;
try{
funcA();
} catch(int val){
exit(1);
}
return 0;
}
funcC()函数引发了一个异常,此时会回退到调用funcC()的funcB(),在funcB()中发现funcC()并没有放到try块中,即在funcB()中没有对异常进行处理;此时会回退到调用funcB()的funcA(),同样在funcA()中发现funcB()并没有放到try块中,此时会回退到调用funcA()的main()函数,在main()函数中funcA()是放在try块中,此时程序的控制权就跳转到main()函数中。
1.5,throw-catch语句与函数参数与函数返回机制的区别
- 函数func()中的返回语句将控制权返回到调用func()的函数,但是throw语句将控制权向上返回到第一个这样的函数,即包含能够捕获异常的try-catch块。
- 引发异常时编译器总是创建异常对象的一个临时拷贝,即catch块中的参数是引用类型。
class bad_func{
private:
int v1;
public:
bad_func(int v1){
this->v1 = v1;
cout<<"bad_func构造函数"<<endl;
}
void mesg(){
cout<<"除数不能为:"<<v1<<endl;
}
bad_func(const bad_func &p){
this->v1 = p.v1;
cout<<"bad_func复制构造函数"<<endl;
}
~bad_func(){
cout<<"bad_func析构函数: "<<this<<endl;
}
};
double fun(double x, double y){
if(y == 0){
bad_func obj(0);
cout<<"bad_func对象地址: "<<&obj<<endl;
throw obj; //除数为0,抛出异常
}
return x / y;
}
int main()
{
double res;
try{
fun(2, 0);
} catch(bad_func &p){
cout<<"bad_func对象地址: "<<&p<<endl;
exit(1);
}
return 0;
}
输出结果
bad_func构造函数
bad_func对象地址: 0x69fe6c
bad_func复制构造函数
bad_func析构函数: 0x69fe6c
bad_func对象地址: 0x6f0dc0
bad_func析构函数: 0x6f0dc0
Process returned 1 (0x1) execution time : 0.008 s
Press any key to continue.
1.6,exception类
较新的C++编译器将异常合并到语音中。例如,为了支持该语言,exception头文件定义了exception类作为基类。其中有一个名为what()的虚函数,它返回一个字符串,该字符串的特征随实现而异。
class bad_func : exception{
private:
int v1;
public:
bad_func(int v1){
this->v1 = v1;
cout<<"bad_func构造函数"<<endl;
}
bad_func(const bad_func &p){
this->v1 = p.v1;
cout<<"bad_func复制构造函数"<<endl;
}
//重写虚函数what()
const char* what(){
cout<<"除数不能为0.";
}
};
double fun(double x, double y){
if(y == 0){
bad_func obj(0);
cout<<"bad_func对象地址: "<<&obj<<endl;
throw obj; //除数为0,抛出异常
}
return x / y;
}
int main()
{
double res;
try{
fun(2, 0);
} catch(bad_func &p){
cout<<"bad_func对象地址: "<<&p<<endl;
p.what();
exit(1);
}
return 0;
}
二,RTTI
2.1,RTTI的作用
假设有一个类层次结构,其中的类都是从同一个基类派生而来的,则可以让基类指针指向其中任何一个类的对象。这样便可以调用这样一个函数:在处理一些信息后,选择一个类,并创建这种类型的对象,然后返回它的地址,而该地址可以被赋为基类指针。如何知道指针指向的是哪种类型的对象呢?在回答这个问题之前,先考虑为何要知道类型。可能希望调用类方法的正确版本,在这种情况下,这要该函数是类层次结构中所有成员都拥有的虚函数,则并不真正需要知道对象的类型。但派生类可能包含不是派生而来的方法,在这种情况下,只有某些类型的对象可以使用该方法。对于这种情况,RTTI提供了解决方案。
注意:
只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种层次结构,才应该将派生类对象的地址赋给基类指针。
2.2,RTTI的工作原理
C++ 有三个支持RTTI的元素
2.2.1,dynamic_cast
dynamic_cast的语法与作用
dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,用来执行”安全向下类型转换”,也就是用来决定某对象是否属于某继承体系中的某个类。通常,想知道类型的原因在于:知道类型后,就可以知道调用特定的方法是否安全。例如,有下面的类继承体系:
class Grand{...};
class Superb : public Grand{...};
class Magnificent : public Superb{...};
该运算符的用法如下,其中pg指向某个对象:
Superb *pb = dynamic_cast<Superb *>(pg);
这提出了这样的问题:指针pg的类型是否可以安全地转换为Superb *?如果可以,运算符将返回对象的地址,否则返回一个空指针。通常,如果指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型,则下面的表达式将指针(*pt)转换为Type类型的指针。
dynamic_cast<Type *>(pt);
使用dynamic_cast来检查是否可以进行安全类型转换
class Grand{
public:
virtual void speak(){
cout<<"Grand Speak"<<endl;
}
};
class Superb : public Grand{
public:
virtual void speak(){
cout<<"Superb Speak"<<endl;
}
virtual void say(){
cout<<"Superb say"<<endl;
}
};
class Magnificent : public Superb{
public:
void speak(){
cout<<"Magnificent Speak"<<endl;
}
void say(){
cout<<"Magnificent say"<<endl;
}
};
上面定义了三个类,Grand、Superb、Magnificent。Grand类定义了虚函数speak(),而其它的类都继承了该虚函数。Superb类定义了一个虚函数say(),而Magnificent也重新定义了它。程序定义了getOne()函数,该函数随机创建这三种类中某个类的对象。然后将对象的地址作为Grand*指针返回,循环将返回的指针赋给Grand*变量pg,然后使用pg调用speak()函数。因为这个函数是虚函数,所以代码能够正确调用指向的对象的speak()版本。如下:
Grand *pg;
for(int i = 0; i < 5; i ++){
pg = getOne();
pg->speak();
}
但是不能用相同的方式来调用say()函数,因为Grand类没有定义say()函数。然而,可以使用dynamic_cast运算符来检测是否可以安全地将pg转换为Superb类型指针。如果pg指向的对象的实际类型为Superb或Magnificent,则可以安全类型转换。在这两种情况下,都可以安全调用say()函数。
Grand *pg;
Superb *ps;
for(int i = 0; i < 5; i ++){
pg = getOne();
if(ps = dynamic_cast<Superb *>(pg)){
ps->say();
}
}
2.2.2,typeid
typeid运算符的语法与作用
typeid运算符能够确定两个对象是否是同种类型。它接受两个参数,一是类名,二是结果为对象的表达式。typeid运算符返回一个type_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类。type_info类重载了==和!=运算符,以便于使用这些运算符对类型进行比较。
typeid运算符的使用
typeid(Magnificent) == typeid(*pg);
如果pg指向的是一个Magnificent对象,则上面表达式的结果为true,否则为false。如果pg是一个空指针,程序将引发bad_typeid异常。
2.2.3,type_info
type_info的语法与作用
type_info的实现随厂商而异,但包含一个name()成员,该函数返回一个随实现而异的字符串,通常是类的名称。
type_info的使用
cout<<typeid(*pg).name()<<endl;