命名空间
变体
操作

隐式转换

来自 cppreference.cn
< c‎ | language

当在期望不同类型值的上下文中使用表达式时,可能发生转换

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的操作数的值被转换为具有函数返回类型的对象

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

[[编辑]] 默认实参提升

函数调用表达式中,当调用

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 complexfloat imaginary 在此上下文中不会提升为 double complexdouble imaginary

(C99 起)

[[编辑]] 常用算术转换

以下算术运算符的实参为了获得常用实数类型而经历隐式转换,常用实数类型是执行计算的类型

1) 如果一个操作数具有十进制浮点类型,则另一个操作数不得具有标准浮点型、

复数或虚数类型。

  • 首先,如果任一操作数的类型为 _Decimal128,则另一个操作数将转换为 _Decimal128。
  • 否则,如果任一操作数的类型为 _Decimal64,则另一个操作数将转换为 _Decimal64。
  • 否则,如果任一操作数的类型为 _Decimal32,则另一个操作数将转换为 _Decimal32。
(C23 起)
2) 否则,如果一个操作数是 long doublelong double complex,或 long double imaginary (C99 起),则另一个操作数隐式转换为如下
  • 整型或实浮点型转换为 long double
  • 复数类型转换为 long double complex
  • 虚数类型转换为 long double imaginary
(C99 起)
3) 否则,如果一个操作数是 doubledouble complex,或 double imaginary (C99 起),则另一个操作数隐式转换为如下
  • 整型或实浮点型转换为 double
(C99 起)
4) 否则,如果一个操作数是 floatfloat complex,或 float imaginary (C99 起),则另一个操作数隐式转换为如下
  • 整型转换为 float(唯一可能的实数类型是 float,它保持不变)
(C99 起)
5) 否则,两个操作数都是整数。两个操作数都经历整型提升(见下文);然后,在整型提升之后,适用以下情况之一
  • 如果类型相同,则该类型是常用类型。
  • 否则,类型不同
    • 如果类型具有相同的有符号性(均为有符号或均为无符号),则类型具有较小转换等级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

[[编辑]] 数组到指针转换

任何左值表达式数组类型,当用于以下上下文之外的任何上下文时:

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

如果数组被声明为register,则行为未定义。

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

[[编辑]] 整型提升

整型提升是将任何整数类型的值隐式转换为类型 intunsigned int,其中整数类型的等级小于或等于 int 的等级,或者类型为 _Bool(C23 前)bool(C23 起)intsigned intunsigned 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

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

1) 所有有符号整数类型的等级都不同,并且随着其精度的提高而增加:signed char 的等级 < short 的等级 < int 的等级 < long int 的等级 < long long int 的等级
2) 所有有符号整数类型的等级等于相应的无符号整数类型的等级
3) 任何标准整数类型的等级都大于相同大小的任何扩展整数类型或位精确整数类型(C23 起)的等级(即 __int64 的等级 < long long int 的等级,但 long long 的等级 < __int128 的等级,因为规则 (1)
4) char 的等级等于 signed char 的等级和 unsigned char 的等级
5) _Bool(C23 前)bool(C23 起) 的等级小于任何其他标准整数类型的等级
6) 任何枚举类型的等级都等于其兼容整数类型的等级
7) 等级是可传递的:如果 T1 的等级 < T2 的等级,并且 T2 的等级 < T3 的等级,则 T1 的等级 < T3 的等级
8) 位精确有符号整数类型的等级应大于宽度较小的任何标准整数类型或宽度较小的任何位精确整数类型的等级。
9) 位精确整数类型相对于相同宽度的扩展整数类型的相对等级是实现定义的。
(C23 起)
10) 上面未涵盖的扩展整数类型的相对等级的任何方面都是实现定义的。

注意:整型提升仅适用于

  • 作为常用算术转换的一部分(见上文)
  • 作为默认实参提升的一部分(见上文)
  • 对于一元算术运算符 +- 的操作数
  • 对于一元位运算符 ~ 的操作数
  • 对于移位运算符 <<>> 的两个操作数

布尔转换

任何标量类型的值都可以隐式转换为 _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,则可以使用 rintnearbyint

double d = 10; // d = 10.00
float f = 20000001; // f = 20000000.00 (FE_INEXACT)
float x = 1+(long long)FLT_MAX; // undefined behavior

[[编辑]] 实浮点-浮点转换

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

  • 如果该值可以由目标类型精确表示,则它保持不变
  • 如果该值可以表示,但不能精确表示,则结果是最近的较高值或最近的较低值(换句话说,舍入方向是实现定义的),但如果支持 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 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

实数-复数转换

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

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

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

  • 实部按照实浮点类型的规则转换
  • 虚部被丢弃

注意:在复数到实数的转换中,虚部中的 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*

指向非限定类型的指针可以隐式转换为指向该类型的限定版本的指针(换句话说,可以添加 constvolatilerestrict 限定符)。原始指针和结果相等。

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++ 文档 关于 隐式转换