目录
1.C语言中的类型转化
2.C++强制类型转换
2.1、static_cast
2.2、reinterpret_cast
2.3、const_cast
2.4、dynamic_cast
1.C语言中的类型转化
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
2. 显式类型转化:需要用户自己处理
看下面这段代码来理解:
void Test()
{
int i = 1;
// 隐式类型转换(意义相近的类型)
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显式的强制类型转换(意义相差比较大的类型),此时隐式便会报错
int address = (int)p;
printf("%x, %d\n", p, address);
}
int和double意义比较接近,所以可以隐式类型转化,将int升为double类型
而address时整型,p是指针,再隐式类型转化会出错,所以此时必须显式强制转化.
缺陷: 转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换.
下面这种场景也是让人非常难受的.
void insert(size_t pos, char ch)
{
size_t _size = 5;
int end = _size - 1;
while (end >= pos)
{
--end;
}
}
当pos=0时,end会--直到0,最后一次进去end会变成-1,然后再次与pos进行比较,此时-1< 0不符合条件应该跳出,然而这里却发生了一个隐式类型转化,将end从int(有符号)提升为size_t(无符号),这样-1就成了2^32-1,便造成了死循环.
所以C++为了解决这些问题,便提出了这四种强制类型转换.
2.C++强制类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast.
2.1、static_cast
static_cast用于非多态类型的转换(静态转换),编译器执行的任何隐式类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
int* p = &a;
//不支持不相关类型转化
int address = static_cast<int>(p);
}
只要记住,static_cast多用于隐式类型转化.
2.2、reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型.
可以理解为可强转为不相关的类型.
比如刚才上面的例子,static_cast不支持不相关类型转化,即不能将指针p转为int整型.我们改用reinterpret_cast即可.
int* p = &a;
//支持不相近类型转化
int address = reinterpret_cast<int>(p);
这样我们看到static_cast就知道这是相近类型转化. reinterpret_cast就知道是不相近类型转化.
2.3、const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值.
int main()
{
const int a = 2;
//错误的,由于a具有常性
//int* p = reinterpret_cast<int*>(&a);
int* p = const_cast<int*>(&a);
*p = 3;
}
利用const_cast,此时便成功把常量a的地址给了p,然后并且可以修改了.
但有一个奇怪的现象:
我们输出下a和*p的值,由于p的地址是也是a,对p修改也会影响a。
所以我们预期的结果应该都是3.我们输出一下:
cout << a << endl;
cout << *p << endl;
答案是2和3.为什么a没有被修改呢?我们调试观察一下:
奇怪的现象又出现了,内存中显示a也已经被修改为3了,怎么输出出来还是2呢
这源于编译器的优化,编译器认为a是const已经不能被改了,那么我每次从内存中取多慢,编译器便直接把a加载到寄存器里面去,每次访问直接从寄存器读,效率便会提高。因为是const所以不会写,这才敢放到寄存器.所以每次读取的都是一开始的2.
至于监视窗口,也是另外一个单独的进程,没有优化,而是实时检测的,它这个时候便去内存中取,显示的便是3了.
那么我们如果不想让编译器优化,每次就要从内存中取,这时就要在前面加上volatile关键字.
此时便得到了正确的结果.
下面一种转化是C++独有的一种转化.
2.4、dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用 -> 父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意: 1. dynamic_cast只能用于含有虚函数的类 2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr。
看这段代码:
class A
{
public:
int _a = 0;
};
class B : public A
{
public:
int _b = 1;
};
int main()
{
B bb;
//子类->父类
A aa1 = bb;
A& ra1 = bb;
}
以上是可以正常转化的,子类转成父类,会有切片操作,这我们可以理解的.
但是向下转型,父类转化成子类时,可以正常转吗?
首先要知道的是,父类对象是无论如何都不可以转化成子类对象的.
但是利用dynamic_cast可以把父类对象的指针或引用转化成子类的指针或引用.
class A
{
public:
virtual void f() {}
};
class B : public A
{};
//A*指针有可能指向父类,有可能指向子类
void fun(A* pa)
{
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
//如果pa是指向子类,那么可以转化,转化表达式返回正确的地址
//如果pa是指向父类,那么不可以转化,转化表达式返回nullptr
B* pb = dynamic_cast<B*>(pa);//安全的
//B* pb = (B*)pa;//不安全的,都会转化成功
if (pb)
{
cout << "转化成功" << endl;
}
else
{
cout << "转化失败" << endl;
}
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
总的来说:dynamic_cast真正的功能是区分指针是指向父类还是子类.
如果"父类"指针是父类指针,dynamic_cast转化成子类会出现错误.
如果"父类"指针是子类指针,dynamic_cast转化成子类会成功.
所以说dynamic_cast向下转型是安全的.
这样c++的四种类型转化也就全部讲完了.