目录
C语言中的类型转换
C++中的类型转换
C++中的类型转换操作符
static_cast
reinterpret_cast
const_cast
volatile关键字
赋值兼容
dynamic_cast
C语言中的类型转换
基本概念:赋值运算符左右两侧类型不同,或形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,需要发生类项转换,C语言中有两种形式的类型转换,隐式类型转换和显示类型转换
注意事项:有关联的类型才可以相互转换,小类型会转换为大类型
- 整型之间的隐转(int、long、short)
- 整型和浮点数间的隐转
- 整型和字符型间的隐转
- bool和整型、指针间的隐转(整数0和空指针均表示假)
- 整型和指针间的强转(地址的本质是一个编号所以可以)
//整型转换为指针
//缺点:在绝大多数情况下是不安全的,除非你确切知道这个整数表示的内存地址是有效的
int number = 1234;
int *ptr = (int *)number;
//指针转换为整型:通常用于获取指针的内存地址
//缺点:如果在64位系统上,将指针转换为32位整数(如 int),可能会导致数据丢失。因此,建议使用 intptr_t 或 uintptr_t 类型,它们在 <stdint.h> 头文件中定义,可以确保适当的大小来存储指针值
int *ptr = &number;
int address = (int)ptr;
关于intptr_t和uintptr_t可以查看:intptr_t、uintptr_t数据类型的解析
- 不同类型的指针间的强转
//不同类型的指针间的强制类型转换
char s = 'a';
char* ptr1 = &s;
int* ptr2 = (int*) ptr1;
显示类型转换的缺点:
- 可能导致数据丢失:当从一个较大的数据类型转换为一个较小的数据类型时,可能会丢失数据。例如,将 double 转换为 int 时,小数部分会被截断
double d = 3.14; int i = (int)d; // i = 3, 小数部分丢失
- 降低代码可读性:频繁使用显式类型转换会使代码变得难以阅读和理解,尤其是当类型转换逻辑复杂时
float f = (float)((int)doubleValue * 2.0);
- 增加错误风险:进行类型转换时,容易忽略一些潜在的错误,如溢出或数据精度问题,导致难以发现的bug
long longValue = 1234567890123456789LL;
int intValue = (int)longValue; // 可能会溢出
隐式类型转换的缺点:
-
隐蔽的数据丢失:将
int
转换为float
时,如果整数的值超过了浮点数的精度范围,可能会丢失精度
int i = 123456789; float f = i; // 可能会丢失精度
-
难以调试和维护:隐式类型转换发生在幕后,可能导致意外的结果和难以调试的问题。出错时需要仔细检查代码,以确保所有隐式转换都符合预期
//int被转换为size_t,当传入的pos = 0时,会陷入死循环
void Insert(size_t pos)
{
int end = 10;
// 比较的时候end会隐式类型转换成size_t,再比较
while (end >= pos)
{
cout << end << "挪走" << endl;
--end;
}
}
C++中的类型转换
基本概念:C++中通过构造函数和重载可以实现内置类型与自定义类型间、自定义类型间的转换
- 内置类型->自定义类型:借助构造函数,隐式类型转换
//单参数的构造函数,支持内置类型隐式转换成自定义类型
string s1 = "111111";
const string& s2 = "11111";
- 自定义类型->内置类型:重载某个类型(operator +类型名)
class A
{
public:
operator int()
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
A aa;
int a = aa;
int b = (int)aa;
cout << a << endl;
cout << b << endl;
return 0;
}
- 自定义类型->自定义类型:借助构造函数进行隐式类型转换(initializer_list和容器间)
C++中的类型转换操作符
基本概念:C++依然兼容C语言的两种类型转换方式,但是为了加强类型转换时的可视性,C++引入了四种类型转换操作符,它们是一些进行类型转换时的命名建议,并不强制
static_cast
基本概念:进行有关联的类型间的隐式类型转换(编译时进行,也叫静态转换)
int main()
{
//C++的隐式类型转换:明显
double d = 12.34;
int a1 = static_cast<int>(d);
cout << a1 << endl;//12
//C语言的隐式类型转换:不明显
int a2 = d;
cout << a2 << endl;//12
return 0;
}
reinterpret_cast
音标:riːɪnˈtɜːrprət
基本概念:进行强制类型转换,不进行类型检查
int main()
{
int a = 12;
// int* p = static_cast<int*>(a); 报错,int和int*不是两个相关的类型
int* p = reinterpret_cast<int*>(a);
cout << p << endl;
return 0;
}
const_cast
基本概念:删除对象的const属性
int main()
{
const int a = 10;
int* p = const_cast<int*>(&a); // 去除const属性
*p = 20; // 尝试修改值
cout << a << endl;//a == 10
cout << *p << endl;//*p == 20
return 0;
}
对比监视列表和控制台,可以发现虽然在内存中a的值变为了20,但是打印时a仍为原来的10,这是因为编译器知道const修饰的变量不会被修改,那么编译器在编译阶段就会对其优化,即编译器会将常量的值直接内联到使用该常量的地方,而不是每次都访问内存中的存储位置,这一优化技术称为“常量折叠”或“常量传播”
补充:const修饰的局部变量仍然存放在栈上,修饰的全局变量才放在常量区中
volatile关键字
基本概念:用于指示编译器不对变量进行优化(带入上述代码再试一试即可)
volatile const int a = 10;//表示不会对a进行优化
结论:const_cast和reinterpret_cast都是强制类型转换(const_cast的第一个例子中的a去掉const修饰也能const_cast也能正常使用,且a打印时也为20),但是单独将const_cast拿出来就是因为可能出现因为编译器对const修饰的变量进行优化而导致的内存可见性
赋值兼容
基本概念:有继承关系的派生类与基类之间所遵循的赋值规则,包括向上和向下转型
- 向上转型:派生类对象赋值给基类对象(发生切片,切片 != 类型转换)、派生类的指针/引用赋值给基类的指针/引用(通常情况下都支持)(即这两种情况正常情况下都是可以支持的)
#include <iostream>
using namespace std;
class A
{};
class B : public A
{};
int main()
{
//向上转型
B bb;
A aa = bb;//派生类对象直接赋值给基类对象,发生切片
A* ptr = &bb;//派生类对象的指针可以直接赋值给基类对象的指针
B& cc = bb;
A& str = cc;//派生类对象的引用可以直接赋值给基类对象的引用
return 0;
}
- 向下转型:基类对象赋值给派生类对象(除非有相关的构造函数、否则即使是强转也转不了)、基类对象的指针/引用赋值给派生类对象的指针/引用(可以通过强转实现,但有越界风险需要得到控制)
//有相应的构造函数才能实现基类对象到派生类对象间的转换
#include <iostream>
using namespace std;
class A {
public:
int baseData;
A() : baseData(0) {}
A(int data) : baseData(data) {}
};
class B : public A {
public:
B() {};
// 自定义构造函数,实现基类到派生类的转换
B(const A& a) : A(a) {
cout << "hello" << endl;
}
// 自定义赋值运算符,实现基类到派生类的转换
B& operator=(const A& a) {
cout << "world" << endl;
return *this;
}
};
int main() {
A aaa(10);
B bbb;
bbb = aaa; // 使用自定义赋值运算符
B bbb2 = aaa; // 使用自定义构造函数
return 0;
}
dynamic_cast
基本概念:为了能控制向下转型时的越界问题、C++提供了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 = 1;
};
void fun(A* pa)
{
// 向下转换:父->子
// pa指向子类对象,转回子类,是安全的
// pa指向父类对象,转回子类,是不安全的,存在越界的风险问题
// 不安全
//B* pb = (B*)pa;
// pa指向子类对象,转回子类,正常转换
// pa指向父类对象,转回子类,转换失败
B* pb = dynamic_cast<B*>(pa);
if (pb)
{
cout << pb << endl;
cout << pb->_a << endl;
cout << pb->_b << endl;
}
else
{
cout << "转换失败" << endl;
}
}
int main()
{
A a;
B b;
fun(&a);//父类转为子类
fun(&b);//子类转为子类
return 0;
}
注意事项:dynamic_cast在使用时,基类要有虚函数(有就行)
~over~