隐式转换

来自cppreference.com
< c‎ | language

当表达式用在期待相异类型的语境中时,可以发生转换

int n = 1L; // 表达式 1L 拥有类型 long ,期待 int
n = 2.1; // 表达式 2.1 拥有类型 double ,期待 int
char *p = malloc(10); // 表达式 malloc(10) 拥有类型 void* ,期待 char*

转换在下列情况下发生:

如同赋值的转换

  • 赋值运算符中,右操作数的值被转换成左操作数的无限定类型
  • 标量初始化中,初始化器表达式的值被转换成待初始化对象的无限定类型
  • 在对有原型的函数调用表达式中,每个参数表达式的类型被转换成对应参数的声明类型
  • return 语句中, return 操作数的值被转换成拥有函数返回类型的对象

注意在实际赋值中,在转换外,还会移除浮点类型的额外范围和精度,并禁止重叠;这些特性不作用于如同赋值的转换。

默认实参提升

函数调用表达式中,当调用下列函数时

2) 变参数函数,其中参数表达式是匹配省略号参数的尾随参数之一

每个整数类型的参数都会经历整数提升(见后述),而每个 float 类型参数都隐式转换为 double 类型

int add_nums(int count, ...);
int sum = add_nums(2, 'c', true); // add_nums 将以三个 int 调用: (2, 99, 1)

注意 float complexfloat imaginary 在此语境中不会提升到 double complexdouble imaginary

通常算术转换

下列算术运算符的参数会经历隐式转换,为了含有公共实数类型,这是执行计算所用的类型:

1) 若一个操作数是 long doublelong double complexlong double imaginary ,则会按下列方式隐式转换另一操作数:
  • 整数或实浮点类型转换成 long double
  • 复数类型转换成 long double complex
  • 虚数类型转换成 long double imaginary
2) 否则,若一个操作数是 doubledouble complexdouble imaginary ,则会按下列方式隐式转换另一操作数:
  • 整数或实浮点类型转换成 double
  • 复数类型转换成 double complex
  • 虚数类型转换成 double imaginary
3) 否则,若一个操作数是floatfloat complexfloat imaginary ,则会按下列方式隐式转换另一操作数:
  • 整数类型转换成 float (唯一可能的实数类型是 float ,它保持原状)
  • 复数类型保持 float complex
  • 虚数类型保持 float imaginary
4) 否则两个操作数均为整数。此情况下,
首先,两个操作数都会经历整数提升(见后述)。然后
  • 若两类型在提升后相同,则该类型即为公共类型
  • 否则,若两操作数在提升后有相同的符号性(均为有符号或均为无符号),则拥有较低转换等级(见后述)者会隐式转换成拥有较高转换等级的操作数的类型
  • 否则,两者符号性不同:若无符号类型操作数拥有大于或等于有符号类型操作数的转换等级,则有符号类型操作数会隐式转换成无符号类型
  • 否则,两者符号性不同且有符号操作数的等级大于无符号操作数的等级。此情况中,若有符号类型可以表达无符号类型的所有值,则有无符号类型的操作数被隐式转换成有符号操作数的类型。
  • 否则,两个操作数都会经历隐式转换,到有符号类型的无符号类型对应者。
1.f + 20000001; // int 被转换成 float ,给出 20000000.00
                // 相加后舍入到 float ,给出20000000.00
(char)'a' + 1L; // 首先,char 被提升回 int。
                // 这是有符号+有符号的情形,等级不同
                // int 被转换成 long ,结果是 signed long 的 98
2u - 10; // 无符号/有符号,等级相同
         // 10 被转换成无符号,无符号数学运算为模 UINT_MAX+1
         // 对于 32 位 int ,结果是 unsigned int 类型的 4294967288 (即 UINT_MAX-7 )
0UL - 1LL; // 无符号/有符号,相异等级,有符号的等级较大。
           // 若 sizeof(long) == sizeof(long long) ,则有符号数不能表示所有无符号数
           // 这是最后一种情况:两个操作数都被转换成 unsigned long long
           // 结果是 unsigned long long 类型的 18446744073709551615( ULLONG_MAX )

