Implicit conversions
当表达式在需要不同类型值的上下文中使用时,可能会发生 转换 :
int n = 1L; // 表达式 1L 的类型为 long,但期望的类型是 int n = 2.1; // 表达式 2.1 的类型为 double,但期望的类型是 int char* p = malloc(10); // 表达式 malloc(10) 的类型为 void*,但期望的类型是 char*
在以下情况下会发生转换:
目录 |
按赋值方式转换
- 在 赋值运算符 中,右操作数的值被转换为左操作数的非限定类型。
- 在 标量初始化 中,初始化器表达式的值被转换为被初始化对象的非限定类型。
- 在具有原型的函数的 函数调用表达式 中,每个实参表达式的值被转换为对应形参声明的非限定类型。
- 在 返回语句 中, return 操作数的值被转换为具有函数返回类型的对象。
注意,实际赋值除了转换外,还会移除浮点类型的额外范围和精度,并禁止重叠;这些特性不适用于按赋值方式的转换。
默认参数提升
在 函数调用表达式 中,当调用指向
每个整数类型的实参都会进行 整数提升 ,而每个类型为 float 的实参会隐式转换为 double 类型。
int add_nums(int count, ...); int sum = add_nums(2, 'c', true); // add_nums 被调用时传入三个 int 类型参数:(2, 99, 1)
|
注意在此上下文中, float complex 和 float imaginary 不会被提升为 double complex 和 double imaginary 。 |
(since C99) |
常规算术转换
以下算术运算符的参数会经历隐式转换以获得 共通实数类型 ,这是执行计算时使用的类型:
|
1)
若一个操作数具有十进制浮点类型,则另一操作数不得具有标准浮点、
复数或虚数类型。
|
(since C23) |
-
- 整数或实浮点类型转换为 long double
| (C99 起) |
-
- 整数或实浮点类型转换为 double
| (C99 起) |
-
- 整数类型转换为 float (唯一可能的实数类型是 float,保持原样)
| (C99 起) |
1.f + 20000001; // int 被转换为 float,得到 20000000.00 // 加法运算后舍入为 float 类型,结果为 20000000.00 (char)'a' + 1L; // 首先,字符 '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 |
(since 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∞。 |
(since C99) |
注意:无论通常的算术转换如何,根据 as-if规则 ,计算始终可以在比这些规则所指定的类型更窄的类型中执行。
值转换
左值转换
任何非数组类型的 左值表达式 ,当在除以下情况外的任何语境中使用时:
- 作为 取址运算符 的操作数(如果允许),
- 作为前置/后置 自增和自减运算符 的操作数,
- 作为 成员访问 (点号)运算符的左操作数,
- 作为 赋值和复合赋值 运算符的左操作数,
-
作为
sizeof的操作数,
进行
左值转换
:类型保持不变,但会丢失
const
/
volatile
/
restrict
限定符及
atomic
属性(若存在)。值保持不变,但失去其左值属性(可能无法再获取其地址)。
如果左值具有不完整类型,则行为未定义。
如果左值指代一个自动存储期对象,且该对象的地址从未被获取,同时该对象未被初始化(声明时未提供初始化器且在首次使用前未进行赋值操作),则行为是未定义的。
此转换模拟从对象所在位置加载其值的内存操作。
volatile int n = 1; int x = n; // 对n进行左值转换会读取n的值 volatile int* p = &n; // 无左值转换:不会读取n的值
数组到指针转换
任何 左值表达式 (C99前) 表达式 (C99起) 具有 数组类型 ,当在除以下情况外的任何上下文中使用时
- 作为 取址运算符 的操作数,
-
作为
sizeof的操作数, -
作为
typeof和typeof_unqual的操作数 (C23起) , - 作为用于 数组初始化 的字符串字面量,
undergoes a conversion to the non-lvalue pointer to its first element.
如果数组被声明为
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 起合法 */ int n = f().a[0]; /* C99 前错误;C99 起合法 */ f().a[0] = 13; /* C99 前错误;C99 起为未定义行为 */ (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 类型。
|
来自位精确整数类型的位字段值会被转换为相应的位精确整数类型。否则,位精确整数类型不受整型提升规则约束。 |
(since C23) |
整数提升保留值,包括符号:
int main(void) { void f(); // 旧式函数声明 // 自 C23 起,void f(...) 在类型提升方面具有相同行为 char x = 'a'; // 从 int 到 char 的整型转换 f(x); // 从 char 到 int 的整型提升 } void f(x) int x; {} // 该函数期望接收 int 类型参数
rank 是每个 整数类型 的属性,其定义如下:
|
8)
位精确有符号整数类型的等级应大于任何宽度较小的标准整数类型或任何宽度较小的位精确整数类型的等级。
9)
任何位精确整数类型相对于同宽度的扩展整数类型的等级由实现定义。
|
(since 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起) |
整数转换
任何整数类型的值都可以隐式转换为其他整数类型。除上述提升和布尔转换涵盖的情况外,规则如下:
- 若目标类型能够表示该值,则值保持不变,
-
否则,若目标类型为无符号类型,则通过重复加减
2
b
(其中 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位整型是未定义行为
任何整数类型的值都可以隐式转换为任何实数浮点类型。
- 若该值能被目标类型精确表示,则保持不变。
- 若该值能被表示但无法精确表示,结果将按实现定义选择最接近的较高值或较低值(若支持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; // 未定义行为
复数类型转换任何复数类型的值均可隐式转换为其他复数类型。实部和虚部分别遵循实浮点类型的转换规则。 虚数类型转换任何虚数类型的值均可隐式转换为其他虚数类型。虚部遵循实浮点类型的转换规则。 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起) 的转换 (C99起) 以及从值为零的整型常量表达式到指针的转换)、对象指针之间的转换(除非其中一方为指向void的指针)以及函数指针之间的转换(除非函数具有兼容类型)永远不会隐式发生,需要使用 强制转换运算符 。
函数指针与对象指针(包括 void * )或整数之间不存在(隐式或显式)转换。
参考文献
- C23 标准 (ISO/IEC 9899:2024):
-
- 6.3 转换 (页码: 44-50)
- 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++ 文档
关于
隐式转换
|