在C++或者大部分程序开发语言中,某些类型之间是有关联的,比如int和long,signed int和unsinged int等。这种相关联的类型之间是可以相互转换的。
一般这种转换可以分为两类:
- 隐式转换
自动执行的转换,程序中并没有显式指定类型转换。比如int ival = 3.14 + 3;
- 显式转换
也叫做强制类型转换。在代码中将某个类型强制转换成另一个相关类型。
比如int i = 2; double d = static_cast<double>(i);
一、隐式转换
隐式类型转换是编译器自动进行的类型转换。下面是C++中一些重要的隐式类型转换规则:
1.1 算术类型转换
算术类型转换遵循一定的等级规则,一般
* 整型提升:较小的整型(如`char`、`short`)会被提升为`int`或`unsigned int`
* 无符号与有符号混合:当无符号类型和有符号类型混合时,如果无符号类型的范围大于或等于有符号类型,有符号类型会转换为无符号类型。
* 浮点提升: `float`会被提升为`double`
* 整型和浮点型混合: 整型会被转换为浮点型
1.2 数组转换成指针
很多用到数组的表达式中,数组会自动退化成指向数组首元素的指针:
int ia[10];
int* ip = ia; // ia转换成指向数组首元素的指针
不过存在一些例外情况,以下这些情况数组不会自动转换为指针:
sizeof
sizeof
返回整个数组的大小,而不是指针的大小。
size_t size = sizeof(ia);
&
取地址运算符
当对数组使用取地址运算符&
时,结果是指向整个数组的指针,而不是指向单个元素
int (*pa)[10] = &ia; // pa 是指向含有10个整数的数组的指针
- 引用
当数组用来初始化引用时,它不会转换为指针。也就是说允许引用绑定到整个数组。
int (&ref)[10] = ia;
- 模板参数
template<typename T>
void func(T param) {
// ...
}
int ia[10];
func(ia); // T 是 int[10] 类型,不是 int*
decltype
运算符
deltype
返回表达式的确切类型,所以数组不会转换为指针。
decltype(ia) ib; // ib的类型是int[10]
1.3 指针的转换
- 常量整数值0 和 字面值
nullptr
可以转换成任意指针类型。 - 指向任意非常量的指针能转换成 void*。
- 指向任意对象的指针能转换成 const void*.
- 继承关系的类型之间的转换。比如子类对象的指针可以隐式转换为父类对象的指针。
1.4 bool型转换
跟C语言一样。如果指针或算术类型的值为0,则转换结果为false;或者为true。
1.5 常量转换
TODO
1.6 构造函数或转换函数
单参数构造函数:具有一个参数的构造函数可以用来隐式地从该参数类型转换为类类型。
类型转换函数:可以实现一个类型转换函数将该类的实例隐式转换成其他类型。
#include <iostream>
#include <string>
class MyString {
private:
std::string data;
public:
MyString(const char* str) : data(str) {}
// 类型转换函数,将 MyString 转换为 std::string
operator std::string() const {
return data;
}
};
int main() {
// 隐式转换,单参数的构造函数。这里直接将C字符串转换成了MyString类的实例。
MyString myStr = "Hello, World!";
// 隐式转换:MyString 通过类型转换函数转换为 std::string
std::string stdStr = myStr;
// 使用转换后的 std::string
std::cout << stdStr << std::endl;
return 0;
}
这里插一下explicit
关键字,C++11引入,它可以用于构造函数,要求显式转换。如果上面MyString前面有explicit
关键字的话,那么MyString myStr = "Hello, World!";
是会报错的,必须显式转换。
二、显式转换
C++提供了四种显式转换运算符。
2.1 static_cast
用于非多态类型的转换。主要用在数值类型之间的转换,或派生类到基类的转换。
double d = 9.5;
int i = static_cast<int>(d); // 转换 double 为 int
2.2 dynamic_cast
主要用于处理多态性,它可以在类的继承层次中安全地进行上行转换(从基类到派生类),如果转换失败,它会返回nullptr。
class Base { virtual void dummy() {} };
class Derived: public Base { int a; };
Base *pBase = new Derived;
Derived *pDerived = dynamic_cast<Derived*>(pBase); // 这里将基类指针转换成派生类的指针是安全的
2.3 const_cast
const_cast
用于改变对象的const
或volatile
属性, 它不改变对象本身的类型。
- 移除
const
属性const int ci = 10; int *pi = const_cast<int*>(&ci); // 移除 const 属性
- 添加
const
属性
这种用法不太常见。void process(const int* ptr) { // 只读操作 } int value = 10; process(const_cast<const int*>(&value)); // 将 int* 转换为 const int*
- 类成员函数
如果一个类的成员函数既有const
版本也有非const
版本,可以使用const_cast
在它们之间转换。class MyClass { public: void func() const { // 做一些只读操作 } void func() { // 可以修改对象状态 } }; const MyClass obj; obj.func(); // 调用 const 版本 const_cast<MyClass&>(obj).func(); // 调用非 const 版本
- 改变
volatile
限定符
用的比较少,可能只有硬件或多线程相关编程的时候,有使用场景。
下面假设reg
是一个代表硬件寄存器的变量,默认以volatile
的方式访问。
代码中使用int main() { volatile int reg= 42; // 正常访问: 访问被视为 volatile,每次访问都会重新从硬件读取 std::cout << "Volatile access: " << reg<< std::endl; // 通过 const_cast 移除 volatile 属性 int& nonVolatileReference = const_cast<int&>(reg); // 非 volatile 访问: 编译器可能优化这个读取 std::cout << "Non-volatile access: " << nonVolatileReference << std::endl; return 0; }
const_cast
来获得一个非volatile
的对变量reg
的引用,以便以非volatile
的方式来访问它。
2.4 reinterpret_cast
提供一种低级转换,它可以将任何指针转换成任何其他类型的指针。它也可以用于证书和指针之间的转换。
这种转换可能导致运行时错误,使用时应非常谨慎。
long p = 51110980;
char *pc = reinterpret_cast<char*>(p); // 将 long 转换为 char* 类型
2.4 注意事项
显式转换应当小心使用,因为不当的使用可能导致数据丢失、类型不兼容、内存错误等问题。
尽可能使用 static_cast 和 dynamic_cast,因为它们相对更安全。尽量避免使用 reinterpret_cast,除非确实需要进行低级的指针操作。