结果类型按下列方式确定:

  • 若两操作数均为复数,则结果类型是复数
  • 若两操作数均为虚数,则结果类型是虚数
  • 若两操作数均为实数,则结果类型为实数
  • 若两浮点操作数拥有不同定义域(复数 VS 实数、复数 VS 虚数,或虚数 VS 实数),则结果类型是复数
double complex z = 1 + 2*I;
double f = 3.0;
z + f; // z 保持原态,f 被转换成 double ,结果是 double complex

浮点运算符的结果可能照常会有大于其类型所指示的范围和及精度(见 FLT_EVAL_METHOD )。

注意:实数和虚数操作数不会隐式转换成复数,因为这么做需要额外计算,会在牵涉到无穷大、NaN和有符号零的具体情况时产生不想要的结果。例如,若实数被转换成复数,2.0×(3.0+i∞)会按照(2.0+i0.0)×(3.0+i∞) ⇒ (2.0×3.0–0.0×∞) + i(2.0×∞+0.0×3.0) ⇒ NaN+i∞求值,而非正确的6.0+i∞。若虚数被转换成复数,则i2.0×(∞+i3.0)会按照(0.0+i2.0) × (∞+i3.0) ⇒ (0.0×∞ – 2.0×3.0) + i(0.0×3.0 + 2.0×∞) ⇒ NaN + i∞求值,而非–6.0 + i∞。

注意:无关乎通常算术转换,可以在如同规则下,始终以窄于这些规则指定的类型进行计算。

值变换

左值转换

任何非数组类型的左值表达式,在用于异于下列语境时

会经历左值转换:类型保持相同,但失去 const/volatile/restrict 限定符及原子属性,若原先有。值保持相同,但失去其左值属性(不再能取其地址)。

若左值拥有不完整类型,则行为未定义。

若左值指代自动存储期的对象,该对象从不被取地址,且若该对象未被初始化(没有用初始化器声明且没有在使用它前赋值),则行为未定义。

此转换模拟从内存中其位置加载对象的值。

volatile int n = 1;
int x = n;            // n 上左值转换读 n 的的值
volatile int* p = &n; // 无左值转换:不读 n 的值

数组到指针转换

任何数组类型左值表达式,在用于异于下列语境时

会经历到指向其首元素的非左值指针的转换。

若数组声明为 register ,则行为未定义。

int a[3], b[3][4];
int* p = a;      // 转换成 &a[0]
int (*q)[4] = b; // 转换成 &b[0]

函数到指针转换

任何函数指代器表达式,在用于异于下列语境时

会经历到指向表达式所指代函数的指针的转换。

int f(int);
int (*p)(int) = f; // 转换成 &f
(***p)(1); // 重复解引用到 f 和转换回 &f

隐式转换语义

隐式转换,要么是如同赋值要么是通常算术转换,由二阶段组成:

1) 值变换(若可应用)
2) 下列转换之一(若它能产生目标类型)

兼容类型

将任何类型的值转换成任何兼容类型始终是无操作,且不改变表示。

uint8_t (*a)[10];         // 若 uint8_t 是对 unsigned char 的 typedef
unsigned char (*b)[] = a; // 则这些指针类型是兼容的

整数提升

整数提升是任何等级小于或等于 int 等级的整数类型,或是 _Bool 、 signed int 、 unsigned int 类型的位域类型的值到 intunsigned int 类型值的隐式转换。

int 能表示原类型的整个值域(或原位域的值域),则值转换成 int 类型。否则值转化成 unsigned int 类型。

整数提升保持值,包含符号:

int main(void) {
   void f(); // 旧式函数声明
   char x = 'a'; // 整数转换 int 到 char
   f(x); // 整数提升 char 回 int
}
void f(x) int x; {} // 函数期待int

上述的等级是每个整数类型的属性,定义如下:

1) 所有有符号整数的等级都不同,且按其精度递增:signed char 等级 < short 等级 < int 等级 < long 等级 < long long 等级
2) 所有有符号整数等级与对应无符号整数等级相等
3) 任何标准整数类型的等级大于任何相同大小的非标准整数类型等级(即 __int64 等级 < long long 等级,但根据规则(1), long long 等级 < __int128 等级)
4) char 的等级等于 signed char 和 unsigned char 的等级
5) _Bool 的等级小于任何其他标准整数类型的等级
6) 任何枚举类型的等级等于其兼容整数类型的等级
7) 等级排行是传递性的:若 T1 等级 < T2 等级 且 T2 等级 < T3 等级 ,则 T1 等级 < T3 等级
8) 任何上面未提及的扩展整数类型排行方面是实现定义的

