隐式转换
当在期望不同类型值的上下文中使用表达式时,可能发生转换
int n = 1L; // expression 1L has type long, int is expected n = 2.1; // expression 2.1 has type double, int is expected char *p = malloc(10); // expression malloc(10) has type void*, char* is expected
转换发生在以下情况
内容 |
[[编辑]] 如同赋值的转换
- 在赋值运算符中,右手操作数的值被转换为左手操作数的非限定类型。
- 在标量初始化中,初始化器表达式的值被转换为正在初始化的对象的非限定类型
- 在函数调用表达式中,对于具有原型的函数,每个实参表达式的值都被转换为相应形参的非限定声明类型
- 在return 语句中,
return
的操作数的值被转换为具有函数返回类型的对象
请注意,实际赋值除了转换之外,还会从浮点类型中移除额外的范围和精度,并禁止重叠;这些特性不适用于如同赋值的转换。
[[编辑]] 默认实参提升
在函数调用表达式中,当调用
每个整型类型的实参都经历整型提升(见下文),并且每个 float 类型的实参都隐式转换为 double 类型
int add_nums(int count, ...); int sum = add_nums(2, 'c', true); // add_nums is called with three ints: (2, 99, 1)
请注意,float complex 和 float imaginary 在此上下文中不会提升为 double complex 和 double imaginary。 |
(C99 起) |
[[编辑]] 常用算术转换
以下算术运算符的实参为了获得常用实数类型而经历隐式转换,常用实数类型是执行计算的类型
1) 如果一个操作数具有十进制浮点类型,则另一个操作数不得具有标准浮点型、
复数或虚数类型。
|
(C23 起) |
- 整型或实浮点型转换为 long double
(C99 起) |
- 整型或实浮点型转换为 double
(C99 起) |
- 整型转换为 float(唯一可能的实数类型是 float,它保持不变)
(C99 起) |
- 如果类型相同,则该类型是常用类型。
- 否则,类型不同
- 如果类型具有相同的有符号性(均为有符号或均为无符号),则类型具有较小转换等级1的操作数隐式转换2为另一种类型。
- 否则,操作数具有不同的有符号性
- 如果无符号类型的转换等级大于或等于有符号类型的等级,则具有有符号类型的操作数隐式转换为无符号类型。
- 否则,无符号类型的转换等级小于有符号类型
- 如果带符号类型可以表示无符号类型的所有值,则具有无符号类型的操作数隐式转换为带符号类型。
- 否则,两个操作数都隐式转换为带符号操作数类型的无符号类型对应类型。
- 1. 有关等级的规则,请参阅下面的“整型提升”。
- 2. 有关“隐式转换语义”,请参阅下面的“整型转换”。
1.f + 20000001; // int is converted to float, giving 20000000.00 // addition and then rounding to float gives 20000000.00 (char)'a' + 1L; // first, char 'a', which is 97, is promoted to int // different types: int and long // same signedness: both signed // different rank: long is of greater rank than int // therefore, int 97 is converted to long 97 // the result is 97 + 1 = 98 of type signed long 2u - 10; // different types: unsigned int and signed int // different signedness // same rank // therefore, signed int 10 is converted to unsigned int 10 // since the arithmetic operation is performed for unsigned integers // (see "Arithmetic operators" topic), the calculation performed is (2 - 10) // modulo (2 raised to n), where n is the number of value bits of unsigned int // if unsigned int is 32-bit long and there is no padding bits in its object // representation, then the result is (-8) modulo (2 raised to 32) = 4294967288 // of type unsigned int 5UL - 2ULL; // different types: unsigned long and unsigned long long // same signedness // different rank: rank of unsigned long long is greater // therefore, unsigned long 5 is converted to unsigned long long 5 // since the arithmetic operation is performed for unsigned integers // (see "Arithmetic operators" topic), // if unsigned long long is 64-bit long, then // the result is (5 - 2) modulo (2 raised to 64) = 3 of type // unsigned long long 0UL - 1LL; // different types: unsigned long and signed long long // different signedness // different rank: rank of signed long long is greater. // if ULONG_MAX > LLONG_MAX, then signed long long cannot represent all // unsigned long therefore, this is the last case: both operands are converted // to unsigned long long unsigned long 0 is converted to unsigned long long 0 // long long 1 is converted to unsigned long long 1 since the arithmetic // operation is performed for unsigned integers // (see "Arithmetic operators" topic), // if unsigned long long is 64-bit long, then // the calculation is (0 - 1) modulo (2 raised to 64) // thus, the result is 18446744073709551615 (ULLONG_MAX) of type // unsigned long long
结果类型确定如下
double complex z = 1 + 2*I; double f = 3.0; z + f; // z remains as-is, f is converted to double, the result is 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; // lvalue conversion on n reads the value of n volatile int* p = &n; // no lvalue conversion: does not read the value of n
[[编辑]] 数组到指针转换
任何左值表达式的数组类型,当用于以下上下文之外的任何上下文时:
- 作为取地址运算符的操作数
- 作为 sizeof 的操作数
- 作为 typeof 和 typeof_unqual 的操作数 (C23 起)
- 作为用于数组初始化的字符串字面量
经历到指向其首元素的非左值指针的转换。
如果数组被声明为register,则行为未定义。
int a[3], b[3][4]; int* p = a; // conversion to &a[0] int (*q)[4] = b; // conversion to &b[0]
[[编辑]] 函数到指针转换
任何函数指示符表达式,当用于以下上下文之外的任何上下文时:
- 作为取地址运算符的操作数
- 作为 sizeof 的操作数
- 作为 typeof 和 typeof_unqual 的操作数 (C23 起)
经历到指向表达式指定的函数的非左值指针的转换。
int f(int); int (*p)(int) = f; // conversion to &f (***p)(1); // repeated dereference to f and conversion back to &f
[[编辑]] 隐式转换语义
隐式转换,无论是如同赋值还是常用算术转换,都包含两个阶段
[[编辑]] 兼容类型
将任何类型的值转换为任何兼容类型始终是空操作,并且不会更改表示形式。
uint8_t (*a)[10]; // if uint8_t is a typedef to unsigned char unsigned char (*b)[] = a; // then these pointer types are compatible
[[编辑]] 整型提升
整型提升是将任何整数类型的值隐式转换为类型 int 或 unsigned int,其中整数类型的等级小于或等于 int 的等级,或者类型为 _Bool(C23 前)bool(C23 起)、int、signed int、unsigned int 的位域。
如果 int 可以表示原始类型(或原始位域的值范围)的整个值范围,则该值转换为 int 类型。否则,该值转换为 unsigned int。
来自位精确整数类型的位域的值将转换为相应的位精确整数类型。否则,位精确整数类型不受整数提升规则的约束。 |
(C23 起) |
整型提升保留值,包括符号
int main(void) { void f(); // old-style function declaration // since C23, void f(...) has the same behavior wrt promotions char x = 'a'; // integer conversion from int to char f(x); // integer promotion from char back to int } void f(x) int x; {} // the function expects int
上面的等级是每个整数类型的属性,定义如下
8) 位精确有符号整数类型的等级应大于宽度较小的任何标准整数类型或宽度较小的任何位精确整数类型的等级。
9) 位精确整数类型相对于相同宽度的扩展整数类型的相对等级是实现定义的。
|
(C23 起) |
注意:整型提升仅适用于
- 作为常用算术转换的一部分(见上文)
- 作为默认实参提升的一部分(见上文)
- 对于一元算术运算符 + 和 - 的操作数
- 对于一元位运算符 ~ 的操作数
- 对于移位运算符 << 和 >> 的两个操作数
布尔转换任何标量类型的值都可以隐式转换为 _Bool(C23 前)bool(C23 起)。 与值为零的整数常量表达式相等的值(C23 前)是零(对于算术类型)、空(对于指针类型)或具有 nullptr_t 类型的值(C23 起) 转换为 0(C23 前)false(C23 起),所有其他值都转换为 1(C23 前)true(C23 起)。 bool b1 = 0.5; // b1 == 1 (0.5 converted to int would be zero) bool b2 = 2.0*_Imaginary_I; // b2 == 1 (but converted to int would be zero) bool b3 = 0.0 + 3.0*I; // b3 == 1 (but converted to int would be zero) bool b4 = 0.0/0.0; // b4 == 1 (NaN does not compare equal to zero) bool b5 = nullptr; // b5 == 0 (since C23: nullptr is converted to false) |
(C99 起) |
[[编辑]] 整型转换
任何整数类型的值都可以隐式转换为任何其他整数类型。除了上面涵盖的提升和布尔转换之外,规则是
- 如果目标类型可以表示该值,则该值不变
- 否则,如果目标类型是无符号类型,则值 2b
(其中 b 是目标类型中的值位数)会反复从源值中减去或添加到源值中,直到结果适合目标类型。换句话说,无符号整数实现模运算。 - 否则,如果目标类型是有符号类型,则行为是实现定义的(可能包括引发信号)
char x = 'a'; // int -> char, result unchanged unsigned char n = -123456; // target is unsigned, result is 192 (that is, -123456+483*256) signed char m = 123456; // target is signed, result is implementation-defined assert(sizeof(int) > -1); // assert fails: // operator > requests conversion of -1 to size_t, // target is unsigned, result is SIZE_MAX
[[编辑]] 实浮点-整型转换
任何实浮点类型的有限值都可以隐式转换为任何整数类型。除了上面涵盖的布尔转换之外,规则是
- 小数部分被丢弃(向零截断)。
- 如果结果值可以由目标类型表示,则使用该值
- 否则,行为未定义
int n = 3.14; // n == 3 int x = 1e10; // undefined behavior for 32-bit 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; // undefined behavior
[[编辑]] 实浮点-浮点转换
任何实浮点类型的值都可以隐式转换为任何其他实浮点类型。
- 如果该值可以由目标类型精确表示,则它保持不变
- 如果该值可以表示,但不能精确表示,则结果是最近的较高值或最近的较低值(换句话说,舍入方向是实现定义的),但如果支持 IEEE 算术,则舍入到最近的值
- 如果该值无法表示,则行为未定义
本节不完整
原因:检查 IEEE 是否需要适当符号的无穷大
此转换的结果可能具有比其目标类型指示的更大的范围和精度(请参阅 FLT_EVAL_METHOD)。
double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625 float f = d; // f = 0.100000001490116119384765625 float x = 2*(double)FLT_MAX; // undefined
复数类型转换任何复数类型的值都可以隐式转换为任何其他复数类型。实部和虚部分别遵循实浮点类型的转换规则。 虚数类型转换任何虚数类型的值都可以隐式转换为任何其他虚数类型。虚部遵循实浮点类型的转换规则。 double imaginary d = 0.1*_Imaginary_I; float imaginary f = d; // f is 0.100000001490116119384765625*I 实数-复数转换任何实浮点类型的值都可以隐式转换为任何复数类型。
任何复数类型的值都可以隐式转换为任何实浮点类型
注意:在复数到实数的转换中,虚部中的 NaN 不会传播到实数结果。 double complex z = 0.5 + 3*I; float f = z; // the imaginary part is discarded, f is set to 0.5 z = f; // sets z to 0.5 + 0*I 实数-虚数转换任何虚数类型的值都可以隐式转换为任何实数类型(整数或浮点型)。结果始终为正(或无符号)零,除非目标类型为 _Bool(C23 前)bool(C23 起),在这种情况下,适用布尔转换规则。 任何实数类型的值都可以隐式转换为任何虚数类型。结果始终为正虚数零。 double imaginary z = 3*I; bool b = z; // Boolean conversion: sets b to true float f = z; // Real-imaginary conversion: sets f to 0.0 z = 3.14; // Imaginary-real conversion: sets z to 0*_Imaginary_I 复数-虚数转换任何虚数类型的值都可以隐式转换为任何复数类型。
任何复数类型的值都可以隐式转换为任何虚数类型
double imaginary z = I * (3*I); // the complex result -3.0+0i loses real part // sets z to 0*_Imaginary_I |
(C99 起) |
[[编辑]] 指针转换
指向 void 的指针可以隐式地与任何指向对象类型的指针相互转换,并具有以下语义
- 如果将指向对象的指针转换为指向 void 的指针然后再转换回来,则其值与原始指针相等。
- 不提供其他保证
int* p = malloc(10 * sizeof(int)); // malloc returns void*
指向非限定类型的指针可以隐式转换为指向该类型的限定版本的指针(换句话说,可以添加 const、volatile 和 restrict 限定符)。原始指针和结果相等。
int n; const int* p = &n; // &n has type int*
任何值为 0 的整型常量表达式,以及值为零的整型指针表达式,都可以隐式转换为任何指针类型(对象指针和函数指针)。结果是其类型的空指针值,保证与该类型的任何非空指针值都不相等。这种整型或 void* 表达式被称为空指针常量,标准库提供此常量的一个定义,即宏 NULL。
int* p = 0; double* q = NULL;
[编辑] 注解
尽管任何算术运算符中的有符号整数溢出是未定义行为,但在整数转换中,有符号整数类型的溢出仅仅是未指明的行为。
另一方面,尽管任何算术运算符(以及整数转换)中的无符号整数溢出是良好定义的运算,并遵循模算术规则,但在浮点到整数的转换中,无符号整数的溢出是未定义行为:可以转换为无符号整数的实浮点类型的值是开区间 (-1; Unnn_MAX+1) 中的值。
unsigned int n = -1.0; // undefined behavior
指针和整数之间的转换(除了 从指针到 _Bool(直到 C23)bool(自 C23 起) 和 (自 C99 起)从值为零的整型常量表达式到指针的转换),对象指针之间的转换(除非其中一个是指向 void 的指针),以及函数指针之间的转换(除非函数具有兼容的类型)永远不是隐式的,并且需要强制类型转换运算符。
函数指针和对象指针(包括 void*)或整数之间没有转换(隐式或显式)。
[编辑] 参考
- C23 标准 (ISO/IEC 9899:2024)
- 6.3 转换 (页码:待定)
- C17 标准 (ISO/IEC 9899:2018)
- 6.3 转换 (页码:37-41)
- C11 标准 (ISO/IEC 9899:2011)
- 6.3 转换 (页码:50-56)
- C99 标准 (ISO/IEC 9899:1999)
- 6.3 转换 (页码:42-48)
- C89/C90 标准 (ISO/IEC 9899:1990)
- 3.2 转换
[编辑] 参见
C++ 文档 关于 隐式转换
|