static_cast
static_cast< new_type >(expression)
该运算符把expression转换为new_type 类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
(1)用于基本数据类型之间的转换;
如把int转换为char,把int转换成enum,但这种转换的安全性需要开发者自己保证(这可以理解为保证数据的精度,即程序员能不能保证自己想要的程序安全),如在把int转换为char时,如果char没有足够的比特位来存放int的值(int>127或int<-127时),那么static_cast所做的只是简单的截断,及简单地把int的低8位复制到char的8位中,并直接抛弃高位。
(2)把空指针转换成目标类型的空指针;
(3)把任何类型的表达式类型转换成void类型;
(4)用于类层次结构中父类和子类之间指针和引用的转换,即上行转换(子类到父类)和下行转换(父类到子类)。
对于static_cast,上行转换时安全的,而下行转换时不安全的,为什么呢?因为static_cast的转换时粗暴的,它仅根据类型转换语句中提供的信息(尖括号中的类型)来进行转换,这种转换方式对于上行转换,由于子类总是包含父类的所有数据成员和函数成员,因此从子类转换到父类的指针对象可以没有任何顾虑的访问其(指父类)的成员。而对于下行转换为什么不安全,是因为static_cast只是在编译时进行类型检查,没有运行时的类型检查。
class Base
{};
class Derived : public Base
{}
Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB))
{}//下行转换是不安全的(坚决抵制这种方法)
Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD))
{}//上行转换是安全的
(5)注意:static_cast不能转换掉expression的const(这个只有const_cast才可以办得到)、volatile、或者__unaligned属性
char a = 'a';
int b = static_cast<int>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
const_cast
const_cast<type_id> (expression)
1、去除或增加类型中的const属性或volatile属性
2、const_cast一般用于指针或者引用
3、使用const_cast去除const限定的目的不是为了修改它的内容,使用const_cast去除const限定,通常是为了函数能够接受这个实际参数
4、非const引用不能指向const引用,如果想要引用该const引用,可以利用const_cast转换
注意:
虽然这里用了“去除”,但实际上并没有改变原类型的const属性或volatile属性,而是提供了一个接口(指针或引用),使得我们可以通过这个新的接口(变量)来改变原类型的值。在const_cast中,type_id也只能是指针或者引用,因为只有通过指针或引用才能得到原类型的地址,从而对其内存内容进行修改。
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
// 强制转换
int *p = (int *)(&a);
*p = 20;
cout << "a = " << a << ", *p = " << *p << endl;
cout <<"a address : "<< &a << endl;
cout << "p address : " << p << endl;
// const_cast到指针
int *p2 = const_cast<int *>(&a);
*p2 = 30;
cout << "a = " << a << ", *p = " << *p << endl;
cout << "p2 address : " << p2 << endl;
// const_cast到引用
int &p3 = const_cast<int &>(a);
p3 = 40;
cout << "a = " << a << ", *p = " << *p << endl;
cout << "p3 address : " << &p3 << endl;
return 0;
}
即使设置“*p = 20”、“*p2 = 30”、“p3 = 40”,在三次输出中,a的值都是最初定义的值10,并没有改变,这是为什么呢?原因就在于接下来要介绍的概念:**常量折叠。**常量折叠只对原生类型其作用,对我们自定义的类型,是不会起作用的。
#include <iostream>
using namespace std;
struct Test {
int a;
Test() {
a = 10;
};
} ;
int main()
{
const Test b;
Test *b1 = const_cast<Test *>(&b);
cout << "b = " << b.a << endl;
b1->a = 100;
cout << "b = " << b.a << ", *b1 = " << b1->a << endl;
return 0;
}
假如函数A中调用B函数,B中定义参数是const的而A传入的变量是非const,这个时候我们就可以用const_cast给传入的变量加上const属性了。反过来B中定义参数是非const而A传入的变量是const的,则我们可以用const_cast将传入变量的const属性去掉了(当然这种情况下就算不加const属性也是可以的)。
#include <iostream>
using namespace std;
void not_const_char(char *str)
{
cout << str + 5 << endl;
}
void const_char(const char *str)
{
cout << str + 5 << endl;
}
int main()
{
const char str[] = "abcdefgggg";
char str1[] = "efggggeeee";
not_const_char(str1); // ok
// not_const_char(str); // compile fail
not_const_char(const_cast<char *>(str)); // ok
const_char(str1); // ok
const_char(str); // ok
return 0;
}
reinterpret_cast
reinterpret_cast<new_type> (expression)
reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expression)有完全相同的比特位。具体表现:
- 从指针类型到一个足够大的整数类型
- 从整数类型或者枚举类型到指针类型
- 从一个指向函数的指针到另一个不同类型的指向函数的指针
- 从一个指向对象的指针到另一个不同类型的指向对象的指针
- 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
- 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
所以总结来说:reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。reinterpret_cast不能像const_cast那样去除const修饰符。
(所谓“足够大的整数类型”,取决于操作系统的参数,如果是32位的操作系统,就需要整型(int)以上的;如果是64位的操作系统,则至少需要长整型(long)。具体大小可以通过sizeof运算符来查看)。
typedef int (*FunctionPointer)(int);
int value = 21;
FunctionPointer funcP;
funcP = reinterpret_cast<FunctionPointer> (&value);
funcP(value);
先用typedef定义一个指向函数的指针类型,所指向的函数接受一个int类型作为参数。然后我用reinterpret_cast将一个整型的地址转换成该函数类型并赋值给了相应的变量。最后,我还用该整型变量作为参数交给了指向函数的指针变量。
reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。
辅助哈希函数的例子:
// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}
using namespace std;
int main() {
int a[20];
for ( int i = 0; i < 20; i++ )
cout << Hash( a + i ) << endl;
}
dynamic_cast(动态类型转换)
dynamic_cast < type-id > ( expression)
在运行时使用类型信息就叫做“run-time type information”, 即RTTI。
-
作用:
dynamic_cast将基类的指针或引用安全地转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。如果是基类指针或引用调用的是虚函数无需转换就能在运行时调用派生类的虚函数。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。 -
原理
只能在有虚函数的类层次之间使用dynamic_cast。要实现dynamic_cast,编译器会在每个含有虚函数的类的虚函数表的前四个字节存放一个指向_RTTICompleteObjectLocator结构的指针,当然还要额外空间存放_RTTICompleteObjectLocator及其相关结构的数据。
这个_RTTICompleteObjectLocator就是实现dynamic_cast的关键结构。里面存放了vfptr相对this指针的偏移,构造函数偏移(针对虚拟继承),type_info指针,以及类层次结构中其它类的相关信息。如果是多重继承,这些信息更加复杂。 -
使用
dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理,即会作一定的判断。
1、 对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
2、对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
1、dynamic_cast<type*>(e) //e是指针
2、dynamic_cast<type&>(e) //e是左值
3、dynamic_cast<type&&>(e)//e是右值
e能成功转换为type*类型的情况有三种:
1)e的类型是目标type的公有派生类:派生类向基类转换一定会成功。
2)e的类型是目标type的基类,当e是指针指向派生类对象,或者基类引用引用派生类对象时,类型转换才会成功,当e指向基类对象,试图转换为派生类对象时,转换失败。
3)e的类型就是type的类型时,一定会转换成功。
#include<iostream>
using namespace std;
class Shape
{
public:
virtual void Draw() = 0;
virtual ~Shape(){}
};
class Circle:public Shape
{
public:
void Draw()
{
cout << "Circle......" << endl;
}
};
class Square :public Shape
{
public:
void Draw()
{
cout << "Square......" << endl;
}
};
int main()
{
Shape* p;
Circle c;
p = &c;
p->Draw();
if (dynamic_cast<Circle*>(p))//运行时类型识别
{
cout << "p is point to a Circle objector " << endl;
Circle* cp = dynamic_cast<Circle*>(p);//向下安全转型
cp->Draw();//比虚函数的多态方式(p = &c;)开销大
}
else if (dynamic_cast<Square*>(p))
{
cout << "p is point to a Square objector " << endl;
}
else
{
cout << "p is point to a other objector " << endl;
}
cout << typeid(*p).name() << endl;//运行时类型识别
if (typeid(Circle).name() == typeid(*p).name())
{
cout << "p is point to a Circle objector " << endl;
((Circle*)p)->Draw();
}
else if (typeid(Square).name() == typeid(*p).name())
{
cout << "p is point to a Square objector " << endl;
((Square*)p)->Draw();
}
else
cout << "p is point to a other objector " << endl;
return 0;
}