注意:整数提升仅应用于

  • 通常算术转换的一部分(见前述)
  • 默认参数提升的一部分(见前述)
  • 给一元算术运算符 + 和 - 的操作数
  • 给一元位运算符的操作数
  • 给位移运算符 << 和 >> 的两个操作数

布尔转换

任何标量类型的值(包括 nullptr_t (C23 起)可以隐式转换成 _Bool 。比较等于零的值转换成 0false),所有其他值转换成 1true)。

bool b1 = 0.5;              // b1 == 1 (0.5 转换成 int 会是零)
bool b2 = 2.0*_Imaginary_I; // b2 == 1 (但转换成 int 会是零)
bool b3 = 0.0 + 3.0*I;      // b3 == 1 (但转换成 int 会是零)
bool b4 = 0.0/0.0;          // b4 == 1 (NaN 与零比较不相等)
bool b5 = nullptr;          // b5 == 0 (C23 起: nullptr 转换为 false)
(C99 起)

整数转换

任何整数类型的值可以隐式转换到任何其他整数类型。除了上述整数提升和布尔转换所提及的情况,规则为:

  • 若目标类型能表示值,则值不变
  • 否则,若目标类型为无符号,则源值会重复减或加值 2b
    ,其中 b 是目标类型的位数,直到结果符合目标类型。换言之,无符号整数实现模算术。
  • 否则,若目标类型为有符号,则行为是实现定义的(可能包括引发信号)
char x = 'a'; // int -> char,结果不变
unsigned char n = -123456; // 目标是无符号数,结果为 192 (即 -123456+483*256 )
signed char m = 123456;    // 目标是有符号数,结果实现定义
assert(sizeof(int) > -1);  // 断言失败:
                           // 运算符 > 要求 -1 到 size_t 的转换,
                           // 目标为无符号,结果是 SIZE_MAX

实浮点整数转换

任何实浮点类型的有限值可以隐式转换到任何整数类型。除了上述布尔转换所提及的情况,规则为:

  • 忽略小数部分(向零取整)。
  • 若结果值可表示成目标类型,则使用该值
  • 否则,行为未定义
int n = 3.14; // n == 3
int x = 1e10; // 对于 32 位 int 是未定义行为

任何整数类型的值可以隐式转换成任何实浮点类型。

  • 若值能被目标类型准确表示,则它不变
  • 若值能被表示,但无法准确表示,则结果是最接近的较高或较低值(换言之,舍入方向是实现定义的),尽管若支持IEEE算术,则向最近舍入。此情况下是否引发 FE_INEXACT 是未指定的。
  • 若值不能被表示,则行为未定义,尽管若支持 IEEE 算术,则引发 FE_INVALID 且值为未指定。

此转换的结果可能拥有大于其目标类型所指示的值和精度(见 FLT_EVAL_METHOD )。

若在浮点到整数转换中控制 FE_INEXACT ,则可以使用 rintnearbyint

double d = 10; // d = 10.00
float f = 20000001; // f = 20000000.00 (FE_INEXACT)
float x = 1+(long long)FLT_MAX; // 未定义行为

实浮点数转换

任何实浮点类型的值可以隐式转换到任何其他实浮点类型。

  • 若值能被目标类型准确表示,则它不变
  • 若值能被表示,但无法准确表示,则结果是最接近的较高或较低值(换言之,舍入方向是实现定义的),尽管若支持IEEE算术,则向最近舍入
  • 若值不能被表示,则行为未定义

此转换的结果可能拥有大于其目标类型所指示的值和精度(见 FLT_EVAL_METHOD )。

double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625
float f = d;    // f = 0.100000001490116119384765625
float x = 2*(double)FLT_MAX; // 未定义

复数类型转换

任何复数类型的值可以隐式转换成任何另一种复数类型。实部和虚部各自遵循实浮点类型的转换规则。

