有的时候,我们可以使用显式转换(又称强制类型转换),明确地告知编译器:这种转换正是程序所期望的,请不要再出现啰嗦的警告,如下:
- short s2 = (short)100000; //ok
显示转换的语法如下:
- (类型)表达式;
- 类型(表达式);
- (类型)(表达式);
也就是说,如下3种形式都可以:
- s2 = (short)100000; //ok
- s2 = short(100000); //ok
- s2 = (short)(100000); //ok
一般来说,第1种的用法更常见一些。
以上的表达方式非常简洁,但是C++并不推荐使用该方式,而推荐使用强制类型转换操作符(包括static_cast、dynamic_cast、reinterpret_cast和const_cast)来完成显式转换,它们的含义如表4-2所示。
表4-2 强制类型转换操作符 |
||
操作符 |
中文名称 |
含义 |
dynamic_cast |
动态类型转换符 |
支持多态而存在, 它主要用于类之间的转换 |
static_cast |
静态类型转换符 |
仅仅完成编译时期的转换检查 |
reinterpret_cast |
再解释类型转换符 |
完成不同类型指针之间的相互转换 |
const_cast |
常类型转换符 |
用来修改类型的const或volatile属性 |
接下来我们将简单回顾一下这些操作符,它们在Visual C++代码中经常出现。
1.动态类型转换符dynamic_cast
dynamic_cast为支持多态而存在,它主要用于类之间的转换,包括向下转换(downcast)和向上转换(upcast)。向下转换即将一个指向基类的指针或引用转换成一个指向派生类的指针或引用,如:
- void CMyMenu::UpdateMenu(CMenu *pmenu)
- {
- CMyMenu *psubmenu = dynamic_cast<CMyMenu *>(pmenu);
- …
- }
当使用dynamic_cast尝试进行指针的非法转换时,指针的值会被赋成0。当这个非法转换是针对引用而进行时,C++会抛出一个bad_cast异常来通知这个错误。
2.静态类型转换符static_cast
静态类型转换仅仅完成编译时期的转换检查,它一般用于如下场合。
用于类层次结构中基类和子类之间指针或引用的转换。进行向上转换(把子类的指针或引用转换成基类表示)是安全的(向上转换一般都是隐式转换,这时候 实际上根本就没有必要使用静态类型转换);进行向下转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
完成任何类型的指针与void之间的转换。
将空指针(0)转换成目标指针类型。
用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要由程序员来保证。
3.再解释类型转换符reinterpret_cast
再解释类型转换完成的都是一些不可思议的类型转换。实际上,所有指针的值都是一个表示地址的数值,值本身的转换是没有任何问题的。所谓"再解释",是指对指针的类型(如:int *和double *)进行重新解释。
再解释类型转换reinterpret_cast完成不同类型指针之间的相互转换,同时也支持将指针与数字之间的转换,如下代码:
- void CMyList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
- {
- NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
- …
- }
该段代码在MFC源码中比较常见,它将NMHDR指针重新解释成NMLVCUSTOMDRAW指针。
4.常类型转换符const_cast
常类型转换const_cast用来修改类型的const或volatile属性。除了const 或volatile修饰之外,其操作对象的类型和目标类型必须是一样的。
===================~~~~~~~~~~~~~~~~~~~~~~~~~华丽的分隔线~~~~~~~~~~~~~~~~~~~~~~~~~=============================
用法:static_cast < type-id > ( exdivssion )
该运算符把exdivssion转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
3.2 dynamic_cast
用法:dynamic_cast < type-id > ( exdivssion )
该运算符把exdivssion转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;
如果type-id是类指针类型,那么exdivssion也必须是一个指针,如果type-id是一个引用,那么exdivssion也必须是一个引用。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B{
public:
int m_iNum;
virtual void foo();
};
public:
char *m_szName[100];
};
D *pd1 = static_cast(pb);
D *pd2 = dynamic_cast(pb);
}
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),
而pd2将是一个空指针。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(
关于虚函数表的概念,详细可见)中,只有定义了虚函数的类才有虚函数表,
没有定义虚函数的类是没有虚函数表的。
class A{
public:
int m_iNum;
virtual void f(){}
};
};
};
B *pb = new B;
pb->m_iNum = 100;
D *pd2 = dynamic_cast(pb); //pd2 is NULL
delete pb;
}
3.3 reindivter_cast
用法:reindivter_cast (exdivssion)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,
在把该整数转换成原类型的指针,还可以得到原先的指针值)。
用法:const_cast (exdivssion)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和exdivssion的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
class B{
public:
int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast
(b1);b2. m_iNum = 200; //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;
使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
== ===========================================
class B { ... };
class D : public B { ... };
void f(B* pb)
{}
== ===========================================
== static_cast .vs.reinterdivt_cast
== ================================================