目录
C语言的类型转换可读性较差,难以区分;C++为了增强程序的可读性,在兼容C语言类型转换的基础上,又增加了独属于C++的四种类型转换。
本章会从C语言类型转换到C++类型转换,逐一进行介绍。
一、C语言中的类型转换
在C语言里,当赋值运算符左右两侧类型不同 / 函数形参与实参类型不匹配 / 函数返回值类型与接受返回值的类型不一致时,就会发生类型转换。
C语言中的类型转换分为两种:隐式类型转换 和 显式类型转换。
1、隐式类型转换
编译器在编译阶段自己进行的类型转换,通常是意义相近 / 相关的类型之间,可以进行类型转换,能转就转,转不了就会编译失败。
缺陷:有些情况下可能会出现一些问题,比如数据精度丢失。
2、显式类型转换
需要用户自己来处理的转换。
缺陷:代码可读性较差,所有的显式转换都是以同一种形式书写,这让我们跟踪错误转换变得困难。
3、示例代码与缺陷
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++标准为了增强程序可读性,引入了四种命名的强制类型转换操作符:
static_cast,reinterpret_cast、const_cast、dynamic_cast
1、static_cast
static_cast 主要用于非多态类型的转换(编译时就进行了转换,所以是静态转换)
编译器中任何的隐式类型转换都可以用 static_cast,代码可读性更高。一般用于意义相近的类型的转换;不能用于两个不相关类型之间的转换。
#include <iostream> using namespace std; int main() { double d = 20.15; int a = static_cast<int>(d); cout << a << endl; return 0; }
2、reinterpret_cast
reinterpret_cast 一般用于对有一定的关联但意义不相近的类型之间进行转换,可以将整形转换为指针,也可以把指针转换为数组;可以在指针和引用之间进行肆无忌惮的转换。
(使用的时候一定要慎之又慎,因为其实际上是从底层对数据进行了重新解释,对平台有很强依赖性,可移植性很差;而且如果类型之间差距过大,还可能造成数据损坏和未定义行为!)
#include <iostream>
#include <cstdint>
int main() {
// 示例 1: 将整型转换为指针
int value = 42;
void* ptr = reinterpret_cast<void*>(value);
std::cout << "Pointer from integer: " << ptr << std::endl;
// 再次转换回整型
int original_value = reinterpret_cast<intptr_t>(ptr);
std::cout << "Integer from pointer: " << original_value << std::endl;
// 示例 2: 将指针转换为整型
int* int_ptr = new int(123);
uintptr_t address = reinterpret_cast<uintptr_t>(int_ptr);
std::cout << "Address as integer: " << address << std::endl;
// 再次转换回指针
int* original_int_ptr = reinterpret_cast<int*>(address);
std::cout << "Value at address: " << *original_int_ptr << std::endl;
delete int_ptr;
// 示例 3: 在不同类型的指针之间转换
class A {};
class B {};
A* a = new A();
B* b = reinterpret_cast<B*>(a);
// 注意:这里的转换没有任何实际意义,除非你知道这两个类有某种特殊的关联
std::cout << "Pointer to A: " << a << std::endl;
std::cout << "Pointer to B (cast from A): " << b << std::endl;
delete a;
// 示例 4: 在指针和引用之间转换
int ref_value = 789;
int& ref = ref_value;
// 将引用转换为指针
int* ref_ptr = &ref;
std::cout << "Pointer from reference: " << ref_ptr << std::endl;
// 将指针转换为引用
int& ref_from_ptr = *reinterpret_cast<int*>(ref_ptr);
std::cout << "Reference from pointer: " << ref_from_ptr << std::endl;
return 0;
}
3、const_cast
const_cast 最常用的用途是去除 / 增加一个常变量的 const 常量属性,也是四大类型转换中唯一的一个可以操作常变量的转换。参数必须是常变量的指针 / 引用。
#include <iostream> using namespace std; int main() { const int num = 2014; int& p = const_cast<int&>(num); cout << num << " " << p << endl; p = 2015; cout << num << " " << p << endl; return 0; }
观察运行结果我们会有个疑问,p 不是 num 的别名吗,为什么 P 改变了,并没有改变 num呢?
这是因为,const_cast 也是个静态转换,在编译期间,编译器会对 const 类型的常变量进行优化,会把 const 常变量直接放入寄存器甚至直接以常量替换,取常变量的值不会从内存里面取,因此 p 在内存中的更改并不会导致寄存器中 num 的取值变化。所以严格意义上说,对常变量的更改也算是一种未定义行为,要尽量避免。
(PS:如果想让结果正确,可以在 const 前面加上 volatile 关键字,这个关键字会指示编译器不要优化 const ,这样每次取值就直接从内存中取了,结果也就正确了)
#include <iostream> using namespace std; int main() { volatile const int num = 2014; int& p = const_cast<int&>(num); cout << num << " " << p << endl; p = 2015; cout << num << " " << p << endl; return 0; }
4、dynamic_cast
dyanmic_cast是一个动态转换,就是在运行时才进行转换,一般用于向下转换,即把父类的指针 / 引用转换成子类的指针 / 引用。
向上转换:子类对象的指针 / 引用 -> 父类对象的指针 / 引用(不需要强转,赋值兼容原则即可)
向下转换:父类对象的指针 / 引用 -> 子类对象的指针 / 引用(利用 dynamic_cast)
一些注意事项:
1、dynamic_cast 的父类必须含有虚函数,即只能用于多态类型的转换。
2、父类对象无论如何都转换成子类对象,但是父类的指针 / 引用可以通过 dynamic_cast 安全转换为子类的指针 / 引用。(static_cast / 直接转换都是不安全的,子类会比父类多出一块空间,如果转换后的父类指针 / 引用访问了这块不存在的空间,会造成越界;而dynamic_cast会先检查转换是否合法,合法才转换,不合法会直接返回)
#include <iostream> using namespace std; class A { public: virtual void test() { printf("我是A\n"); } }; class B : public A { public: virtual void test() { printf("我是B\n"); } }; void Test(A* pa) { // 使用 static_cast 进行转换 B* pb1 = static_cast<B*>(pa); // static_cast 不会检查是否越界,可能会转换失败 if (pb1) { pb1->test(); // 如果 pa 实际上不是 B 的实例,这可能会导致未定义行为 } else { cout << "static_cast 转换失败" << endl; } // 使用 dynamic_cast 进行转换 B* pb2 = dynamic_cast<B*>(pa); // dynamic_cast 会检查 if (pb2) { pb2->test(); // 如果转换成功,调用 B 的 test 方法 } else { cout << "dynamic_cast 转换失败" << endl; } } int main() { A* a = new A(); A* b = new B(); cout << "测试 A 类型的对象:" << endl; Test(a); // pa 实际上是 A 类型的对象 cout << "测试 B 类型的对象:" << endl; Test(b); // pa 实际上是 B 类型的对象 delete a; delete b; return 0; }
测试 A 类型的对象:
static_cast
成功将A*
转换为B*
,但调用test
方法时,由于pa
实际上是A
类型的对象,所以输出我是A
。
dynamic_cast
失败,因为pa
实际上不是B
类型的对象,所以输出dynamic_cast 转换失败
。测试 B 类型的对象:
static_cast
成功将B*
转换为B*
,调用test
方法时,输出我是B
。
dynamic_cast
也成功,因为pa
实际上是B
类型的对象,所以输出我是B
。
三、小总结
强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是
否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用
域,以减少发生错误的机会。强烈建议:避免使用强制类型转换。