double complex d = 0.1 + 0.1*I;
float complex f = d; // f 为 (0.100000001490116119384765625, 0.100000001490116119384765625)

虚数类型转换

任何虚数类型的值可以隐式转换成另一种虚数类型。虚部遵循实浮点类型的转换规则。

double imaginary d = 0.1*_Imaginary_I;
float imaginary f = d; // f 为 0.100000001490116119384765625*I

实复数转换

任何实浮点类型的值可以隐式转换成任何复数类型。

  • 结果的实部根据实浮点类型的转换规则确定
  • 结果的虚部是正零(或非 IEEE 系统上的无符号零)

任何复数类型的值可以隐式转换成任何实浮点类型

  • 实部遵循实浮点类型的规则转换
  • 虚部被忽略

注意:在复到实转换中,虚部的 NaN 不会传播到实结果。

double complex z = 0.5 + 3*I;
float f = z;  // 舍去虚部,设置 f 为 0.5
z = f;        // 设置 z 为 0.5 + 0*I

实虚数转换

任何虚数类型的值可以转换到任何实数类型(整数或浮点数)。结果始终是正(或无符号)零,除非目标类型是 _Bool ,这种情况下会应用布尔转换规则。

任何实数类型的值可以隐式转换成任何虚数类型。结果始终是虚数正零。

double imaginary z = 3*I;
bool b = z;  // 布尔转换:设置 b 为 true 
float f = z; // 实虚转换:设置 f 为 0.0 
z = 3.14;    // 虚实转换:设置 z 为 0*_Imaginary_I

复虚数转换

任何虚数类型的值可以隐式转换到任何复数类型。

  • 结果的实部是正零
  • 结果的虚部遵循对应的实类型转换规则

任何复数类型可以隐式转换到任何虚数类型

  • 实部被忽略
  • 结果的虚部遵循对应的实数类型转换规则
double imaginary z = I * (3*I); // 复结果 -3.0+0i 失去实部
                                // 设置 z 为 0*_Imaginary_I

指针转换

指向 void 可与任何指向对象指针类型间相互隐式转换,并拥有下列语义:

  • 若指向对象的指针被转换成指向void的指针再转换回来,则其值与原指针比较相等。
  • 不提供其他保证
int* p = malloc(10 * sizeof(int)); // malloc 返回 void*

指向无限定类型的指针可以隐式转换成指向该类型有限定版本的指针(换言之,可以添上 constvolatile 、 及 restrict 限定符)。原指针与结果比较相等。

int n;
const int* p = &n; // &n 拥有类型 int*

任何拥有值 0 的整数常量表达式也是一个拥有转换成 void* 类型的零的整数指针表达式,可以隐式转换成任意指针类型(既可以是指向对象指针,又可以是指向函数指针)。结果是该类型的空指针值,保证与任何该类型的非空指针值比较不相等。此整数或 void* 表达式又称空指针常量,而且标准库提供此常量作为宏 NULL 的一种定义。

int* p = 0;
double* q = NULL;

注解

尽管有符号整数在任何算术运算符中的溢出是未定义行为,在整数类型转换中溢出有符号整数仅是未指定行为。

另一方面,尽管任何算术运算符(和整数转换)中无符号整数溢出是良好定义的操作,并遵循模算术规则,在浮点到整数转换中溢出无符号整数是未定义行为:可以转换成无符号整数的实浮点类型值是来自开区间 (-1; Unnn_MAX+1) 的值。

unsigned int n = -1.0; // 未定义行为

指针和整数间(除了从指针到 _Bool 和从拥有零值的整数常量表达式到指针)、指向对象指针间(除了从或到指向 void 的指针)以及指向函数指针间(除非函数拥有兼容类型)的转换始终非隐式,并要求有转型运算符

不存在(隐式或显式的)指向函数指针与指向对象指针(包括 void* )或整数间的转换。

引用

  • C11 标准(ISO/IEC 9899:2011):
  • 6.3 Conversions (第 50-56 页)
  • C99 标准(ISO/IEC 9899:1999):
  • 6.3 Conversions (第 42-48 页)
  • C89/C90 标准(ISO/IEC 9899:1990):
  • 3.2 Conversions

参阅