C/C++ 类型转换
C/C++中的类型转换主要分为:隐式类型转换 & 显示类型转换(即强制类型转换)
隐式类型转换
在 C Primer Plus 中对隐式类型转换介绍了一下几大规则:
-
在表达式中,unsigned 和 signed 的 char 和 short 都会被系统自动转成 int,当 short 和 int 内存大小相同时(比如16 bit操作系统)unsigned short 会被转成 unsigned int(因为这个时候 unsigned short 比 int 大)
-
包含两种数据类型的任意运算中,两个值会被分别转成两种类型中的高级别的数据类型
-
类型的级别从低到高:long double > double > float > unsigned long long > long long > unsigned long > long > unsigned int > int,有一个特殊情况,当 long 和 int 的大小相同时,unsigned int 比 long 级别高,还有就是 short 和 char 类型没有出现是因为它们已经被 OS 转成了 int 或 unsigned int,(如:混合运算转换过程 $3+4/5.0F+6-9.0$,先计算 $4/5.0F$,4 转成 float 参与运算得到 0.8F,$3+0.8F$,3 转成 float 参与运算得到 3.8F,$3.8F+6$ 得到 9.8F,$9.8F-9.0$ 因为浮点数默认是 double 型,9.8F 被转成 double 型 9.8 参与运算得到 double 型 0.8,可以看出混合运算转换过程是一步一步进行的)
-
在赋值语句中,会被转换成赋值运算符左侧的类型,可能升级,也可能降级,不考虑四舍五入
-
作为函数参数时,char 和 short 会被自动转换成 int,float 会被转成 double
注意
隐式数据类型转换是 OS 在上述 5 种情况下自发进行的,对 coding 者来说是透明的,这里我没有继续深究,不再过多介绍,有些编译器可能在非上述情况下也进行了自动数据类型转换,那不是隐式数据类型转换,是编译器提供的便利,隐式数据类型转换只涉及基本的数据类型,指针不进行隐式数据类型转换
显示类型转换(即强制类型转换)
形式是 (type)data; 即小括号后面的数据被转成小括号内的数据类型
-
强转基本数据类型,12 + 12.2; 如果不进行强制类型转换,系统将自动进行隐式数据类型转换,转成两个数据类型中较高(占内存较大)的数据类型,也就是 double,这样也以最大限度的保证计算的精度,也可以进行强转,(如:12 + (int)12.2; 会把 12.2 先转成整型 12,然后再进行加法操作)
-
强转指针类型/强转地址类型,进行指针/地址类型的转换,需要注意两点,一是指针的类型,决定了指针的读写方式(也就是一次可以操作多少字节的数据),另一点是一定不要越界访问
int a = 12;
double *p = (double *)&a;
*p = 12.3;
这样的操作是非法的,int a; 只有 4Byte,double *p 一次可以操作 8Byte,明显是越界操作了
int a = 12;
float *p = (float *)&a;
*p = 23.2;
这样的操作是合法的(int 和 float 都占 4Byte),但因为 float 和 int 在内存中的存储方式不同,所以输出的数据可能与想象中的结果不同,但这是合法的操作
double d = 12.3;
int *p = (int *)&d;
*p = 34; // 操作前 4Byte
*(p + 1) = 45; // 操作后 4Byte
*(int *)((short*)p + 1) = 56; // 操作中间 4Byte
可以看出指针的操作是很灵活的,但是一定注意不要越界进行读写操作
C++11 中引入的四种强制类型转换方式
static_cast
用法:static_cast< type_name >(expression)
-
仅当 type_name 可被隐式转换为 expression 所属的类型或 expression 可被隐式转换为 type_name 所属的类型时,static_cast 的转换才是合法的
-
任意具有明确定义的类型转换,只要不包含底层 const,都可以进行转换
int *const i; // 顶层 const 表示指针本身是常量, 指针的指向不能变
const int *i; // 底层 const 表示指针所指向的对象是常量
-
用 static_cast 进行强制类型转换,没有运行时类型检查来保证转换的安全性
-
基类和子类之间的相互转换是合法的(其中基类转派生类不保证安全性),与不相关类的转换是不合法的
CFather father; // 父类
CSon son; // 子类
CFather* sonToFather = static_cast<CFather*>(&son); // 合法的
CSon* fatherToSon = static_cast<CSon*>(&father); // 合法的,可能不安全
COther* fatherToOther = static_cast<COther*>(&father); // 非法的
-
基本数据类型转换,例如:enum 转 int,int 转 enum,double 转 int 等
-
也可用于编译器无法自动进行的类型转换
int nNum = 10;
void* pTmp = static_cast<void*>(&nNum); // 任意非常量对象的地址存入 void*
int* pNum = static_cast<int*>(pTmp); // 将 void* 转回初始的指针类型
dynamic_cast
用法:dunamic_cast< type_name >( expression ) 在父类和子类之间进行安全的上行和下行转换
-
上行转换与 static_cast 效果是一样的
-
下行转换与 static_cast 相比增加了类型检测的功能,可保证转换的安全
-
上行转换,即把派生类的指针或引用转换成基类表示;下行转换:把基类指针或引用转换成派生类表示
适用情况
-
expression 是目标类型 type_name 的公有派生类
-
expression 与目标类型 type_name 类型相同(同类型转换)
-
expression 是目标类型 type_name 的公有基类(vs2017下测试必须是多态,其他平台未测试)
-
转换不成功返回空指针
CFather father;
CSon son;
CSon* fatherToSon = dynamic_cast<CSon*>(&father); // 父类转子类,必须是多态的情况
CFather* sonToFather = dynamic_cast<CFather*>(&son); // 子类转父类
CSon* sonToSon = dynamic_cast<CSon*>(&son); // 相同类型之间的转换
const_cast
用法:const_cast< type_name >( expression )
- 转换掉表达式的 const (只能改变运算对象的底层 const)或 volatile 属性,仅当 type 和 expression 一样的时候才合法
const CFather *father = new CFather;
CFather* nonconstFather = const_cast<CFather*>(father); // 合法
CSon* nonconstCSon = const_cast<CSon*>(father); // 非法
delete father;
reinterpret_cast
用法:reinterpret_cast< type_name >(expression)
- 用于危险类型的转换
struct Data {
short a;
short b;
};
int main() {
long nNum = 16909320; // 0000 0001 0000 0010 0000 0100 0000 1000
Data *data = reinterpret_cast<Data*>(&nNum);
// a == 1032;(0000 0100 0000 1000)
// b == 258; (0000 0001 0000 0010)
cout << data->a << " " << data->b << endl;
}
reinterpret_cast 注意:
-
这种转换适用于依赖实现的底层编程技术,是不可移植,因为不同的系统在存储多字节整型时,可能以不同的顺序存储
-
并不支持所有的类型转换,可将指针类型转换为足以存储指针的整型,但不能将指针转换为更小的整型或者浮点型
-
不能将函数指针转换为数据指针,反之亦然