隐式转换
当表达式用在期待相异类型的值语境中时,可以发生转换:
int n = 1L; // 表达式 1L 类型为 long,期待 int
n = 2.1; // 表达式 2.1 类型为 double,期待 int
char* p = malloc(10); // 表达式 malloc(10) 类型为 void*,期待 char*
下列情况下将发生转换:
如同赋值的转换
- 在赋值运算符中,右操作数的值被转换成左操作数的无限定类型。
- 在标量初始化中,初始化式表达式的值被转换成待初始化对象的无限定类型。
- 在对有原型函数的函数调用表达式中,每个实参表达式的值被转换成对应形参的声明类型。
- 在 return 语句中,
return的操作数的值被转换成具有该函数返回类型的对象。
注意在实际赋值中,除转换外,还会移除浮点数类型的额外范围和精度,并禁止重叠;这些特性对于如同赋值的转换不适用。
默认实参提升
在函数调用表达式中,当调用下列函数时
每个整数类型的实参都会经历整数提升,而每个 float 类型的实参都隐式转换为 double 类型
int add_nums(int count, ...);
int sum = add_nums(2, 'c', true); // add_nums 将以三个 int 调用:(2, 99, 1)
|
注意 |
(C99 起) |
一般算术转换
下列算术运算符的实参会经历隐式转换,以获得公共实数类型 ,这是执行计算所用的类型:
|
1) 若一个操作数具有十进制浮点数类型,则另一个操作数不能为标准浮点数、
复数或虚数类型。
|
(C23 起) |
long double、long double complex 或 long double imaginary,则会按下列方式隐式转换另一操作数:
- 整数或实浮点数类型转换成
long double - 复数类型转换成
long double complex - 虚数类型转换成
long double imaginary
- 整数或实浮点数类型转换成
double、double complex 或 double imaginary,则会按下列方式隐式转换另一操作数:
- 整数或实浮点数类型转换成
double - 复数类型转换成
double complex - 虚数类型转换成
double imaginary
- 整数或实浮点数类型转换成
float、float complex 或 float imaginary,则会按下列方式隐式转换另一操作数:
- 整数类型转换成
float(唯一可能的实数类型是float,它保持原状) - 复数类型保持
float complex - 虚数类型保持
float imaginary
- 整数类型转换成
1.f + 20000001; // int 被转换成 float,给出 20000000.00
// 相加后舍入到 float,给出 20000000.00
(char)'a' + 1L; // 首先,char 'a',其值为 97,被提升为 int
// 类型不同:int 和 long
// 符号性相同:均有符号
// 等级不同:long 的等级大于 int
// 因而,int 97 转换为 long 97
// 结果为 97 + 1 = 98,类型为 signed long
2u - 10; // 类型不同:unsigned int 和 signed int
// 符号性不同
// 等级相同
// 因而,signed int 10 被转换为 unsigned int 10
// 这是由于要对无符号整数实施算术运算
// (参见“算术运算符”话题),所实施的计算为 (2 - 10)
// 模 (2 的 n 次方),其中 n 为 unsigned int 的值位数
// 若 unsigned int 为 32 位宽,且其对象表示中
// 没有填充位,则其结果为 (-8) 模 (2 的 32 次方) = 4294967288
// 类型为 unsigned int
5UL - 2ULL; // 类型不同:unsigned long 和 unsigned long long
// 符号性相同
// 等级不同:unsigned long long 的等级更大
// 因而,unsigned long 5 被转换为 unsigned long long 5
// 这是由于要对无符号整数实施算术运算
// (参见“算术运算符”话题),
// 若 unsigned long long 为 64 位宽,则
// 其结果为 (5 - 2) 模 (2 的 64 次方) = 3
// 类型为 unsigned long long
0UL - 1LL; // 类型不同:unsigned long 和 signed long long
// 符号性不同
// 等级不同:signed long long 的等级更大。
// 若 ULONG_MAX > LLONG_MAX,则 signed long long 不能表示
// unsigned long 的所有值,因而,这属于最后一种情况:两操作数均被
// 转换为 unsigned long long,unsigned long 0 被转换为 unsigned long long 0
// long long 1 被转换为 unsigned long long 1,这是由于
// 要对无符号整数实施算术运算
// (参见“算术运算符”话题),
// 若 unsigned long long 为 64 位宽,则
// 计算为 (0 - 1) 模 (2 的 64 次方)
// 因此,其结果为 18446744073709551615 (ULLONG_MAX),
// 类型为 unsigned long long
|
结果类型按下列方式确定:
double complex z = 1 + 2*I;
double f = 3.0;
z + f; // z 保持原态,f 被转换成 double ,结果是 double complex
|
(C99 起) |
浮点数运算符的结果可能照常会有大于其类型所指示的范围和及精度(见 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∞。 |
(C99 起) |
注意:无关乎一般算术转换,可以在如同规则下,始终以窄于这些规则指定的类型进行计算。
值变换
左值转换
任何非数组类型的左值表达式,在用于异于下列语境时
会经历左值转换 :类型保持相同,但失去 const/volatile/restrict 限定符及原子属性,若原先有。值保持相同,但失去其左值属性(不再能取其地址)。
若左值拥有不完整类型,则行为未定义。
若左值指代自动存储期的对象,该对象从不被取地址,且若该对象未被初始化(没有用初始化器声明且没有在使用它前赋值),则行为未定义。
此转换模拟从内存中其位置加载对象的值。
volatile int n = 1;
int x = n; // n 上左值转换读 n 的的值
volatile int* p = &n; // 无左值转换:不读 n 的值
数组到指针转换
任何数组类型的左值表达式(C99 前)表达式(C99 起),在用于下列语境之外时
- 作为取址运算符的操作数,
- 作为
sizeof的操作数, - 作为
typeof和typeof_unqual的操作数 (C23 起), - 作为用于数组初始化的字符串字面量,
会经历到指向其首元素的非左值指针的转换。
若数组声明为 register,则行为未定义。
非左值数组或其任何元素不可访问(C99 前)拥有临时生存期(C99 起)。
int a[3], b[3][4];
int* p = a; /* 转换成 &a[0] */
int (*q)[4] = b; /* 转换成 &b[0] */
struct S
{
int a[1];
};
struct S f(void)
{
struct S result = {{0}}; /* C99 起支持 {0} */
return result;
}
void g(void)
{
int* p = f().a; /* C99 前错误;C99 起 OK */
int n = f().a[0]; /* C99 前错误;C99 起 OK */
f().a[0] = 13 /* C99 前错误;C99 起 UB */
(void)p, (void)n;
}
int main(void) { return 0; }
函数到指针转换
任何函数指代器表达式,在用于异于下列语境时
- 作为取址运算符的操作数,
- 作为
sizeof的操作数, - 作为
typeof和typeof_unqual的操作数 (C23 起),
会经历到指向表达式所指代函数的指针的转换。
int f(int);
int (*p)(int) = f; // 转换成 &f
(***p)(1); // 重复解引用到 f 和转换回 &f
隐式转换语义
隐式转换,要么是如同赋值 要么是一般算术转换 ,由二阶段组成:
兼容类型
将任何类型的值转换成任何兼容类型始终是无操作,且不改变表示。
uint8_t (*a)[10]; // 若 uint8_t 是对 unsigned char 的 typedef
unsigned char (*b)[] = a; // 则这些指针类型是兼容的
整数提升
整数提升是任何等级小于或等于 int 等级的整数类型,或是 _Bool(C23 前)bool(C23 起)、int、signed int、unsigned int 类型的位域类型的值到 int 或 unsigned int 类型值的隐式转换。
若 int 能表示原类型的整个值域(或原位域的值域),则值转换成 int 类型。否则值转化成 unsigned int 类型。
|
位精确整数类型的位域的值,转换为对应的位精确整数类型。其他情况下,位精确整数类型免于遵守整数提升规则。 |
(C23 起) |
整数提升保持值,包含符号:
int main(void)
{
void f(); // 旧式函数声明
// 自 C23 起,void f(...) 在提升方面具有相同行为
char x = 'a'; // 整数转换 int 到 char
f(x); // 整数提升 char 回 int
}
void f(x) int x; {} // 函数期待 int
上述的等级是每个整数类型的属性,定义如下:
signed char 等级 < short 等级 < int 等级 < long 等级 < long long 等级__int64 等级 < long long 等级,但根据规则(1),long long 等级 < __int128 等级)char 的等级等于 signed char 和 unsigned char 的等级|
8) 位精确有符号整数类型的等级,应当大于任何更小宽度的标准整数类型或更小宽度的位精确整数类型的等级。
9) 任何位精确整数类型的等级,与相同宽度的扩充整数类型的等级之间的关系由实现定义。
|
(C23 起) |
注意:整数提升仅应用于
- 一般算术转换的一部分(见前述),
- 默认参数提升的一部分(见前述),
- 给一元算术运算符
+和-的操作数, - 给一元位运算符
~的操作数, - 给位移运算符
<<和>>的两个操作数。
布尔转换任何标量类型的值可以隐式转换成 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,则可以使用 rint 及 nearbyint。
double d = 10; // d = 10.00
float f = 20000001; // f = 20000000.00 (FE_INEXACT)
float x = 1 + (long long)FLT_MAX; // 未定义行为
实浮点数转换
任何实浮点数类型的值可以隐式转换到任何其他实浮点数类型。
- 若值能被目标类型准确表示,则它不变。
- 若值能被表示,但无法准确表示,则结果是最接近的较高或较低值(换言之,舍入方向是实现定义的),尽管若支持IEEE算术,则向最近舍入。
- 若值不能被表示,则行为未定义
。本节未完成
原因:检查 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
实复数转换任何实浮点数类型的值可以隐式转换成任何复数类型。
任何复数类型的值可以隐式转换成任何实浮点数类型
注意:在复到实转换中,虚部的 NaN 不会传播到实结果。 double complex z = 0.5 + 3*I;
float f = z; // 舍去虚部,设置 f 为 0.5
z = f; // 设置 z 为 0.5 + 0*I实虚数转换任何虚数类型的值可以转换到任何实数类型(整数或浮点数)。结果始终是正(或无符号)零,除非目标类型是 _Bool(C23 前)bool(C23 起),这种情况下会应用布尔转换规则。 任何实数类型的值可以隐式转换成任何虚数类型。结果始终是虚数正零。 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 |
(C99 起) |
指针转换
指向 void 可与任何指向对象指针类型间相互隐式转换,并拥有下列语义:
- 若指向对象的指针被转换成指向void的指针再转换回来,则其值与原指针比较相等。
- 不提供其他保证。
int* p = malloc(10 * sizeof(int)); // malloc 返回 void*指向无限定类型的指针可以隐式转换成指向该类型有限定版本的指针(换言之,可以添上 const、volatile、及 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(C23 前)bool(C23 起) 和从拥有零值的整数常量表达式到指针)、指向对象指针间(除了从或到指向 void 的指针)以及指向函数指针间(除非函数拥有兼容类型)的转换始终非隐式,并要求有转换运算符。
不存在(隐式或显式的)指向函数指针与指向对象指针(包括 void*)或整数间的转换。
引用
- C23 标准(ISO/IEC 9899:2024):
- 6.3 Conversions (第 44-50 页)
- C17 标准(ISO/IEC 9899:2018):
- 6.3 Conversions (第 37-41 页)
- 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
参阅
隐式转换的 C++ 文档
|