命名空间
变体
操作

隐式转换

来自 cppreference.com
< c‎ | 语言

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

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 double, long double complex, 或 long double imaginary (自 C99 起),则另一个操作数将隐式转换为如下类型
  • 整型或实浮点型转换为 long double
  • 复数类型转换为 long double complex
  • 虚数类型转换为 long double imaginary
(自 C99 起)
3) 否则,如果一个操作数为 double, double complex, 或 double imaginary (自 C99 起),则另一个操作数将隐式转换为如下类型
  • 整型或实浮点型转换为 double
(自 C99 起)
4) 否则,如果一个操作数为 float, float complex, 或 float imaginary (自 C99 起),则另一个操作数将隐式转换为如下类型
  • 整型转换为 float(唯一可能的实数类型是 float,它保持不变)
(自 C99 起)
5) 否则,两个操作数都是整数。两个操作数都进行 _整数提升_(见下文);然后,在整数提升后,将应用以下情况之一
  • 如果类型相同,则该类型为公共类型。
  • 否则,类型不同
    • 如果类型具有相同的符号性(都带符号或都不带符号),则类型具有较低 _转换等级_ 1 的操作数将隐式转换为另一个类型。
    • 否则,操作数具有不同的符号性
      • 如果无符号类型的 _转换等级_ 大于或等于带符号类型的等级,则带符号类型的操作数将隐式转换为无符号类型。
      • 否则,无符号类型的 _转换等级_ 小于带符号类型
        • 如果带符号类型可以表示无符号类型的所有值,则无符号类型的操作数将隐式转换为带符号类型。
        • 否则,两个操作数都进行隐式转换,转换为带符号操作数类型对应无符号类型。

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 起)

注意:无论通常的算术转换如何,根据 as-if 规则,计算始终可以在比这些规则所指定的更窄的类型中执行。

[edit] 值转换

[edit] 左值转换

任何非数组类型的 左值表达式,在除以下情况之外的任何上下文中使用时

进行 _左值转换_:类型保持不变,但会丢失 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

[edit] 数组到指针转换

任何 左值表达式数组类型,在除以下情况之外的任何上下文中使用时

进行转换为指向其第一个元素的非左值指针。

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

int a[3], b[3][4];
int* p = a;      // conversion to &a[0]
int (*q)[4] = b; // conversion to &b[0]

[edit] 函数到指针转换

任何函数指示符表达式,在除以下情况之外的任何上下文中使用时

进行转换为指向表达式所指定的函数的非左值指针。

int f(int);
int (*p)(int) = f; // conversion to &f
(***p)(1); // repeated dereference to f and conversion back to &f

[edit] 隐式转换语义

隐式转换,无论是 _按赋值方式_ 还是 _通常的算术转换_,都包括两个阶段

1) 值转换(如果适用)
2) 下面列出的转换之一(如果它可以生成目标类型)

[edit] 兼容类型

将任何类型的值得转换为任何 兼容类型 始终是一个无操作,不会改变表示形式。

uint8_t (*a)[10];         // if uint8_t is a typedef to unsigned char
unsigned char (*b)[] = a; // then these pointer types are compatible

[edit] 整数提升

整数提升是将任何 _等级_ 小于或等于 _int_ 的等级的整数类型的值得,或将类型为 _Bool(直到 C23)bool(自 C23 起)intsigned intunsigned int位字段 的值得隐式转换为类型为 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 起)

[edit] 整型转换

任何整型类型的数值都可以隐式转换为任何其他整型类型。除了上面提到的提升和布尔转换之外,规则如下:

  • 如果目标类型可以表示该数值,则数值保持不变
  • 否则,如果目标类型是无符号的,则会从源数值中反复减去或加上 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

[edit] 实浮点-整型转换

任何实浮点类型的有限数值都可以隐式转换为任何整型类型。除了上面提到的布尔转换之外,规则如下:

  • 小数部分被丢弃(截断为零)。
  • 如果结果数值可以由目标类型表示,则使用该数值
  • 否则,行为是未定义的
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

[edit] 实浮点转换

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

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

[edit] 指针转换

指向 void 的指针可以隐式转换为任何指向对象类型的指针,反之亦然,语义如下:

  • 如果将指向对象的指针转换为指向 void 的指针再转换回来,它的值将与原始指针相等。
  • 没有提供其他保证
int* p = malloc(10 * sizeof(int)); // malloc returns void*

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

int n;
const int* p = &n; // &n has type int*

任何值为 0 的整型 常量表达式 以及值为零的整型指针表达式(强制转换为 void* 类型)可以隐式转换为任何指针类型(指向对象和指向函数的指针)。结果是其类型的空指针值,保证与该类型的任何非空指针值不等。这个整型或 void* 表达式被称为 *空指针常量*,标准库提供了一个该常量的定义作为宏 NULL

int* p = 0;
double* q = NULL;

[edit] 注释

虽然在任何算术运算符中,有符号整型溢出都是未定义的行为,但在整型转换中,有符号整型溢出仅仅是未指定的行为。

另一方面,虽然在任何算术运算符(以及整型转换)中,无符号整型溢出都是一个定义良好的操作,并遵循模运算的规则,但在浮点到整型转换中,无符号整型溢出是未定义的行为:可以转换为无符号整型的实浮点类型的数值是开区间 (-1; Unnn_MAX+1) 中的数值。

unsigned int n = -1.0; // undefined behavior

指针和整数之间的转换(除了 从指针到 _Bool(直到 C23)bool(从 C23 开始)(从 C99 开始)从值为零的整型常量表达式到指针),指向对象的指针之间的转换(除非其中一个是指向 void 的指针),以及指向函数的指针之间的转换(除非函数具有兼容类型)从来都不是隐式的,需要使用 强制转换运算符

在指向函数的指针和指向对象的指针(包括 void*)或整数之间没有转换(隐式或显式)。

[edit] 参考文献

  • C23 标准(ISO/IEC 9899:2024)
  • 6.3 转换(p: TBD)
  • C17 标准(ISO/IEC 9899:2018)
  • 6.3 转换(p: 37-41)
  • C11 标准(ISO/IEC 9899:2011)
  • 6.3 转换(p: 50-56)
  • C99 标准(ISO/IEC 9899:1999)
  • 6.3 转换(p: 42-48)
  • C89/C90 标准(ISO/IEC 9899:1990)
  • 3.2 转换

[edit] 另请参阅

C++ 文档 用于 隐式转换