正在加载……
专注、离线、切勿分心
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> 中还定义了typeinfotypeid操作符,从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;
}



posted on 2018-07-25 20:51  正在加载……  阅读(143)  评论(0编辑  收藏  举报