目录
c++的类型转换
c语言和c++是一个强类型的语言,也就是变量和变量之间有明显的类型的区分
1. C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
2. 显式类型转化:需要用户自己处理

缺陷:转换的可视性比较差,所有的转换形式都是以一种相同的形式书写,难以跟踪错误转换
2. 为什么c++需要四种类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
C风格的转换格式很简单,但是有不少缺点的:
1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
2. 显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格
c++兼容c语言留下来的隐式类型转换和显示转换,但是c++觉得c语言做的不规范,c++想规范一下,标准c++为了加强类型的可视性,引入了四种命名的强制类型转换操作符
3. c++强制类型转换解析
3.1 static_cast
对于c语言中的隐式类型转换,相近的类型就使用这个
int i = 1;
double d = 8.88;
i = static_cast<int>(d);
cout << i << endl;
3.2 reinterpret_cast
对于c语言中的强制类型转换,不相近的类型就用这个
int i = 1;
double d = 8.88;
i = static_cast<int>(d);
cout << i << endl;
int* p = nullptr;
p = reinterpret_cast<int*>(i);
cout << p << endl;
总结:如果你不相近的类型使用了static_cast就不可以,如果相近的类型使用了reinterpret_cast也不可以,总之c++是为了实现你一看用哪个cast就知道,但注意你并没有改变原来变量的类型,也就是d还是8.88,还是double类型
3.3 const_cast
const_cast就是去除const属性的
const int ci = 10;
int* pi = const_cast<int*>(&ci);
*pi = 20;
cout << *pi << endl;
cout << ci << endl;
这种情况是未定义行为:
原变量是const修饰的,但是经你这么修改,原变量本来是const,但是可以通过指针来修改,这种是不合法的,结果是未定义的
// 可能的结果: // 1. 程序崩溃; // 2. 输出 ci 仍为 10(编译器优化,a 被存储在只读内存/寄存器); // 3. 输出 ci为 20(极少数未优化场景)。我的编译器优化之后ci仍然为10,原因
可以看到通过调试已经修改了变量的值,但是为什么打印ci是10
这是是去寄存器当中找的,就是因为你原来是const对象,它认为你是不可能修改的,所以放到寄存器当中,这样速度快,但是你这里修改的是内存当中的值(ci当中内存是变20了),然后打印的时候由于ci寄存器有直接就拿了,本质是由于编译器对const对象存取优化机制导致的,甚至有些直接优化成常量10了
为了避免编译器优化,可以通过加volatile关键字,让编译器每次都到内存当中取
volatile const int ci=10;
但是一般不要这么编写,本来好好的const常量被你修改了之后能变
一般都是const指针或者const引用去除const属性(只有这种场景是合法的)
3.4 dynamic_cast
用于多态类型的转换
c++中子类对象可以赋值给父类的对象、指针、引用、这个过程是语法天然支持的,可以成功
https://blog.youkuaiyun.com/Laydya/article/details/148145002
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则) 向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意: 1. dynamic_cast只能用于含有虚函数的类 2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
class A {
public:
virtual void f() {}
int _a;
};
class B :public A {
public:
int _b;
};
void f_cast(A* pa) {
//如果想要区分pa是指向父类,还是子类对象?
B* pb = (B*)pa;
pb->_a = 1;
pb->_b = 2;
}
int main() {
A a;
B b;
A* pa = &a;
f_cast(pa);
B* pb = &b;
f_cast(pb);
return 0;
}
这里强制类型转换了之后,其实就是这个pb指针能看到的东西更多了,也就是指针的意义就是根据类型能够看到多大的空间,由于你原来是A的类型,但是你这里强制类型转换成b了,所以看到的东西变多了,然后你pb-> _b 就会越界访问
如果你本身就是一个子类对象,那就没问题,因为你本身就可以看到这么多东西
class A {
public:
virtual void f() {}
int _a;
};
class B :public A {
public:
int _b;
};
void f_cast(A* pa) {
//如果想要区分pa是指向父类,还是子类对象?
B* pb = dynamic_cast<B*>(pa);
if (pb != nullptr) {
cout << "转换成功" << endl;
pb->_a = 1;
pb->_b = 2;
}
else {
cout << "转换失败" << endl;
}
}
int main() {
A a;
B b;
A* pa = &a;
f_cast(pa);
B* pb = &b;
f_cast(pb);
return 0;
}

使用dynamic_cast,如果pa是指向子类对象,则转换成功
反之如果是父类对象,则转换失败
注意:只能针对继承中的多态类型(父类必须包含虚函数)如果你把A里面的虚函数去掉那就编译不过去了
原理:dynamic_cast通过去虚表的上方存储的标识信息,来判断指向父类对象还是子类对象,如果没有虚函数,则没有虚表,就无法判断了


总结:尽量少用类型转换,如果要用,就用规范一些,让别人一看到就知道你在干嘛
4. RTTI
RTTI(Run-Time Type Information,运行时类型信息)是 C++ 提供的在程序运行阶段获取对象 / 指针实际类型信息的机制,核心作用是打破编译期的类型绑定,让程序在运行时识别多态类型(比如基类指针指向的派生类对象的真实类型)。
C++ 标准中,RTTI 的支持是可选的(但主流编译器如 GCC/Clang/MSVC 均默认开启),且仅对带有虚函数的类(多态类)能正确返回动态类型;非多态类的 RTTI 仅能返回编译期类型。
C++通过以下方式来支持RTTI:
1. typeid运算符
typeid 运算符返回 std::type_info 对象,包含类型的名称、哈希值等信息,可用于:
-
比较两个对象 / 指针的实际类型是否相同;
-
获取类型的名称(注意:名称格式由编译器决定,不一定是类的原名)。
-
只有多态才能返回指向的类型,如果没有多态返回的是编译期类型(基类),而非实际指向的派生类
2. dynamic_cast运算符
这个上面已经做了详细的讲解,也就是通过运行时的类型确定的,运行起来后通过查虚表,虚表里面有标识来辨别实际类型,仅仅针对多态



1901

被折叠的 条评论
为什么被折叠?



