目录
C++——类型转换
1.C语言的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化
C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
-
隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
-
显式类型转化:需要用户自己处理
// C语言的类型转换,分为隐式类型转换和显式类型转换
int i = 1;
double d = 2.11;
i = d; // 这里发现了隐式类型转换
cout << i << endl; // 2
int n = 4;
int* np = (int*)n; //显式的强制类型转换
cout << np << endl; // 0000000000000004
缺陷:转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
2.C++的类型转换
C风格的转换格式很简单,但是有不少缺点的:
-
隐式类型转化有些情况下可能会出问题:比如数据精度丢失
-
显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格
**标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:**分别为 static_cast、reinterpret_cast、const_cast、dynamic_cast
2.1static_cast、reinterpret_cast
这里先将前2个,前2个比较简单易懂,并且都是C语言里有的类型转化
- static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换
- reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型
下面是使用的例子:
// 关于C++的类型转化的学习
int main1()
{
// C语言的类型转换,分为隐式类型转换和显式类型转换
int i = 1;
double d = 2.11;
i = d; // 这里发现了隐式类型转换
cout << i << endl; // 2
int n = 4;
int* np = (int*)n; //显式的强制类型转换
cout << np << endl; // 0000000000000004
// 上面是C语言的类型转换,C++觉得这样不规范,于是想将类型转换规范一下
// 为了增强类型转换的可视性,C++引入了4种命名的强制类型转化符
// 分别为 static_cast、reinterpret_cast、const_cast、dynamic_cast
d = static_cast<double>(i); //static_cast对应C语言的隐式类型转换(相近类型)
cout << d << endl;
np = reinterpret_cast<int*>(i); //reinterpret_cast对应C语言的大部分的强制类型转换(不相近类型)
cout << np << endl;
return 0;
}
2.2const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值
下面是讲解一个const_cast会遇到的一个问题:
int main()
{
const int a = 10;
//int* pa = &a; //这样是不被允许的
int* pa = const_cast<int*>(&a); //const_cast将a的connst属性去除掉了[也是属于强制类型转换]
cout << *pa << endl; // 10
*pa = 20; //能改是因为类型转换起作用了,将const int* 强制转化为int*
cout << *pa << endl; // 20
cout << a << endl; // 10 [思考:为什么这里是10,不是已经通过指针解引用找到a并且修改了吗]
// 这是因为编译器对const对象存取进行了优化导致的
// a是一个const对象,a原来的值,也就是10会被放在了寄存器当中【因为觉得const对象不会被修改】
// 实际上内存的值已经被修改为20了。但是这里打印是从寄存器拿的数据,所以打印的是10
// 这里就体现了随便强转可能会导致代码出现一些问题
// 因此为了解决这个问题,需要给a加上一个关键字,volatile【作用就是禁止编译器做存取优化】
//volatile const int a = 10;
return 0;
}
2.3dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
为了更好的理解,我们需要知道两个概念:
- 向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
- 向下转型:父类对象指针/引用->父类指针/引用(不安全的类型转化,可能会报错,要用dynamic_cast)
注意:
- dynamic_cast只能用于父类含有虚函数的类
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
为什么父类一定要有虚函数呢,因为dynamic_cast识别指针是父类还是子类是通过虚表(虚表中有标识对象的信息,可以判断是什么类)来识别的,而虚表(虚函数表)就一定要有虚函数的存在。
下面是使用的例子:
//dynamic_cast是c语言里面没有的一种强制类型转化[一般用于多态的场景]
class A
{
public:
virtual void f()
{}
int _a;
};
class B : public A
{
public:
int _b;
private:
};
void f_cast(A* pa)
{
// 在这个函数会有个问题,如何区分pa是指向父类还是指向子类
// 下面这个代码是否出错是不确定的,当pa指向父类对象,那么就会报错,指向子类对象那么就不会报错
//B* pb = (B*)pa; //因此这句话是不安全的,并且c语言无法解决,但是C++可以解决
B* pb = dynamic_cast<B*>(pa);
// dynamic_cast为了安全的类型转化会做下面的事情:
//如果pa指向子类对象,就转化成功,如果pa指向父类对象就转化失败,返回一个nullptr、
if (pb != nullptr)
{
// 走进来说明转化成功,即pa指向子类对象
cout << "转化成功,pa指向子类对象\n";
pb->_a = 1;
pb->_b = 1;
}
else
{
cout << "转化失败,pa指向父类对象\n";
}
}
int main()
{
A a;
B b;
a = b;
A* pa = &a;
pa = &b;
//在之前继承的时候就学过,在c++中子类对象、指针、引用是可以赋值给父类的对象、指针、引用,这是切片。
// 这是c++语法天然支持的,这是向上转换
// 如果是父类的指针或引用,传给子类的指针,这是向下转换,有可能能成功,要根据情况分析
A* paa = &a;
B* pbb = (B*)paa; //这里就是向下切换,父类指针进行强转之后给到子类,这是不安全的
A* p1 = &a;
f_cast(p1); //这里会报错
B* p2 = &b;
f_cast(p2);
return 0;
}
2.4总结
强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。
建议:避免使用强制类型转换
3.RTTI(了解)
RTTI:Run-time Type identification的简称,即:运行时类型识别
C++通过以下方式来支持RTTI:
-
typeid运算符
-
dynamic_cast运算符
-
decltype
4.思考
- c++四种类型转换分别是什么?
- c++的四种类型转换的应用场景分别是什么?