1. C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
显式类型转化:需要用户自己处理
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);
}
缺陷:转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
C风格的转换格式很简单,但是有不少缺点的:
隐式类型转化有些情况下可能会出问题:比如数据精度丢失
显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。
2. C++强制类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
2.1 static_cast、reinterpret_cast、const_cast
#include<iostream>
using namespace std;
int main()
{
// 情况1:
int i = 1;
// C++规范转换 -- static_cast适用相似类型的转换(这些类型的表示意义差不多)
// 如下,将int类型转化为double类型
double d = static_cast<double>(i);
printf("%d, %.2f\n", i, d);
// 打印结果为
// 1
// 1.00
// 情况2:
int* p = &i;
// int* 是地址, int是整型,因此不是相似类型,也就不能够使用static_cast
// C++规范转换 --reinterpret_cast适用于不相关的类型之间的转换
// reinterpret v.重新解释
// 如下将int*类型转化为int类型
int address = reinterpret_cast<int>(p);
printf("%x, %d\n", p, address);
// 打印结果为
// 56f9a8
// 5700008
// 情况3:
// C++规范转换 -- const_cast 去掉const属性。单独分出来,警示你这个很危险,用的时候谨慎一点
const int a = 2;
// 如下,去掉了&a的const属性
int* p1 = const_cast<int*>(&a);
*p1 = 3; // 将a内存中对应的值修改为3
cout << a << endl;
cout << *p1 << endl;
// 打印结果为
// 2
// 3
// 为什么a和*p1打印的结果不一致呢?
// 这是因为a的类型为const int,编译器默认a是不会被改变的
// 因此将a的值,直接拿到了寄存器来使用,不再到内存中读取a的值(这是一种编译器的优化),打印a时,打印的是寄存器中的值
// 而*p1指向的是a内存中的位置,打印的a内存对应的值
// 情况4:
// 使用关键词volatile,编译器就不会进行优化,读取a时,会到内存中进行读取,而不是在寄存器中读取
// volatile /ˈvɑːlət(ə)l/ adj.易变的
// 使用关键词修饰volatile,那么编译器就不会进行优化
volatile const int a1 = 2;
int* p2 = const_cast<int*>(&a1);
*p2 = 3;
cout << a1 << endl;
cout << *p2 << endl;
// 打印结果为
// 3
// 3
return 0;
}
2.2 dynamic_cast
dynamic_cast
用于将一个基类对象的指针/引用转换为派生类对象的指针或引用(动态转换)
向上转型:派生类类对象指针/引用->基类指针/引用(不需要转换,赋值兼容规则)
向下转型:基类对象指针/引用->派生类指针/引用(用dynamic_cast转型是安全的)
注意:
dynamic_cast
只能用于基类含有虚函数的类dynamic_cast
会先检查是否能转换成功,能成功则转换,不能则返回0
- 错误演示
#include<iostream>
using namespace std;
// 基类
class A
{
public:
virtual void f() {}
int _a = 0;
};
// 派生类
class B : public A
{
public:
int _b = 0;
};
void Func(A* ptr)
{
// 直接转换是不安全的
// 当ptr指向的是派生类对象B时,那么派生类对象转为派生类对象是没有什么问题的
// 当ptr指向的是基类对象A时,那么基类对象转为派生类对象存在越界风险
// _b是属于派生类对象B,如果将基类对象转为子类对象,那么基类对象也可以访问到_b,这就造成了越界,是不安全的
B* bptr = (B*)ptr;
cout << bptr << endl;
bptr->_a++;
bptr->_b++;
cout << bptr->_a << endl;
cout << bptr->_b << endl;
}
int main()
{
A aa;
B bb;
Func(&aa);
Func(&bb);
return 0;
}
- 使用dynamic_cast进行转换
#include<iostream>
using namespace std;
// 基类
class A
{
public:
virtual void f() {}
int _a = 0;
};
// 派生类
class B : public A
{
public:
int _b = 0;
};
void Func(A* ptr)
{
// C++规范的dynamic_cast是安全的
// 如果ptr是指向基类,则转换失败,返回空
// 如果ptr是指向派生类,则转换成功
B* bptr = dynamic_cast<B*>(ptr);
cout << bptr << endl;
// 当bptr不为空,说明是派生类转派生类,转换成功
// 当bptr为空,说明是基类转派生类,转换失败,此处进行判断,避免越界,保证安全访问
if (bptr)
{
bptr->_a++;
bptr->_b++;
cout << bptr->_a << endl;
cout << bptr->_b << endl;
}
}
int main()
{
A aa;
B bb;
Func(&aa);
Func(&bb);
return 0;
}