[cpp primer随笔] 08. 类型转换

0. 类型转换

在C++中,如果两种类型的对象可以通过某种方式进行转换(conversion),则称这两种对象是有关联的。如果程序需要一种类型的变量进行运算,那么此时就可以用与之相关联的其他类型进行替代。
在上述的转换中,一部分无需程序员主动操作,由编译器以自动的方式进行,称之为隐式转换(implcit conversion);而剩下的,则需要程序员以某种方式主动将变量转换为另一种类型,则称之为显式转换,又叫做强制类型转换(cast)。

1. 隐式转换

1.1 算术转换

一种典型的隐式转换是算术转换(arithmetic conversion),其含义是将一种算术类型转换为另一种算术类型。主要发生在两种不同大小(指类型大小)的整型进行算术运算,或者无符号与有符号数进行运算时。

  1. 整型提升

当两个整型进行运算时,较小的类型会被自动转化为较大的类型进行运算,这称之为整型提升(integral promotion)。
这种提升不光存在于整型中,以下类型在作为一个运算符的运算对象时,都会按序转换为较宽的类型。(boolcharshortintlonglong long,其中还包括了一些宽字符类型,例如wchar_tchar16_tchar32_t等)

int a = 5;
short b = 3;
long i = a + b;

在上面的例子里,a + b这一表达式在运算时,b的值自动被提升为inta完成加法运算,又在赋值运算符的作用下,被提升为long作为i的初始值。

  1. 无符号类型转换

如果在进行整型提升后,两者因存在有无符号上的差异仍无法达成类型匹配,则将进行进一步转换。有无符号类型的互相转换有一个核心原则,即转换后的类型一定能完整存储转换前类型的所有可能值。

  • 若无符号类型不小于带符号类型,则带符号转换为无符号的。
  • 若带符号类型大于无符号类型,则根据机器的实际情况具体判断。若无符号所有可能值均可以转入有符号类型,则无转有;反之,则有转无。

注意这里的大小指的是类型大小,而非数值大小。例如long > short。因为有些机器中,unsigned short的所有可能值不一定能在int中全部装下,此时就需要int转化为unsigned short

1.2 其他隐式类型转换

  1. 数组名转换

数组名将被隐式转换为其元素类型的指针类型,除了下列情况:

  • 数组名用作decltype参数
  • 数组名用作&sizeoftypeid等运算符的运算对象
  • 数组名被用于与引用作绑定,例如用auto &去推断数组名类型时
  1. 指针转换
  • 常量整数值0或者字面值nullptr能转换成任意指针类型
  • 任意指向非常量的指针都能转换为void *
  • 指向任意对象的指针能转换成const void*

第三种额外补充说明下,为便于理解可拆开看作(type* -> void* -> const void*),任何非常量指针都可以转换为void*,而非常量指针又都可以转换成常量指针类型,因此任何类型的对象指针都可以转换成const void*

int i = 10;
void *p_i = &i;
const void *cp_i = &i;

需要注意,void*转换成原来的类型无法隐式转换,必须强转,而const void*更麻烦,还需先借助const_cast取消底层const,再强转为原来的类型。强转的内容后面介绍。

  1. 布尔类型转换

指针或算术类型的值如果为0,则转换为false;若不为0,则转换为true

  1. 常量类型转换

指向非常量类型的指针或者引用,可以转换成常量类型的指针特性。反之则不成立,因为底层const特性无法轻易去除。

  1. 类类型转换

只允许一步自动的类类型转换,多步需要显式转换处理。涉及到类型转换运算符operator type()的重载,在之后的内容中会讲。

2. 显式转换(强制类型转换)

强制类型转换的语法为cast-name<type>(expression),因为需要cast_name标明执行转换的方式,因此也称为命名的强制类型转换。

2.1 static_cast

任何具有明确定义,且不包含底层const属性变化的类型转换,均可通过static_cast完成。
不包含底层const好理解,那么什么是具有明确定义呢?主要有以下几种情况:

  • 任何隐式转换可以用static_cast覆写
  • 算术类型转换,可以将低精度转换为高精度类型
int a = 5, b = 10;
double c = static_cast<double> (a) / b; // 转换为浮点型运算
  • 可以将void*转换为任何指针类型
int i = 10;
void *p_i = &i;
int *pp_i = static_cast<int*> (p_i); // 从void*恢复为原先类型
  • 可以自动调用自定义类型的类型转换运算符

2.2 const_cast

能且只能修改对象的底层const属性。例如:将const int转换为int,或将double转化为const double
需要注意的是,当底层const对象被去掉const性质(cast away the const)后,再对其进行写操作属于未定义行为。
另外,在之后的内容中可以看到,const_cast在存在函数重载的上下文中经常使用。

int i = 10;
const int* cp_i = &i;
int *p_i = const_cast<int*> (cp_i); // 正确:成功取消了cp_i的底层const特性
int *p_i = const_cast<void*> (cp_i); // clang 16.0.0 error: const_cast from 'const int *' to 'void *' is not allowed
// visual studio:  const_cast 只能调节类型限定符;不能更改基础类型	

2.3 dynamic_cast

与其他几种不同的是,dynamic_cast支持程序运行时的类型转换。但由于涉及到运行时类型识别(run-time type identification, RTTI),属于高级特性,留到后面讲。

2.4 reinterpret_cast

“为运算对象在位模式上提供重新解释”,从底层数据层面上进行类型转换。
简单来说,这是最强的一种强制类型转换,对于一个变量,你说是啥就是啥,只要被转换的对象不是自定义类型,那么编译器就不多管,否则会报not allowed的错误。
这种转换下,如果出了问题,例如转换类型与实际类型不匹配,将由程序员自己承担后果。因此,这种转换完全不推荐使用。

int i = 10, *ip = &i;
char *pc = reinterpret_cast<char *>(ip); // 随意转换,错了不管,且无警告
string str = reinterpret_cast<string> (i);// error: reinterpret_cast from 'int' to 'string' (aka 'basic_string<char>') is not allowed
// 自定义类型不允许reinterpret_cast强转

2.5 旧式强制类型转换

除了命名的强制类型转换,还有一种老的强转写法,语法是type (expr)或者(type) expr
这种转换在允许的情况下执行类似于static_cast或者const_cast的行为,当不前两者不满足时,则直接按照reinterpret_cast的方式进行超级强转。这种旧式的写法,其语义和结果是不明确的,因此不推荐使用

int i = 10;
double d = (double) i; // static_cast行为
int *p_i = &i;
const int* p_i = (const int*) p_i; // const_cast行为
char *a = (char*) i; // reinterpret_cast行为,运行时会带来严重后果
string str = (string) i; // no matching conversion for C-style cast from 'int' to 'string' (aka 'basic_string<char>')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值