一、 const_cast
编译时执行,仅能用于同种数据类型间的转换。即常量属性转换。转换对象仅能为指针或引用。
#include<iostream>
using namespace std;
int main(){
const int a = 123; //声明a为const常量
a = 1234; //报错,无法修改const属性的值
cout << "a= " << a << endl;
int* ptr = &a; //报错,int*无法指向const类型的值
cout << "a= " << a << endl;
const int *ptr = &a;
cout << "a = " << a << endl; // a = 123
cout << "ptr = " << ptr << endl;
*ptr = 456; //出错,指针也无法修改const类型的值
cout << "a = " << a << endl;
cout << endl;
int* ptr1 = const_cast<int*>(ptr); //取消const属性
*ptr1 = *ptr1 + 1; //成功
cout << "a = " << a << endl; //1235
cout << "*ptr = " << *ptr << endl; //1235
cout << "*ptr1 = " << *ptr1 << endl; //1235
cout << "&a = " << &a << endl;
cout << "ptr = " << ptr << endl;
cout << "ptr1 = " << ptr1 << endl;
const int* ptr2 = const_cast<const int*>(ptr1);
*ptr2 += 1;
cout << "a = " << a << endl;
cout << endl;
const int& tmp = a;
cout << "a = " << a << endl;
tmp += 1;
cout << "a = " << a << endl;
int &tmp1 = const_cast<int&>(tmp);
tmp1 += 1;
cout << "a = " << a << endl;
}
#include<iostream>
using namespace std;
int main(){
/*
int* p = new int(1);
const int* p1 = const_cast<const int*>(p);
*p = 123;
*/
const int a = 1;
const int* p = &a;
const int *p1 = const_cast<const int*>(p);
*p1 = 123;
cout << *p << " " << *p1 << endl;
cout << p << " " << p1 << endl;
}
当我们使用const_cast为一个指针添加const属性时,仅仅是一个浅拷贝。所以之前非const属性的指针此时仍可以修改const所指向的地址的值。同样的,如果const指针指向一个const数据时,给这个指针去除const属性后,是可以修改const变量的。


二、reinterpret_cast
reinterpret_cast可以用于不同变量类型之间的转换,甚至可以将指针转为普通类型变量(实际就是将指针当中的地址转换为对于变量的格式)。它的本质是编译器指令,是四种cast转换中最为暴力的一种。在自由度如此高的情况下也存在很大的安全性问题。
#include<iostream>
using namespace std;
int main(){
int *p = new int(1);
int a = reinterpret_cast<int>(p);
float b = reinterpret_cast<int>(p);
cout << "a = " << a << endl << "p = " << p << endl << "b = " << b <<endl;
}

三、static_cast
static_cast类似于C语言中的强制转换,类似于
double a = 123.123;
int b = (int)a;
主要用途是:
1)基本类型之间的相互转化
2)void*转换为其他类型指针
3)父类子类之间的相互转化
先来看个例子:
#include<iostream>
using namespace std;
class A{
public:
int m_num = 666;
int setnum(){
return ++m_num;
}
};
class B:public A{
public:
int m_num = 666;
int setnum(){
return --m_num;
}
};
int main(){
double a = 123.123;
A* c = new A;
int b = static_cast<int>(a);
B* d = static_cast<B*>(c);
d->setnum();
cout << "b = " << b << endl << "d->num = " << d->m_num << endl;
}

运行结果如图所示,这里的double向int转换得到的结果符合预期,没什么问题。但是A与B类对象指针的转换结果却不符合预期,这是为什么呢?
这里A与B之间是父子继承关系,在B中继承了A的所有属性,但是A中却不存在纯属于B的属性。所以如果父类指针向子类指针转换,子类所指向的范围是大于父类的,当调用子类当中的某些成员是也是未初始化的部分。但是调用方法时却不会发生任何问题,这是因为类方法是存储在.text段的,是程序在编译好就确定的。而成员变量是存储在每个对象当中的,创建每一个对象时都需要进行初始化。
如果改成,子类指针转换为父类指针时,就不会发送这一问题。可以理解为大范围变为小范围内存时,不会发生访问越界。如:
#include<iostream>
using namespace std;
class A{
public:
int m_num = 666;
int setnum(){
return ++m_num;
}
};
class B:public A{
public:
int m_num = 666;
int setnum(){
return --m_num;
}
};
int main(){
double a = 123.123;
B* c = new B;
int b = static_cast<int>(a);
//B* d = static_cast<B*>(c);
A* d = static_cast<A*>(c);
d->setnum();
cout << "b = " << b << endl << "d->num = " << d->m_num << endl;
}
如果不是父子关系的static的转换是不被允许的,编译器会直接抛出错误。
如果不是指针间的转换,而是对象间类型转换。此时会调用对应类的拷贝构造函数,如果没有定义相关的拷贝构造函数,那么编译器同样会抛出错误。
四、dynamic_cast
关于dynamic_cast,它主要是用于同层类之间的类型转换,或是指针或引用不同层级间的下行转换。
比如一个基类对象的指针,可以通过dynamic_cast安全的转换为派生类对象指针。为什么说是安全的转换呢?这里就和static_cast进行比对了。前面提到的static_cast很容易出现派生类指针指向一个父类对象,这样对于指针来讲就是指向了一个不完整的内存空间,很容易出现非法访问的情况。而dynamic_cast会在转换时识别所指向的对象类型。
如果能正确转换则返回对应类型指针,不能转换则返回空指针。
如果引用不能正常转换则会抛出异常。
所以当我们在使用dynamic进行类型转换时需要对返回的指针进行校验。
同时,dynamic_cast的使用必须要求对应的类包含虚函数。因为dynamic_cast是C++当中RTTI的一种,RTTI的具体实现依赖于虚函数表中的type_info对象。
type_info是一个构造函数、拷贝构造和赋值都非公有的一个类。其中的name方法可以输出当前类的类型名称。而在多继承的情况下,派生类存在多个虚函数指针,他们分别指向虚函数表中父类继承过来的部分。而在指针的前一位置(前4字节),则存放的是type_info对象的指针,并且指向的是同一地址。所以无论当前指针是什么类型,我们都可以以此准确的推断出当前指向对象的类型。
关于RTTI这一特性,后面会专门写一篇文章来介绍。