RTTI、 typeid、类型转换操作符:
——RTTI,是RunTime Type Identification的缩写,称“运行时类型识别”,这是C++中相对较新的特性,一些老式的编译器可能不支持,不同编译器的实现方法也不尽相同。
RTTI机制:
★C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。虚函数的使用使得动态联编成为可能,举例来说,存在一个类层次结构A->B->C,结构定义为;
class A{…}; //包含虚函数
class B: public A{…};
class C: public B{…};
★此时,可以直接用派生类(B类或C类)对象为A类指针赋值,而且,通过该A类指针调用虚成员函数时,调用的版本是为其赋值的派生类对此虚函数的覆盖定义。当然,不可能把所有函数都定义成虚函数,对派生类中定义的普通函数来说,使用A类指针调用该函数是否合法需要具体分析。
★先来看如下的强制类型转换机制:
A *pa = new B;
B *pb = (B *)pa; //直接赋值编译器会报错, 需要强制转换
★上述代码是很安全的, 因为pa指向的堆空间中存储的恰好为B类对象,此时使用pb调用B类中定义的非虚函数不会出错。看另一个转换:
A *pa = new A;
B *pb = (B *)pa;
★虽然编译器不会报错,但上述代码明显不安全,存在问题,此时使用pb调用B类中的非虚函数必然会出错。
dynamic_cast操作符:
★综合来看,使用派生类对象为基类指针赋值是安全的,可什么时候使用基类指针为派生类指针赋值是安全的呢?C++提供了操作符dynamic_cast,其语法是:
CSon *p1 = dynamic_cast<CSon*>(pBase);
★其中pBase是基类指针,CSon是派生类型,如果pBase指向的对象是CSon型或CSon的派生类型,指针转换成功;否则,p1为null,即空指针。
#include <iostream>
using namespace std;
class A
{
public:
virtual void disp()
{ cout<<"A::disp()"<<endl; }
void printA()
{ cout<<"A::printA()"<<endl; }
};
class B : public A
{
public:
void disp()
{ cout<<"B::disp()"<<endl; }
void printB()
{ cout<<"B::printB()"<<endl; }
};
int main()
{
A *pa = new B;
B *pb = dynamic_cast<B *>(pa); //转换安全
if(pb != NULL)
{ pb->printB(); }
else
{ cout<<"转换不安全,退出"<<endl; }
A *p1 = new A;
B *p2 = dynamic_cast<B *>(p1); //转换不安全
if(p2 != NULL)
{ p2->printB(); }
else
{ cout<<"转换不安全,退出"<<endl; }
return 0;
}
|
#include <iostream>
#include <typeinfo>
#include <string.h>
using namespace std;
class Animal //抽象类
{
public:
virtual void Say()=0; //纯虚函数
};
class Dog:public Animal
{
public:
void Say()
{ cout<<"汪汪"<<endl; }
};
class Cat:public Animal
{
public:
void Say()
{ cout<<"喵喵"<<endl; }
};
void play(Animal *pa)
{
if(typeid(*pa) == typeid(Dog)) //判断当前指针是不是Dog类型
{
Dog * dog = dynamic_cast<Dog*>(pa);
dog->Say();
}
if(typeid(*pa) == typeid(Cat))
{
Cat * cat = dynamic_cast<Cat*>(pa);
cat->Say();
}
}
|
int main()
{
Dog dog;
Animal *a = &dog;
cout<<typeid(double).name()<<endl; //只输出类型第一个字母
cout<<typeid(5).name()<<endl;
cout<<"typeid(a).name() "<<typeid(a).name()<<endl; //指针类型
cout<<"typeid(*a).name() "<<typeid(*a).name()<<endl;
//指针所存的值类型
play(a);
Cat cat;
play(&cat);
}
|
typeinfo类和typeid操作符:
★在头文件 #include<typeinfo> 中还定义了typeinfo类和typeid操作符,从typeid的字面即可看出,该操作符用以返回类的id,即类型信息,其基本调用格式为: typeinfo& typeid(expr); 参数expr可以是单个对象,也可以是返回结果为对象的表达式,还可以是类名;返回值是一个typeinfo对象的引用,如果expr是类对象(或类名)、且至少包含有一个虚函数,typeid操作符返回的typeinfo对象需要在运行时计算;否则,返回一个静态对象,在编译时就可以计算得到。
★typeinfo类中包含了一个 name() 成员,返回一个字符串,通常是类名,如下述语句返回的均为静态typeinfo对象:
cout<<typeid(5).name()<<endl; //输出字符串“int”
cout<<typeid(double).name()<<endl; //输出字符串“double”
|
★来看一个返回动态typeinfo对象的例子:
class A{……}; //包含虚函数
class B:pulic A{……}
A* pa=new B;
cout<<typeid(*pa).name()<<endl; //输出结果为B
|
★typeinfo类中对 == 和 != 进行了重载,因此可以使用typeid来判断变量是哪种类型。如语句 if (typeid(x) == typeid(double)) 用来判断变量x的类型是否double类型。
●RTTI只能应用于包含虚函数的类层次中,只有在虚函数处理上,使用派生类对象给基类指针赋值才有意义。如果类层次中没有虚函数,将派生类赋值给基类指针没有实质意义。RTTI的引入,可检查基类指针向派生类指针的转换是否安全,为类层次中非虚函数和数据成员的调用提供了方法
类型转换操作符:
★包括dynamic_cast,C++中共添加了4个类型转换符,用以对数据类型的转换进行更严格的限制,分别是dynamic_cast、const_cast、static_cast和reinterpret_cast,和前面所讲的类型转换机制相比,新机制让程序员根据需要选择要使用的操作符,明确了转换意图,可读性更强,而且,编译器可方便地对转换是否安全进行检查,能排查很多传统类型转换无法找出的问题,动态操作符dynamic_cast已在上节介绍过,它将一个指向派生类的基类指针或引用转换为派生类的指针或引用,注意dynamic_cast转换符只能用于含有虚函数的类。
static_cast操作符:
★static_cast的基本语法为:static_cast<T>(expr); 该运算符把expr转换为T型,仅当T类型与expr所属类型能相互隐式转换时,上述转换才合法;否则会报错,能及时发现错误(例如不能把两个不相关的类相互转化)。主要有如下几种用法:
●用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
●用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
●把空指针转换成目标类型的空指针。
●把任何类型的表达式转换成void类型。
const_cast操作符:
★const_cast的基本使用格式为: const_cast<T>(expr);
★const_cast最常用的用途就是删除const属性,如果某个变量在大多数时候是常量,而在某个时候又是需要修改的,这时就可以使用const_cast操作符了。expr应该为指针和引用的形式,该操作符不会影响expr,除了const和volatile修饰符等,T应与expr类型相同,返回一个新的T型变量,与expr有相同的值。
#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
const int i=5;
const int *pi=&i;
//*pi = 6; //不能更改
cout<<"i= "<<i<<endl;
cout<<"*pi = "<<*pi<<endl;
cout<<"pi = "<<pi<<endl;
int *pi1=const_cast<int *>(pi); //为什么这样?因为const int *不能初始化int *
//去除pi的const属性,返回新类型变量,不会影响i
*pi1 = 6;
//*pi = 6; //不能更改
cout<<"i= "<<i<<endl;
cout<<"*pi = "<<*pi<<endl;
cout<<"pi = "<<pi<<endl;
cout<<"*pi1 = "<<*pi1<<endl;
cout<<"pi1 = "<<pi1<<endl;
*(const_cast<int *>(pi))=6; //前面操作可合并为这个
return 0;
}
|
#include <iostream>
using namespace std;
int main()
{
double d = 2.5;
double *pd = &d;
int *pi = reinterpret_cast<int *>(pd);
//int *pi1 = static_cast<int *>(pd); //error
//int *pi2 = dynamic_cast<int *>(pd); //error
int *pi3 = (int *)(pd);
cout<<"pd= "<<pd<<"\t\tpi= "<<pi<<"\t\tpi3= "<<pi3<<endl; //因为指针类型不同,所以值不同
cout<<"*pi= "<<*pi<<"\t\t*pi3= "<<*pi3<<endl;
int a = 3; //这里必须int
int *a1 = reinterpret_cast<int *>(a); //整形量转换为指针,只能值int类型
double *a2 = reinterpret_cast<double *>(a);
cout<<"a1= "<<a1<<"\t\ta2= "<<a2<<endl;
cout<<"*a1= "<<*a1<<"\t\t*a2= "<<*a2<<endl; //段错误
return 0;
}
|
reinterpret_cast操作符:
★reinterpret_cast的调用格式: reinterpret_cast<T>(expr)
★reinterpret_cast无法保证转换的安全性,用来将一个类型的指针转变为另一种类型的指针,也用在将整型量转为指针,或将指针转为整型量上。
reinterpret(重新解释)
◆RTTI机制特性能让程序在运行时检测对象的类型,不以指针为转换是否安全的依据,而是考虑指针指向的对象,更本质地说,考虑的是指针指向的内存块的有效性,因而能保证使用指针安全调用虚函数和普通函数。typeid操作符返回一个typeinfo对象的引用,通过typeinfo类提供的name成员函数可以输出参数所属类名。
◆C++中新增的4个类型转换操作符,和传统的类型转换相比,新增的4个cast操作符很好地保证了类型转换的安全性,直观体现了程序员的意图,编译器可很好地进行查错处理。
#include<iostream>
using namespace std;
class point
{
public:
point(int x=0,int y=0):_x(x),_y(y){}
virtual void show()
{
cout<<"("<<_x<<","<<_y;
}
private:
int _x;
int _y;
};
class point3D:public point
{
public:
point3D(int _x=0,int _y=0,int z=0):point(_x,_y),_z(z){}
virtual void show()
{
point::show();
cout<<","<<_z<<")"<<endl;
}
private:
int _z;
};
class String
{
public:
String():_mchar(new char[1]){}
void show()
{
cout<<_mchar<<endl;
}
private:
char* _mchar;
};
|
int main()
{
//下面的转换本来是无意义和非法的,以后使用ps->Show()
//成员函数时可能会引起内存错误或得到错误的值, 但编译却不出错. 留下隐患
point3D p1(1,2,3);
String* sp = (String*)&p1;
sp->show(); // 编译通过,调用直接段错误 // 派生类里面加了point::限定就不会段错误了
//但改成下面使用static_cast形式进行转换, 在编译时就报错, 能及时发现错误
//sp = static_cast<String*> (&p1); // 编译报错
cout<<"----------------------------------------------------------------"<<endl;
//而下面这种转换之所以能编译通过,是因为CPoint和CPoint3D的指针本来就可以相互转换
point* pBase = static_cast<point*> (&p1);
pBase->show();
cout<<endl;
return 0;
}
|