Namespaces
Variants

Implicit conversions

From cppreference.net

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

int n = 1L; // 表达式 1L 的类型为 long,但期望的类型是 int
n = 2.1; // 表达式 2.1 的类型为 double,但期望的类型是 int
char* p = malloc(10); // 表达式 malloc(10) 的类型为 void*,但期望的类型是 char*

在以下情况下会发生转换:

目录

按赋值方式转换

  • 赋值运算符 中,右操作数的值被转换为左操作数的非限定类型。
  • 标量初始化 中,初始化器表达式的值被转换为被初始化对象的非限定类型。
  • 在具有原型的函数的 函数调用表达式 中,每个实参表达式的值被转换为对应形参声明的非限定类型。
  • 返回语句 中, return 操作数的值被转换为具有函数返回类型的对象。

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

默认参数提升

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

1) 一个 无原型的函数 (until C23) ,
2) 一个 可变参数函数 ,其中实参表达式是与省略号形参匹配的尾部参数之一。

每个整数类型的实参都会进行 整数提升 ,而每个类型为 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) 若一个操作数具有十进制浮点类型,则另一操作数不得具有标准浮点、

复数或虚数类型。

  • 首先,若任一操作数的类型为 _Decimal128 ,则另一操作数转换为 _Decimal128
  • 否则,若任一操作数的类型为 _Decimal64 ,则另一操作数转换为 _Decimal64
  • 否则,若任一操作数的类型为 _Decimal32 ,则另一操作数转换为 _Decimal32
(since C23)
2) 否则,如果其中一个操作数是 long double long double complex long double imaginary (C99 起) ,则另一个操作数按以下方式隐式转换:
  • 整数或实浮点类型转换为 long double
(C99 起)
3) 否则,如果其中一个操作数是 double double complex double imaginary (C99 起) ,则另一个操作数按以下规则隐式转换:
  • 整数或实浮点类型转换为 double
(C99 起)
4) 否则,若一个操作数为 float float complex float imaginary (C99 起) ,则另一操作数按以下规则隐式转换:
  • 整数类型转换为 float (唯一可能的实数类型是 float,保持原样)
(C99 起)
5) 否则,两个操作数均为整数类型。两个操作数均进行 整型提升 ;随后在整型提升后,适用以下情况之一:
  • 若类型相同,则该类型即为公共类型。
  • 否则,类型不同:
    • 若类型具有相同的符号性(均为有符号或均为无符号),则类型具有较低 转换等级  [1] 的操作数将隐式转换 [2] 为另一操作数的类型。
    • 否则,操作数具有不同的符号性:
      • 若无符号类型的 转换等级 大于或等于有符号类型的等级,则有符号类型的操作数隐式转换为无符号类型。
      • 否则,无符号类型的 转换等级 小于有符号类型:
        • 若有符号类型可表示无符号类型的所有值,则无符号类型的操作数隐式转换为有符号类型。
        • 否则,两个操作数均隐式转换为有符号操作数类型对应的无符号类型。
  1. 关于等级规则请参阅下方 整型提升 章节。
  2. 请参阅下方 隐式转换语义 中的“整型转换”部分。
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规则 ,计算始终可以在比这些规则所指定的类型更窄的类型中执行。

值转换

左值转换

任何非数组类型的 左值表达式 ,当在除以下情况外的任何语境中使用时:

进行 左值转换 :类型保持不变,但会丢失 const / volatile / restrict 限定符及 atomic 属性(若存在)。值保持不变,但失去其左值属性(可能无法再获取其地址)。

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

如果左值指代一个自动存储期对象,且该对象的地址从未被获取,同时该对象未被初始化(声明时未提供初始化器且在首次使用前未进行赋值操作),则行为是未定义的。

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

volatile int n = 1;
int x = n;            // 对n进行左值转换会读取n的值
volatile int* p = &n; // 无左值转换:不会读取n的值

数组到指针转换

任何 左值表达式 (C99前) 表达式 (C99起) 具有 数组类型 ,当在除以下情况外的任何上下文中使用时

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; }

函数到指针转换

任何函数指示符表达式,当用于除

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

int f(int);
int (*p)(int) = f; // 转换为 &f
(***p)(1); // 重复解引用至 f 并转换回 &f

隐式转换语义

隐式转换,无论是 如同通过赋值  还是 通常算术转换  ,都包含两个阶段:

1) 值转换(如适用),
2) 下列转换之一(若其能生成目标类型)。

兼容类型

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

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 是每个 整数类型 的属性,其定义如下:

1) 所有有符号整数类型的等级均不相同,且随其精度递增: signed char 的等级 < short 的等级 < int 的等级 < long int 的等级 < long long int 的等级
2) 所有有符号整数类型的等级等于对应无符号整数类型的等级
3) 任何标准整数类型的等级均高于同尺寸的扩展整数类型 或位精确整数类型 (C23 起) (即 __int64 的等级 < long long int 的等级,但根据规则 (1) long long 的等级 < __int128 的等级)
4) char 的等级等于 signed char 的等级和 unsigned char 的等级
5) _Bool (C23前) bool (C23起) 的等级低于任何其他标准整数类型的等级
6) 任何枚举类型的等级等于其兼容整数类型的等级
7) 排名具有传递性:若 T1 的排名 < T2 的排名且 T2 的排名 < T3 的排名,则 T1 的排名 < T3 的排名。
8) 位精确有符号整数类型的等级应大于任何宽度较小的标准整数类型或任何宽度较小的位精确整数类型的等级。
9) 任何位精确整数类型相对于同宽度的扩展整数类型的等级由实现定义。
(since 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起)

整数转换

任何整数类型的值都可以隐式转换为其他整数类型。除上述提升和布尔转换涵盖的情况外,规则如下:

  • 若目标类型能够表示该值,则值保持不变,
  • 否则,若目标类型为无符号类型,则通过重复加减 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 算术,则采用向最近偶数舍入。
  • 若该值无法被表示,则行为未定义

此转换结果可能具有比其目标类型所指示范围更广和精度更高的值(参见 FLT_EVAL_METHOD )。

double d = 0.1; // d = 0.1000000000000000055511151231257827021181583404541015625
float f = d;    // f = 0.100000001490116119384765625
float x = 2 * (double)FLT_MAX; // 未定义行为

复数类型转换

任何复数类型的值均可隐式转换为其他复数类型。实部和虚部分别遵循实浮点类型的转换规则。

double complex d = 0.1 + 0.1*I;
float complex f = d; // f 为 (0.100000001490116119384765625, 0.100000001490116119384765625)

虚数类型转换

任何虚数类型的值均可隐式转换为其他虚数类型。虚部遵循实浮点类型的转换规则。

double imaginary d = 0.1*_Imaginary_I;
float imaginary f = d; // f 为 0.100000001490116119384765625*I

实数-复数转换

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

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

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

  • 实部遵循实浮点类型的转换规则。
  • 虚部被舍弃。

注意:在复数到实数的转换中,虚部的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++ 文档 关于 隐式转换