C 语言中的类型转换

我们在前面学习了 C 语言的数据类型,那么变量在参与运算的时候类型是始终如一不变的吗?

带着这个疑问,我们可以先看一个例子:

#include <stdio.h>

#define typename(x) _Generic((x),        /* Get the name of a type */             \
                                                                                  \
        _Bool: "_Bool",                  unsigned char: "unsigned char",          \
         char: "char",                     signed char: "signed char",            \
    short int: "short int",         unsigned short int: "unsigned short int",     \
          int: "int",                     unsigned int: "unsigned int",           \
     long int: "long int",           unsigned long int: "unsigned long int",      \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
        float: "float",                         double: "double",                 \
  long double: "long double",                   char *: "pointer to char",        \
       void *: "pointer to void",                int *: "pointer to int",         \
      default: "other")

int main()
{
    int a=1,b=2;
    float c=3.14159,d=0;
    printf("a type: %s, b type: %s, c type: %s, d type: %s\n",typename(a),typename(b),typename(c),typename(d));
    a=b+c;
    printf("a=b+c, a=%d\n",a);
    printf("type (b+c): %s\n",typename(b+c));
    d=b+c;
    printf("d=b+c, d=%f\n",d);
    return 0;
}

经过编译运行后得到如下的结果:

Tips:有关如何编译运行的内容请参考前面的章节。
Tips:同时值得注意的是,这个程序中的 _Generic 来自于 C11 标准中,所以在你之前可能看到的 C 语言的书籍中是没有的。请大家不要使用过于古老的编译器,请使用推荐的较新的编译器。也就是支持 C11 以上标准的编译器。具体情况可以参照你们所选择的编译器的手册。或者直接选择 GCC 7 及更新版本的编译器。

a type: int, b type: int, c type: float, d type: float
a=b+c, a=5
type (b+c): float
d=b+c, d=5.141590

这里面的 a, b 变量为整形,而 c 和 d 都是浮点型。那么当一个整形和一个浮点型相加的时候会发生什么呢?这就是我们今天要介绍的内容。

1. 隐式类型转换

C 语言是强类型语言,也就是说不同类型的数据之间是不能进行运算的。必须保持一致的类型才能进行运算。也就是说在这个不同数据类型的计算过程中,C 语言自动进行了一次类型转换,使得两个变量的数据类型一致,才能进行相关的计算。这种自动的转换,也称之为隐式类型转换。

从前面提及的例子还可以看出,我们定义的数据类型,在不同的类型的数据运算结束后,并没有发生改变,也就是数据类型保持着我们最开始定义时候的类型。这时会发生丢弃精度的事情,也就是上面例子中小数点后面的数值就会消失。

那么这种隐式的转换有什么规律可循吗?

下面的表格就展示了类型转换的规律,当在计算过程中,数值类型不一致的时候,就会发生自动的类型转换,转换的类型是将表格中处于下方的较低优先级的数据类型,向表格上方的较高优先级的数据类型进行转换。

级别 数据类型
1 long double
2 double
3 float
4 unsigned long long
5 long long
6 unsigned long
7 long
8 unsigned int
9 int
10 char short int

根据这个表格我们就可以看出之前的转换中,int 与 float 类型进行计算,编译器会自动将 int 类型转换为 float 类型进行计算。从而使得运算在相同的数据类型间进行。

2. 显式类型转换

如果说隐式类型转换是编译器自动进行的类型转换,那么显式类型转换,则是我们人为的进行数据类型的转换,这里可以理解为是一种强制的类型的转换,这种转换将不再遵守上面的转换规则,而是按照我们人为的标明的类型进行转换。

就是在我们需要指定类型的变量前加上数据类型,并用圆括号包裹。例如: (int)a, (float)b, (long)c 等。

下面我们通过一个示例程序来看一下显式类型转换:

实例演示
预览 复制
复制成功!
#include <stdio.h>

#define typename(x) _Generic((x),        /* Get the name of a type */             \
                                                                                  \
        _Bool: "_Bool",                  unsigned char: "unsigned char",          \
         char: "char",                     signed char: "signed char",            \
    short int: "short int",         unsigned short int: "unsigned short int",     \
          int: "int",                     unsigned int: "unsigned int",           \
     long int: "long int",           unsigned long int: "unsigned long int",      \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
        float: "float",                         double: "double",                 \
  long double: "long double",                   char *: "pointer to char",        \
       void *: "pointer to void",                int *: "pointer to int",         \
      default: "other")

int main()
{
    int a=1,b=2;
    float c=3.14159,d=0;
    printf("a type: %s, b type: %s, c type: %s, d type: %s\n",typename(a),typename(b),typename(c),typename(d));
    a=(float)b+(float)c;
    printf("a=b+c, a=%d\n",a);
    printf("type (b+c): %s\n",typename((int)b+(int)c));
    d=(int)b+(int)c;
    printf("d=b+c, d=%f\n",d);
    return 0;
}#include <stdio.h>

#define typename(x) _Generic((x),        /* Get the name of a type */             \
                                                                                  \
        _Bool: "_Bool",                  unsigned char: "unsigned char",          \
         char: "char",                     signed char: "signed char",            \
    short int: "short int",         unsigned short int: "unsigned short int",     \
          int: "int",                     unsigned int: "unsigned int",           \
     long int: "long int",           unsigned long int: "unsigned long int",      \
long long int: "long long int", unsigned long long int: "unsigned long long int", \
        float: "float",                         double: "double",                 \
  long double: "long double",                   char *: "pointer to char",        \
       void *: "pointer to void",                int *: "pointer to int",         \
      default: "other")

int main()
{
    int a=1,b=2;
    float c=3.14159,d=0;
    printf("a type: %s, b type: %s, c type: %s, d type: %s\n",typename(a),typename(b),typename(c),typename(d));
    a=(float)b+(float)c;
    printf("a=b+c, a=%d\n",a);
    printf("type (b+c): %s\n",typename((int)b+(int)c));
    d=(int)b+(int)c;
    printf("d=b+c, d=%f\n",d);
    return 0;
}
运行案例 点击 "运行案例" 可查看在线运行效果

下面是执行结果。

a type: int, b type: int, c type: float, d type: float
a=b+c, a=5
type (b+c): int
d=b+c, d=5.000000

通过显式类型转换,我们可以控制在计算过程中的数据类型。之前自动转换为 float 类型的数据,在我们显式指定为 int 类型后,计算过程中就会按照 int 类型来进行计算。

3. 小结

对于隐式类型转换。其实变化的原因主要是因为计算的数值为了匹配计算精度而进行的,一般情况下都是较低精度的变量类型会转变为较高精度的变量类型。

而显式类型转换则是我们主动控制了类型的精度,可以抛去我们不需要的高的精度。同时,由于指定了类型,在转换过程中不会产生歧义。

Tips: 请注意,这里在类型转换的过程中不会自动进行四舍五入等操作,因此如果使用不当会造成数据的精度丢失。