在C语言中,我们的类型强转都是:
int a = (int)b;
当然在C++中,提供了语言级别的四种类型转换方式:
- const_cast:去掉常量属性的一个类型转换
- static_cast:提供编译器认为安全的类型转换
- reinterpret_cast:类似于C风格的强制类型转换
- dynamic_cast: 主要用在继承结构中,可以支持RTTI类型识别的上下转换
const_cast
为去掉常量属性的一个类型转换:
const int a = 10;
//int *p = &a; //报错!!
在这里,我们肯定是不能拿普通之战指向一个常量类型的,
我们可以使用C风格的方式进行类型转换,我们也可以用C++风格的类型转换,想把它转成什么类型,尖括号<>就写什么类型
int *p1 = (int*)&a;
int *p2 = const_cast<int*>(&a);
其实这个类型都是语言级别提供的,所以我们无法查看它的源码。
但其实,他们在汇编基本的语法都是一样的:
lea eax, |a|
mov dword ptr [p1], eax
lea eax, |a|
mov dword ptr [p2], eax
但是在编译阶段,还是有所不同的:
- const int a 在内存中是占4个字节的
- 如果是C风格的类型转换,我们可以
double *p1 = (double*)&a
,double是8个字节的,但是仍然可以编译通过。 - 如果我们想通过
char *p2 = const_cast<char*>(&a)
, 用不同类型的指针指向一个整型变量,编译会直接报错。
这样可以消除可能的错误,比如说我们的 const int a 只有4个字节有效,但是 double *p1可以访问8个字节的内存
下一个测试:
const int a = 10;
//const_cast 中的类型必须是指针、引用或指向对象类型成员的指针
int b = const)cast<int>(a); //编译错误!
const_cast<这里必须是指针或者引用类型>
总结:
1. 我们的const_cast<>
必须和基础类型保持一致
2. const_cast 中的类型必须是指针或者引用
⭐️static_cast
这是我们用的最多的类型转换。
static_cast几乎能做任何类型的转换,不过他主要是提供编译器认为安全的类型转换。
int main () {
int a = 10;
char b = static_cast<int>(a);
}
这种类型转换是被允许的。
但是我们看这个例子:
int *p = nullptr;
short* b = static_cast<short*>(p);
编译器报错提示为:static_cast from 'int *' to 'short *' is not allowed
。
也就是说,当两个类型之间没有任何联系的时候,这种类型转换会被否决。
所以该类型转换其实在大部分情况下都给到了我们编译时的检查。
那么基类和派生类类型能不能用static_cast呢?
当然可以了!因为他们就是有联系的。
retinterpret_cast
这就是C++语言级别提供的和C一样的类型转换风格。
⭐️dynamic_cast
支持RTTI类型识别的上下转换,主要用于继承结构中。
举例
我们先定义三个类,其中Base为抽象基类,Derive1、Derive2分别继承自Base类。
并且我们在类A中有虚函数 virtual void func() = 0;
随后我们提供一个统一接口void showFunc(Base *p)
来调用func
class Base {
public:
virtual void func() = 0;
};
class Derive1 : public Base {
public:
void fun() { cout << "call Derive1::func" << endl; }
};
class Derive2 : public Base {
public:
void fun() { cout << "call Derive2::func" << endl; }
};
void showFunc(Base *p) {
p->func(); // 动态绑定
}
我们做如下调用:
int main () {
Derive1 d1;
Derive2 d2;
showFunc(&d1);
showFunc(&d2);
return 0;
}
结果如下:
call Derive1::func
call Derive2::func
那么现在我们突然多了一个需求:
我们需要在Derive2里面写一个新需求:
class Derive2 : public Base {
public:
void func() { cout << "call Derive2::func" << endl; }
//Derive2实现新功能的API接口函数
void derive02func() {
cout << "call Derive2::derive02func" << endl;
}
};
也就是说,现在我们showFunc统一接口,当他动态绑定的是Derive2类的话,我希望它能调用新功能derive02func
。我们应如何解决呢?
void showFunc(Base *p) {
p->func(); // 动态绑定
}
dynamic_cast的使用
此时我们需要关注 *p的类型,当它是Derive2类型的,我们就需要去调用derive02func
。
- 第一种就是通过
typeid(*p).name == "Derive2"
来判断是否为Derive2类型
但其实我们不会使用这种比较字符串的方式来实现。 - 用 dynamic_cast !
void showFunc(Base *p) {
//dynamic_cast会检查p指针是否指向的是一个Derive2类型的毒对象
Derive2 *pd2 = dynamic_cast<Derive2*>(p);
if (pd2 != nullptr) {
pd2->derive02func();
} else {
p->func();
}
}
第一部分的语法为,把Base类型的指针转换成Derive2类型的指针。
这是因为首先p指向的对象里有vfptr虚函数指针,它指向了vftable虚函数表,其中放了RTTI信息。
如果是,dynamic_cast转换类型成功,返回Derive2对象的地址给pd2;
否则,dynamic_cast成功失败,返回nullptr;
所以我们有以下语法:
if (pd2 != nullptr) {
pd2->derive02func();
} else {
p->func();
}
由于我们的derive02func()是新写的函数,基类中也不存在该虚函数,所以只能是静态绑定,我们也只能通过Derive2类型的指针来进行访问。
现在再进行调用:
int main () {
Derive1 d1;
Derive2 d2;
showFunc(&d1);
showFunc(&d2);
}
结果如下:
call Derive1::func
call Derive2::derive02func
总结NOTE:
dynamic_cast往往用在继承结构中,特别是存在虚函数表的情况下,该方法对于统一接口的改进,使得多态能够变得更加灵活。