命名空间
变体
操作

隐式转换

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

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

[编辑] 默认实参提升

函数调用表达式中,当调用以下函数时:

1) 没有原型的函数 (直至 C23)
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 complexlong double imaginary (C99 起),则另一个操作数隐式转换为如下:
  • 整型或实浮点类型转换为 long double
  • 复数类型转换为 long double complex
  • 虚数类型转换为 long double imaginary
(C99 起)
3) 否则,如果一个操作数是 doubledouble complexdouble imaginary (C99 起),则另一个操作数隐式转换为如下:
  • 整型或实浮点类型转换为 double
(C99 起)
4) 否则,如果一个操作数是 floatfloat complexfloat 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

结果类型确定如下:

  • 如果两个操作数都是复数,则结果类型是复数。
  • 如果两个操作数都是虚数,则结果类型是虚数。
  • 如果两个操作数都是实数,则结果类型是实数。
  • 如果两个浮点操作数具有不同的类型域(复数 vs 实数,复数 vs 虚数,或虚数 vs 实数),则结果类型是复数。
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 规则,计算始终可以在比这些规则指定的更窄的类型中执行。

[编辑] 值转换

[编辑] 左值转换

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

会发生左值转换:类型保持不变,但会失去const/volatile/restrict限定符和atomic属性(如果有)。值保持不变,但失去其左值属性(可能无法再取地址)。

如果左值具有不完整类型,则行为未定义。

如果左值指定了一个自动存储期对象,其地址从未被获取,并且该对象未被初始化(未用初始化器声明,且在使用前未进行赋值),则行为未定义。

此转换模拟从对象位置加载对象值的内存操作。

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

[编辑] 整型提升

整型提升是任何整型值(其等级小于或等于 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的等级,但由于规则(1)long long的等级 < __int128的等级)
4) char 的等级等于 signed charunsigned 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* 类型的整数指针表达式,都可以隐式转换为任何指针类型(包括指向对象和指向函数的指针)。结果是其类型的空指针值,保证与该类型的任何非空指针值比较不相等。这个整数或 void* 表达式被称为空指针常量,标准库通过宏 NULL 提供了这个常量的一个定义。

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

[编辑] 注意

尽管任何算术运算符中的有符号整数溢出是未定义行为,但整数转换中有符号整数溢出仅仅是未指定行为。

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

unsigned int n = -1.0; // undefined behavior

指针和整数之间的转换(从指针到 _Bool(C23 之前)bool(C23 及之后) 除外,以及 (C99 及之后)从值为零的整数常量表达式到指针除外),指向对象指针之间的转换(to 或 from 是指向 void 的指针除外)以及指向函数指针之间的转换(当函数具有兼容类型时除外)从不隐式进行,需要强制转换运算符

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

[编辑] 参考

  • C23 标准 (ISO/IEC 9899:2024)
  • 6.3 转换 (p: 待定)
  • 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 转换

[编辑] 另请参阅

C++ 文档 关于 隐式转换