Implicit conversions
隐式转换
当预期在不同类型的值的上下文中使用表达式时,可能会发生转换
:
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
转换发生在以下情况:
按照分配进行转换
- 在赋值运算符中,右侧操作数的值被转换为左侧操作数的非限定类型。
请注意,除转换外,实际分配也会从浮点类型中移除额外的范围和精度,并禁止重叠; 这些特性不适用于转换,就像通过分配一样。
默认参数优化
在调用时的函数调用表达式中。
1)没有原型的功能
2)可变参数函数,其中参数表达式是与省略号参数匹配的尾随参数之一
整数类型的每个参数都经过整数提升
(参见下文),并且每个类型的参数都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
在这种情况下。
通常的算术转换
以下算术运算符的参数经过隐式转换,以获得公共实数类型,这是执行计算的类型:
- 二进制算术*,/,%,+, -
1)如果一个操作数是long double
,long double
complex
或long double
imaginary
,另一个操作数被隐式转换如下:
- 整数或实际浮动类型
long double
2)否则,如果一个操作数是double
,double
complex
或double
imaginary
,另一个操作数被隐式转换如下:
- 整数或实际浮动类型
double
3)否则,如果一个操作数是float
,float
complex
或float
imaginary
,另一个操作数被隐式转换如下:
- 整数类型为
float
(唯一可能的实际类型是float
,保持原样)
4)否则,两个操作数都是整数。在这种情况下,首先,两个操作数都进行整数升级
(见下文)。然后
- 如果促销后的类型相同,则该类型是常见类型
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 is promoted back to int.
// this is signed + signed case, different rank
// int is converted to long, the result is 98 signed long
2u - 10; // signed / unsigned, same rank
// 10 is converted to unsigned, unsigned math is modulo UINT_MAX+1
// assuming 32 bit ints, result is 4294967288 of type unsigned int (aka UINT_MAX-7)
0UL - 1LL; // signed/unsigned diff rank, rank of signed is greater.
// If sizeof(long) == sizeof(long long), signed cannot represent all unsigned
// this is the last case: both operands are converted to unsigned long long
// 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
与往常一样,浮点运算符的结果可能比其类型所指示的范围和精度更高(请参阅参考资料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∞。
注意:无论通常的算术转换如何,在as-if规则下,通过这些规则,计算总是可以以窄于指定符的类型执行。
价值转化
左值转换
任何非数组类型的任何左值表达式,当在除。以外的任何上下文中使用时。
- 作为操作符的操作数(如果允许)
进行左值转换
:类型保持不变,但会丢失 const / volatile / restrict-qualifiers 和原子属性(如果有的话)。价值保持不变,但失去了左值属性(地址可能不再被采用)。
如果左值具有不完整类型,则行为未定义。
如果左值指定一个自动存储持续时间的对象,其地址从未被采用,并且该对象未初始化(未使用初始化器声明并且在使用之前未对其进行分配),则行为是未定义的。
此转换将模拟对象的值从其位置的内存负载。
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
数组到指针的转换
数组类型的任何左值表达式,当在除。以外的任何上下文中使用时。
- 作为操作符地址的操作数
经历转换到非左值指针到它的第一个元素。
如果数组被声明为注册,则行为是未定义的。
int a[3], b[3][4];
int* p = a; // conversion to &a[0]
int (*q)[4] = b; // conversion to &b[0]
函数指针转换
任何函数指示符表达式,当在除。以外的任何上下文中使用时。
- 作为操作符地址的操作数
经过转换到非左值指针指向由表达式指定的函数。
int f(int
int (*p)(int) = f; // conversion to &f
(***p)(1 // repeated dereference to f and conversion back to &f
隐式转换语义
隐式转换,无论是通过赋值还是通常的算术转换,都由两个阶段组成:
1)价值转化(如果适用)
2)下面列出的转换之一(如果它可以产生目标类型)
兼容的类型
将任何类型的值转换为任何兼容类型始终是不可操作的,并且不会更改表示形式。
uint8_t (*a)[10]; // if uint8_t is a typedef to unsigned char
unsigned char (*b)[] = a; // then these pointer types are compatible
整数升级
整数推广是任意整数类型的值的隐式转换与秩小于或等于秩为int
或类型_Bool,整型,符号int
,unsigned int
类型的位域的,以类型的值int
或unsigned int
。
如果int
可以表示原始类型的整个值范围(或原始位域的值的范围),则将该值转换为类型int
。否则,该值将转换为unsigned int
。
整数升级保持价值,包括符号:
int main(void) {
void f( // old-style function declaration
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
上面的 rank 是每个整数类型的属性,定义如下:
1)所有有符号整数类型的行列是不同的,并且随着它们的精度而增加:被签名的char的等级<短的等级<int的等级<long int的等级<long long 的等级 int
2)所有有符号整数类型的等级等于对应无符号整数类型的等级
3)任何标准整数类型的等级大于任何相同大小的扩展整数类型的等级(即,__int64 <等级长long int,但 long long 等级<由于规则而等于__int128等级(1))
4)char 的等级等于 signed char 的等级和 unsigned char 的等级
5)_Bool 的排名小于任何其他标准整数类型的排名
6)任何枚举类型的等级等于其兼容整数类型的等级
7)排名是传递性的:如果T1的等级<T2的等级和T2的等级<T3的等级,那么T1的等级<T3的等级
8)上面未涉及的扩展整数类型的相对排序的任何方面都是实现定义的
注意:整数升级只适用。
- 作为
通常算术转换的
一部分(见上文)
布尔转换
任何标量类型的值都可以隐式转换为_Bool。将等于零的值转换为0
,将其他所有值转换为1
。
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)
整数转换
任何整数类型的值都可以隐式转换为任何其他整数类型。除了上述促销和布尔转换所涵盖的地方外,规则如下:
- 如果目标类型可以表示源类型的整个值范围,则值不变
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
Real floating-integer conversions
任何实数浮点类型的有限值都可以隐式转换为任何整数类型。除了上述布尔转换覆盖的地方,规则是:
- 小数部分被丢弃(截至零)。
int n = 3.14; // n == 3
int x = 1e10; // undefined behavior for 32-bit int
任何整数类型的值都可以隐式转换为任何实数浮点类型。
- 如果该值可以完全由目标类型表示,则该值不变
这种转换的结果可能比其目标类型指示的范围和精度更高(请参阅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
实际浮点转换
任何实际浮动类型的值都可以隐式转换为任何其他实际浮动类型。
- 如果该值可以完全由目标类型表示,则不会改变
这种转换的结果可能比其目标类型指示的范围和精度更高(请参阅FLT_EVAL_METHOD
。
double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625
float f = d; // f = 0.100000001490116119384765625
float x = 2*(double)FLT_MAX; // undefined
复杂的类型转换
任何复杂类型的值都可以隐式转换为任何其他复杂类型。实部和虚部分别遵循实际浮动类型的转换规则。
double complex d = 0.1 + 0.1*I;
float complex f = d; // f is (0.100000001490116119384765625, 0.100000001490116119384765625)
虚构类型转换
任何虚构类型的值都可以隐式转换为任何其他虚构类型。虚部遵循实际浮动类型的转换规则。
double imaginary d = 0.1*_Imaginary_I;
float imaginary f = d; // f is 0.100000001490116119384765625*I
真正复杂的转换
任何实际浮动类型的值都可以隐式转换为任何复杂类型。
- 结果的实际部分取决于实际浮动类型的转换规则
任何复杂类型的值都可以隐式转换为任何实际浮动类型。
- 实部按照真实浮动类型的规则进行转换
注意:在复数到实数转换中,虚数部分的 NaN 不会传播到实际结果。
实际的虚拟转换
任何虚数类型的值都可以隐式转换为任何实数类型(整型或浮点型)。除目标类型为_Bool 之外,结果始终为正值(或无符号)零,在这种情况下,布尔转换规则适用。
任何实数类型的值都可以隐式转换为任何虚数类型。结果总是一个正的虚构的零。
复数 - 虚数转换
任何虚构类型的值都可以隐式转换为任何复杂类型。
- 结果的实际部分是正的零
任何复杂类型的值都可以隐式转换为任何虚构类型。
- 真正的部分被丢弃
double imaginary z = I * (3*I // the complex result -3.0+0i loses real part, gives zero
指针转换
一个指针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*
转换为0的整型指针表达式都可以隐式转换为任何指针类型(既指向对象又指向函数的指针)。结果是它的类型的空指针值,保证比较不等于该类型的任何非空指针值。这个整数或 void *表达式被称为空指针常量
,标准库提供这个常量的一个定义作为宏NULL
。
int* p = 0;
double* q = NULL;
笔记
尽管任何算术运算符中的有符号整数溢出都是未定义行为,但在整数转换中溢出有符号整数类型仅仅是未指定的行为。
另一方面,尽管任何算术运算符(和整数转换)中的无符号整数溢出是一个定义明确的操作,并遵循模算术规则,但浮点到整数转换中的无符号整数溢出是未定义的行为:可以转换为无符号整数的实际浮动类型的值是来自打开间隔(-1; Unnn_MAX + 1)的值。
unsigned int n = -1.0; // undefined behavior
指针和整数之间的转换(除了指向_Bool 的指针和整数常量表达式,值为零到指针)之间,指向对象的指针(除非指向 void 的指针或指向 void 的指针之外)以及指向函数的指针之间的转换函数具有兼容类型)从不隐含,并且需要一个演员操作符。
在指向函数的指针和指向对象的指针(包括 void *)或整数之间没有转换(隐式或显式)。
参考
- C11 standard (ISO/IEC 9899:2011):