1.新增的四个强制类型转换符
“强制”的含义:在告诉编译器:
“我知道你不愿意这样做,可是你必须做,尽管执行吧,后果由我负责!”
const_cast除去对象的常属性。转换的是表达式而非自身. 形式:const_cast< type > ( object )
static_cast用来进行非多态的任何转换。拒绝了运行时的类型检查。形式:static_cast< type > ( object )
reinterpret_cast将一种数据从根本上变为另一种完全不兼容的类型。形式:reinterpret_cast< type > ( object )
dynamic_cast这是唯一的运行时转换符。可完成类族中的向下类型转换——将父类的指针变为子类的指针。形式:dynamic_cast< type > ( object )
下面对它们一一进行介绍。
static_cast
用法:static_cast < type-id > ( expression_r_r )
该运算符把expression_r_r转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
把空指针转换成目标类型的空指针。
把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression_r_r的const、volitale、或者__unaligned属性。
dynamic_cast
用法:dynamic_cast < type-id > ( expression_r_r )
该运算符把expression_r_r转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression_r_r也必须是一个指针,如果type-id是一个引用,那么expression_r_r也必须是一个引用。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B{
public:
int m_iNum;
virtual void foo();
};
class D:public B{
public:
char *m_szName[100];
};
void func(B *pb){
D *pd1 = static_cast<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
}
在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;但是,如果pb指向的是一个 B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A{
public:
int m_iNum;
virtual void f(){}
};
class B:public A{
};
class D:public A{
};
void foo(){
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast<D *>(pb); //copile error
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。
reinpreter_cast
用法:reinpreter_cast<type-id> (expression_r_r)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
该运算符的用法比较多。
const_cast
用法:const_cast<type_id> (expression_r_r)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression_r_r的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
Voiatile和const类试。举如下一例:
class B{
public:
int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast<B>(b1);
b2. m_iNum = 200; //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
最容易理解的解释:
dynamic_cast: 通常在基类和派生类之间转换时使用;
const_cast: 主要针对const和volatile的转换.
static_cast: 一般的转换,如果你不知道该用哪个,就用这个。
reinterpret_cast: 用于进行没有任何关联之间的转换,比如一个字符指针转换为一个整形数。
2.关于RTTI
1989年,由于异常处理的引入,C++必须具有运行时类型识别能力,于是导致了RTTI 机制的诞生。
RTTI 机制不仅满足了异常处理的需要,还解决了虚函数的难题。
“有了RTTI 机制之后,系统就能在运行时查询一个多态指针或引用指向的具体对象的类型了。”(Lippman语)
RTTI 机制的核心:typeid运算符
激活RTTI方法:在Project 菜单中,选Setting项,单击C/C++标签,Category列表中,选C++ Language,单击Enable RTTI
使用typeid运算符的前提:typeinfo类
必须有typeinfo类的支持。该类为所有的内置类型和多态类型的对象保存了运行时类型信息。它在头文件< typeinfo>中定义的。
常用该类的四个成员函数:
测试两个对象的类型是否相同: bool operator ==(const typeinfo&ob)const;
bool operator !=(const typeinfo&ob)const;
返回被测对象的类型名: const char * name()const;
判断两个对象定义的前后关系:boolbefore(consttypeinfo& ob)const;
所涉及的运算符
typeid(对象名/类型名): 返回一个typeinfo类的对象,记录着目标类的类型。
static_cast<目标类型>(源对象):将对象静态转换为目标类型。
dynamic_cast<目标类型>(源对象):若源对象与目标类型存在is -a 关系,则完成转换,否则失败。——将父类的指针变为子类的指针。
使用typeid的例:
# RTTI的typeid() 和dynamic_cast<>()算符。只有在有动态机制下,这两个算符才起作用,也就是在类中必须有虚函数。
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
virtual void foo(){cout<<"A foo"<<endl;}//虚函数的出现会带来动态机制
//因为启动了动态机制,故必须将编译
//器的编译选项"允许 RTTI"选上,在vc6
//上这样设置:工程->设置->c/c++ ->c++ language ->允许时间类型信息(RTTI)
};
class B:public A
{
public:
void foo(){cout<<"B foo"<<endl;}
void shit(){cout<<"you are shit!"<<endl;}
};
void main()
{
B b;
A *pa=&b;
pa->foo(); //调用B::foo()
(*pa).foo();//调用B::foo()
(*pa).shit();//编译错误:'shit' : is not a member of 'A'
cout<<typeid(pa).name()<<endl;//输出 class A*
cout<<typeid(*pa).name()<<endl;//输出 class B
B *pb;
pb=dynamic_cast<B*>(pa);//将父类指针转化为子类指针
//只有当该父类指针在运行时刻指向它的子类
//时候,这个转化才会成功,否则返回0值
//只有父类具有多态性时,才能使用dynamic_cast
//运算符,否则会编译错误
cout<<typeid(pb).name()<<endl;//输出 class B*
cout<<typeid(*pb).name()<<endl;//输出 class B
pb->foo();//调用B::foo()
pb->shit();//ok!
}
对比一下面的非动态机制的结果
class A
{
public:
void foo(){cout<<"A foo"<<endl;}//没有了虚函数
};
class B:public A
{
public:
void foo(){cout<<"B foo"<<endl;}
void shit(){cout<<"you are shit!"<<endl;}
};
void main()
{
B b;
A *pa=&b;
pa->foo(); //调用A::foo()
//因为在A,B类的继承关系中,它们都没有虚函数,故没有启动
//动态机制,一切东西都在编译阶段就决定了
(*pa).foo();//调用A::foo()
(*pa).shit();//编译错误:'shit' : is not a member of 'A'
cout<<typeid(pa).name()<<endl;//输出 class A*
cout<<typeid(*pa).name()<<endl;//输出 class A
B *pb;
pb=dynamic_cast<B*>(pa);//编译错误:'A' is not a polymorphic type
//只有dynamic_cast<type>(expression)中的
//expression具有多态性时,才能这么用,也就是
expression所属的类必须要有虚函数才行
}
(pa)虽然是指向一个它的子类父类指针,也就是说,(pa)虽然是(A*)型的,但它根据
动态机制,可以调用子类override的虚数,所调用的函数必须在A类中有接口的,如
上面的B::foo(),不能调用在A类中没有接口的函数,如B::shit().
指向动态机制下的子类的父类指针(pa)解引用后(*pa)是一个子类的对象,这就是动态
机制的奥妙之处,父类指针解出子类对象;但该对象(*pa)只能调用基类A中声明有的接
口,如(*pa).foo()---B::foo(); (*pa)对象虽然是B的对象,但不能调用B的特有接口,
也就是没有在A类中声明的接口,如B::shit(). 可以这样感性的理解,(*pa)是一个B对
象,但只能当作A对象用,但用的东西是动态邦定的。
(pa)和(*pa)的奥妙要好好的体会,这都是面向对象和动态机制的奥妙之处!
# dynamic_cast<type>(expression)运算符的出现,弥补了虚函数的不足之处,两者相得益彰,
绝代双骄也!
如上面(pa)或者(*pa)是无法访问 B::shit()函数的,因为它不是基类A中声明的虚函数接口,
但可以通过dynamic_cast运算符将(pa)转化后,去访问B::shit().
B*pb=dynamic_cast<B*>(pa);//将(A*)类型的pa转化为(B*)型
if(pb)//如果转换成功
pb->shit();//访问B::shit()
如果转换成功的话,pb=pa,否则的话,pb=0;只有父类指针pa在运行时刻指向子类B的对象时,
dynamic_cast才会转换成功。下面的转化是失败的
A* pa=new A;
B* pb=dynamic_cast<B*>(pa);//转换失败,因为pa并没有指向一个它的子类对象,pb==0.
typeid运算符可用于指出某一数据的类型。当基础类别里有虚拟函数,则此基础类别的指针(参考)指向衍生类别对象时,typeid将可指出实际指向的对象的类型名称。
typeid运算符的语法如下:
typeid(算式)
返回值为指向const type_info对象的参考,此对象将表达出类型的代号。
以下程序将利用typeid判别a变量是否为整数类型。
int a ;
if( typeid (a) == typeid(int)) ...
如果要取得数据类型的名称,则课通过typeid运算符所返回的type_info对象参考声明name成员函数:
typeid(算式).name()
返回值为类型名称的字符串,以下程序将取得a变量的类型名称:
char * data_type = typeid (a).name()
typeid运算符除了可以运用与判断指向衍生类别的基础类别指针(参考)外,还可以运用于模板类别与模板函数,判断套用模板的数据类型。
#include <iostream>
#include <typeinfo>
using namespace std;
class Base
{
public:
virtual void show()
{
cout << "base :: show() is called! " << endl;
}
};
class Derived: public Base
{
public:
void show()
{
cout << "derived :: show() is called! " <<endl;
}
};
int main()
{
Base * base_ptr = new Derived;
//判断base-ptr指针指向对象的类型是否为Derived类别
if(typeid(*base_ptr) == typeid(Derived))
{
cout << typeid(*base_ptr).name << endl;
//声明type_info类别的name成员函数,取得base_ptr指针指向对象的名称
}
//显示base_ptr指针的类型
cout << "base_ptr的类型是:" << typeid(base_ptr).name() <<endl;
return 0;
}
可以使用typeid运算符来求得变量或对象的数据类型。typeid运算符对变量或数据类型作用后,将返回一个type_info类型的对象,通过该对象,就可以获取数据类型的名称。type_info类一般在头文件typeinfo.h中定义。在C++中,相应类的定义如下:
class type_info
{
public :
_CRTIMP virtual ~type_info();
_CRTIMP int operator==(const type_info& rhs) const;
_CRTIMP int operator!=(const type_info& rhs) const;
_CRTIMP int before(const type_info& rhs) const;
_CRTIMP const char* name() const;
_CRTIMP const char* raw_name() const;
private :
void *_m_data;
char _m_d_name[1];
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
} ;
可以看到,通过type_info类的name成员函数就可以获取指定变量或对象的数据类型。
下面通过例程S 0 2 H说明typeid的一些用法。
例程S 0 2 H的源代码
#i nclude <conio.h>
#i nclude <iostream.h>
#i nclude <typeinfo.h>
struct Car // 定义结构C a r
{
double Price; // 价格
double Length; // 长度
} ;
void main()
{
int i; // 整数类型
const type_info & t0=typeid(i); // 获取变量的数据类型
cout<<"i的类型为: "<<t0.name()<<endl; // 显示变量的数据类型
Car c1; // 结构
const type_info & t1=typeid(c1); // 通过typeid操作符来生成
type_info引用
const type_info & t2=typeid(Car);
// 比较变量与结构的数据类型是否一致
if(t1 == t2)
cout<<"c1的类型为:"<<t1.name()<<endl;
else
cout<<"c1与struct Car的类型不匹配。"<<endl;
getch();
}
运行结果:
i的类型为: int
c1的类型为:struct Car
说明:
typeid运算符不区分变量是否有const修饰符,也不区分变量的存储类型,同时也不区分是否是引用。有兴趣的读者可以自己修改上面的例程,测试一下。