# 数据类型

C 语言的每一种数据，都是有类型（type）的，编译器必须知道数据的类型，才能操作数据。所谓“类型”，就是相似的数据所拥有的共同特征，那么一旦知道某个值的数据类型，就能知道该值的特征和操作方式。

基本数据类型有三种：字符（char）、整数（int）和浮点数（float）。复杂的类型都是基于它们构建的。

## 字符类型

字符类型指的是单个字符，类型声明使用`char`关键字。

```c
char c = 'B';
```

上面示例声明了变量`c`是字符类型，并将其赋值为字母`B`。

C 语言规定，字符常量必须放在单引号里面。

在计算机内部，字符类型使用一个字节（8位）存储。C 语言将其当作整数处理，所以字符类型就是宽度为一个字节的整数。每个字符对应一个整数（由 ASCII 码确定），比如`B`对应整数`66`。

字符类型在不同计算机的默认范围是不一样的。一些系统默认为`-128`到`127`，另一些系统默认为`0`到`255`。这两种范围正好都能覆盖`0`到`127`的 ASCII 字符范围。

只要在字符类型的范围之内，整数与字符是可以互换的，都可以赋值给字符类型的变量。

```c
char c = 66;
// 等同于
char c = 'B';
```

上面示例中，变量`c`是字符类型，赋给它的值是整数66。这跟赋值为字符`B`的效果是一样的。

两个字符类型的变量可以进行数学运算。

```c
char a = 'B'; // 等同于 char a = 66;
char b = 'C'; // 等同于 char b = 67;

printf("%d\n", a + b); // 输出 133
```

上面示例中，字符类型变量`a`和`b`相加，视同两个整数相加。占位符`%d`表示输出十进制整数，因此输出结果为133。

单引号本身也是一个字符，如果要表示这个字符常量，必须使用反斜杠转义。

```c
char t = '\'';
```

上面示例中，变量`t`为单引号字符，由于字符常量必须放在单引号里面，所以内部的单引号要使用反斜杠转义。

这种转义的写法，主要用来表示 ASCII 码定义的一些无法打印的控制字符，它们也属于字符类型的值。

- `\a`：警报，这会使得终端发出警报声或出现闪烁，或者两者同时发生。
- `\b`：退格键，光标回退一个字符，但不删除字符。
- `\f`：换页符，光标移到下一页。在现代系统上，这已经反映不出来了，行为改成类似于`\v`。
- `\n`：换行符。
- `\r`：回车符，光标移到同一行的开头。
- `\t`：制表符，光标移到下一个水平制表位，通常是下一个8的倍数。
- `\v`：垂直分隔符，光标移到下一个垂直制表位，通常是下一行的同一列。
- `\0`：null 字符，代表没有内容。注意，这个值不等于数字0。

转义写法还能使用八进制和十六进制表示一个字符。

- `\nn`：字符的八进制写法，`nn`为八进制值。
- `\xnn`：字符的十六进制写法，`nn`为十六进制值。

```c
char x = 'B';
char x = 66;
char x = '\102'; // 八进制
char x = '\x42'; // 十六进制
```

上面示例的四种写法都是等价的。

## 整数类型

### 简介

整数类型用来表示较大的整数，类型声明使用`int`关键字。

```c
int a;
```

上面示例声明了一个整数变量`a`。

不同计算机的`int`类型的大小是不一样的。比较常见的是使用4个字节（32位）存储一个`int`类型的值，但是2个字节（16位）或8个字节（64位）也有可能使用。它们可以表示的整数范围如下。

- 16位：-32,768 到 32,767。
- 32位：-2,147,483,648 到 2,147,483,647。
- 64位：-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。

### signed，unsigned

C 语言使用`signed`关键字，表示一个类型带有正负号，包含负值；使用`unsigned`关键字，表示该类型不带有正负号，只能表示零和正整数。

对于`int`类型，默认是带有正负号的，也就是说`int`等同于`signed int`。由于这是默认情况，关键字`signed`一般都省略不写，但是写了也不算错。

```c
signed int a;
// 等同于
int a;
```

`int`类型也可以不带正负号，只表示非负整数。这时就必须使用关键字`unsigned`声明变量。

```c
unsigned int a;
```

整数变量声明为`unsigned`的好处是，同样长度的内存能够表示的最大整数值，增大了一倍。比如，16位的`signed int`最大值为32,767，而`unsigned int`的最大值增大到了65,535。

`unsigned int`里面的`int`可以省略，所以上面的变量声明也可以写成下面这样。

```c
unsigned a;
```

字符类型`char`也可以设置`signed`和`unsigned`。

```c
signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为 0 到 255
```

注意，C 语言规定`char`类型默认是否带有正负号，由当前系统决定。这就是说，`char`不等同于`signed char`，它有可能是`signed char`，也有可能是`unsigned char`。这一点与`int`不同，`int`就是等同于`signed int`。

### 整数的子类型

如果`int`类型使用4个或8个字节表示一个整数，对于小整数，这样做很浪费空间。另一方面，某些场合需要更大的整数，8个字节还不够。为了解决这些问题，C 语言在`int`类型之外，又提供了三个整数的子类型。这样有利于更精细地限定整数变量的范围，也有利于更好地表达代码的意图。

- `short int`（简写为`short`）：占用空间不多于`int`，一般占用2个字节（整数范围为-32768～32767)。
- `long int`（简写为`long`）：占用空间不少于`int`，至少为4个字节。
- `long long int`（简写为`long long`）：占用空间多于`long`，至少为8个字节。

```c
short int a;
long int b;
long long int c;
```

上面代码分别声明了三种整数子类型的变量。

默认情况下，`short`、`long`、`long long`都是带符号的（signed），即`signed`关键字省略了。它们也可以声明为不带符号（unsigned），使得能够表示的最大值扩大一倍。

```c
unsigned short int a;
unsigned long int b;
unsigned long long int c;
```

C 语言允许省略`int`，所以变量声明语句也可以写成下面这样。

```c
short a;
unsigned short a;

long b;
unsigned long b;

long long c;
unsigned long long c;
```

不同的计算机，数据类型的字节长度是不一样的。确实需要32位整数时，应使用`long`类型而不是`int`类型，可以确保不少于4个字节；确实需要64位的整数时，应该使用`long long`类型，可以确保不少于8个字节。另一方面，为了节省空间，只需要16位整数时，应使用`short`类型；需要8位整数时，应该使用`char`类型。

### 整数类型的极限值

有时候需要查看，当前系统不同整数类型的最大值和最小值，C 语言的头文件`limits.h`提供了相应的常量，比如`SCHAR_MIN`代表 signed char 类型的最小值`-128`，`SCHAR_MAX`代表 signed char 类型的最大值`127`。

为了代码的可移植性，需要知道某种整数类型的极限值时，应该尽量使用这些常量。

- `SCHAR_MIN`，`SCHAR_MAX`：signed char 的最小值和最大值。
- `SHRT_MIN`，`SHRT_MAX`：short 的最小值和最大值。
- `INT_MIN`，`INT_MAX`：int 的最小值和最大值。
- `LONG_MIN`，`LONG_MAX`：long 的最小值和最大值。
- `LLONG_MIN`，`LLONG_MAX`：long long 的最小值和最大值。
- `UCHAR_MAX`：unsigned char 的最大值。
- `USHRT_MAX`：unsigned short 的最大值。
- `UINT_MAX`：unsigned int 的最大值。
- `ULONG_MAX`：unsigned long 的最大值。
- `ULLONG_MAX`：unsigned long long 的最大值。

### 整数的进制

C 语言的整数默认都是十进制数，如果要表示八进制数和十六进制数，必须使用专门的表示法。

八进制使用`0`作为前缀，比如`017`、`0377`。

```c
int a = 012; // 八进制，相当于十进制的10
```

十六进制使用`0x`或`0X`作为前缀，比如`0xf`、`0X10`。

```c
int a = 0x1A2B; // 十六进制，相当于十进制的6699
```

有些编译器使用`0b`前缀，表示二进制数，但不是标准。

```c
int x = 0b101010;
```

注意，不同的进制只是整数的书写方法，不会对整数的实际存储方式产生影响。所有整数都是二进制形式存储，跟书写方式无关。不同进制可以混合使用，比如`10 + 015 + 0x20`是一个合法的表达式。

`printf()`的进制相关占位符如下。

- `%d`：十进制整数。
- `%o`：八进制整数。
- `%x`：十六进制整数。
- `%#o`：显示前缀`0`的八进制整数。
- `%#x`：显示前缀`0x`的十六进制整数。
- `%#X`：显示前缀`0X`的十六进制整数。

```c
int x = 100;
printf("dec = %d\n", x); // 100
printf("octal = %o\n", x); // 144
printf("hex = %x\n", x); // 64
printf("octal = %#o\n", x); // 0144
printf("hex = %#x\n", x); // 0x64
printf("hex = %#X\n", x); // 0X64
```

## 浮点数类型

任何有小数点的数值，都会被编译器解释为浮点数。所谓“浮点数”就是使用 m * b<sup>e</sup> 的形式，存储一个数值，`m`是小数部分，`b`是基数（通常是`2`），`e`是指数部分。这种形式是精度和数值范围的一种结合，可以表示非常大或者非常小的数。

浮点数的类型声明使用`float`关键字，可以用来声明浮点数变量。

```c
float c = 10.5;
```

上面示例中，变量`c`的就是浮点数类型。

`float`类型占用4个字节（32位），其中8位存放指数的值和符号，剩下24位存放小数的值和符号。`float`类型至少能够提供（十进制的）6位有效数字，指数部分的范围为（十进制的）`-37`到`37`，即数值范围为10<sup>-37</sup>到10<sup>37</sup>。

有时候，32位浮点数提供的精度或者数值范围还不够，C 语言又提供了另外两种更大的浮点数类型。

- `double`：占用8个字节（64位），至少提供13位有效数字。
- `long double`：通常占用16个字节。

注意，由于存在精度限制，浮点数只是一个近似值，它的计算是不精确的，比如 C 语言里面`0.1 + 0.2`并不等于`0.3`，而是有一个很小的误差。

```c
if (0.1 + 0.2 == 0.3) // false
```

C 语言允许使用科学计数法表示浮点数，使用字母`e`来分隔小数部分和指数部分。

```c
double x = 123.456e+3; // 123.456 x 10^3
// 等同于
double x = 123.456e3;
```

上面示例中，`e`后面如果是加号`+`，加号可以省略。注意，科学计数法里面`e`的前后，不能存在空格。

另外，科学计数法的小数部分如果是`0.x`或`x.0`的形式，那么`0`可以省略。

```c
0.3E6
// 等同于
.3E6

3.0E6
// 等同于
3.E6
```

## 布尔类型

C 语言原来并没有为布尔值单独设置一个类型，而是使用整数`0`表示伪，所有非零值表示真。

```c
int x = 1;
if (x) {
  printf("x is true!\n");
}
```

上面示例中，变量`x`等于`1`，C 语言就认为这个值代表真，从而会执行判断体内部的代码。

C99 标准添加了类型`_Bool`，表示布尔值。但是，这个类型其实只是整数类型的别名，还是使用`0`表示伪，`1`表示真，下面是一个示例。

```c
_Bool isNormal;

isNormal = 1;
if (isNormal)
  printf("Everything is OK.\n");
```

头文件`stdbool.h`定义了另一个类型别名`bool`，并且定义了`true`代表`1`、`false`代表`0`。只要加载这个头文件，就可以使用这几个关键字。

```c
#include <stdbool.h>

bool flag = false;
```

上面示例中，加载头文件`stdbool.h`以后，就可以使用`bool`定义布尔值类型，以及`false`和`true`表示真伪。

## 字面量的类型

字面量（literal）指的是代码里面直接出现的值。

```c
int x = 123;
```

上面代码中，`x`是变量，`123`就是字面量。

编译时，字面量也会写入内存，因此编译器必须为字面量指定数据类型，就像必须为变量指定数据类型一样。

一般情况下，十进制整数字面量（比如`123`）会被编译器指定为`int`类型。如果一个数值比较大，超出了`int`能够表示的范围，编译器会将其指定为`long int`。如果数值超过了`long int`，会被指定为`unsigned long`。如果还不够大，就指定为`long long`或`unsigned long long`。

小数（比如`3.14`）会被指定为`double`类型。

## 字面量后缀

有时候，程序员希望为字面量指定一个不同的类型。比如，编译器将一个整数字面量指定为`int`类型，但是程序员希望将其指定为`long`类型，这时可以为该字面量加上后缀`l`或`L`，编译器就知道要把这个字面量的类型指定为`long`。

```c
int x = 123L;
```

上面代码中，字面量`123`有后缀`L`，编译器就会将其指定为`long`类型。这里`123L`写成`123l`，效果也是一样的，但是建议优先使用`L`，因为小写的`l`容易跟数字`1`混淆。

八进制和十六进制的值，也可以使用后缀`l`和`L`指定为 Long 类型，比如`020L`和`0x20L`。

```c
int y = 0377L;
int z = 0x7fffL;
```

如果希望指定为无符号整数`unsigned int`，可以使用后缀`u`或`U`。

```c
int x = 123U;
```

`L`和`U`可以结合使用，表示`unsigned long`类型。`L`和`U`的大小写和组合顺序无所谓。

```c
int x = 123LU;
```

对于浮点数，编译器默认指定为 double 类型，如果希望指定为其他类型，需要在小数后面添加后缀`f`（float）或`l`（long double）。

科学计数法也可以使用后缀。

```c
1.2345e+10F
1.2345e+10L
```

总结一下，常用的字面量后缀有下面这些。

- `f`和`F`：`float`类型。
- `l`和`L`：对于整数是`long int`类型，对于小数是`long double`类型。
- `ll`和`LL`：Long Long 类型，比如`3LL`。
- `u`和`U`：表示`unsigned int`，比如`15U`、`0377U`。

`u`还可以与其他整数后缀结合，放在前面或后面都可以，比如`10UL`、`10ULL`和`10LLU`都是合法的。

下面是一些示例。

```c
int           x = 1234;
long int      x = 1234L;
long long int x = 1234LL

unsigned int           x = 1234U;
unsigned long int      x = 1234UL;
unsigned long long int x = 1234ULL;

float x       = 3.14f;
double x      = 3.14;
long double x = 3.14L;
```

## 溢出

每一种数据类型都有数值范围，如果存放的数值超出了这个范围（小于最小值或大于最大值），需要更多的二进制位存储，就会发生溢出。大于最大值，叫做向上溢出（overflow）；小于最小值，叫做向下溢出（underflow）。

一般来说，编译器不会对溢出报错，会正常执行代码，但是会忽略多出来的二进制位，只保留剩下的位，这样往往会得到意想不到的结果。所以，应该避免溢出。

```c
unsigned char x = 255;
x = x + 1;

printf("%d\n", x); // 0
```

上面示例中，变量`x`加`1`，得到的结果不是`256`，而是`0`。因为`x`是`unsign char`类型，最大值是`255`（二进制`11111111`），加`1`后就发生了溢出，`256`（二进制`100000000`）的最高位`1`被丢弃，剩下的值就是`0`。

再看下面的例子。

```c
unsigned int ui = UINT_MAX;  // 4,294,967,295
ui++;
printf("ui = %u\n", ui); // 0
ui--;
printf("ui = %u\n", ui); // 4,294,967,295
```

上面示例中，常量`UINT_MAX`是 unsigned int 类型的最大值。如果加`1`，对于该类型就会溢出，从而得到`0`；而`0`是该类型的最小值，再减`1`，又会得到`UINT_MAX`。

溢出很容易被忽视，编译器又不会报错，所以必须非常小心。

```c
for (unsigned int i = n; i >= 0; --i) // 错误
```

上面代码表面看似乎没有问题，但是循环变量`i`的类型是 unsigned int，这个类型的最小值是`0`，不可能得到小于0的结果。当`i`等于0，再减去`1`的时候，并不会返回`-1`，而是返回 unsigned int 的类型最大值，这个值总是大于等于`0`，导致无限循环。

为了避免溢出，最好方法就是将运算结果与类型的极限值进行比较。

```c
unsigned int ui;
unsigned int sum;

// 错误
if (sum + ui > UINT_MAX) too_big();
else sum = sum + ui;

// 正确
if (ui > UINT_MAX - sum) too_big();
else sum = sum + ui;
```

上面示例中，变量`sum`和`ui`都是 unsigned int 类型，它们相加的和还是 unsigned int 类型，这就有可能发生溢出。但是，不能通过相加的和是否超出了最大值`UINT_MAX`，来判断是否发生了溢出，因为`sum + ui`总是返回溢出后的结果，不可能大于`UINT_MAX`。正确的比较方法是，判断`UINT_MAX - sum`与`ui`之间的大小关系。

下面是另一种错误的写法。

```c
unsigned int i = 5;
unsigned int j = 7;

if (i - j < 0) // 错误
  printf("negative\n");
else
  printf("positive\n");
```

上面示例的运算结果，会输出`positive`。原因是变量`i`和`j`都是 unsigned int 类型，`i - j`的结果也是这个类型，最小值为`0`，不可能得到小于`0`的结果。正确的写法是写成下面这样。

```c
if (j > i) // ....
```

## sizeof 运算符

`sizeof`是 C 语言提供的一个运算符，返回某种数据类型或某个值占用的字节数量。它的参数可以是数据类型的关键字，也可以是变量名或某个具体的值。

```c
// 参数为数据类型
int x = sizeof(int);

// 参数为变量
int i;
sizeof(i);

// 参数为数值
sizeof(3.14);
```

上面的第一个示例，返回得到`int`类型占用的字节数量（通常是`4`或`8`）。第二个示例返回整数变量占用字节数量，结果与前一个示例完全一样。第三个示例返回浮点数`3.14`占用的字节数量，由于浮点数的字面量一律存储为 double 类型，所以会返回`8`，因为 double 类型占用的8个字节。

`sizeof`运算符的返回值，C 语言只规定是无符号整数，并没有规定具体的类型，而是留给系统自己去决定，`sizeof`到底返回什么类型。不同的系统中，返回值的类型有可能是`unsigned int`，也有可能是`unsigned long`，甚至是`unsigned long long`，对应的`printf()`占位符分别是`%u`、`%lu`和`%llu`。这样不利于程序的可移植性。

C 语言提供了一个解决方法，创造了一个类型别名`size_t`，用来统一表示`sizeof`的返回值类型。该别名定义在`stddef.h`头文件（引入`stdio.h`时会自动引入）里面，对应当前系统的`sizeof`的返回值类型，可能是`unsigned int`，也可能是`unsigned long`。

C 语言还提供了一个常量`SIZE_MAX`，表示`size_t`可以表示的最大整数。所以，`size_t`能够表示的整数范围为`[0, SIZE_MAX]`。

`printf()`有专门的占位符`%zd`或`%zu`，用来处理`size_t`类型的值。

```c
printf("%zd\n", sizeof(int));
```

上面代码中，不管`sizeof`返回值的类型是什么，`%zd`占位符（或`%zu`）都可以正确输出。

如果当前系统不支持`%zd`或`%zu`，可使用`%u`（unsigned int）或`%lu`（unsigned long int）代替。

## 类型的自动转换

某些情况下，C 语言会自动转换某个值的类型。

### 赋值运算

赋值运算符会自动将右边的值，转成左边变量的类型。

（1）浮点数赋值给整数变量

浮点数赋予整数变量时，C 语言直接丢弃小数部分，而不是四舍五入。

```c
int x = 3.14;
```

上面示例中，变量`x`是整数类型，赋给它的值是一个浮点数。编译器会自动把`3.14`先转为`int`类型，丢弃小数部分，再赋值给`x`，因此`x`的值是`3`。

这种自动转换会导致部分数据的丢失（`3.14`丢失了小数部分），所以最好不要跨类型赋值，尽量保证变量与所要赋予的值是同一个类型。

注意，舍弃小数部分时，不是四舍五入，而是整个舍弃。

```c
int x = 12.99;
```

上面示例中，`x`等于`12`，而不是四舍五入的`13`。

（2）整数赋值给浮点数变量

整数赋值给浮点数变量时，会自动转为浮点数。

```c
float y = 12 * 2;
```

上面示例中，变量`y`的值不是`24`，而是`24.0`，因为等号右边的整数自动转为了浮点数。

（3）窄类型赋值给宽类型

字节宽度较小的整数类型，赋值给字节宽度较大的整数变量时，会发生类型提升，即窄类型自动转为宽类型。

比如，`char`或`short`类型赋值给`int`类型，会自动提升为`int`。

```c
char x = 10;
int i = x + y;
```

上面示例中，变量`x`的类型是`char`，由于赋值给`int`类型，所以会自动提升为`int`。

（4）宽类型赋值给窄类型

字节宽度较大的类型，赋值给字节宽度较小的变量时，会发生类型降级，自动转为后者的类型。这时可能会发生截值（truncation），系统会自动截去多余的二进制位，导致难以预料的结果。

```javascript
int i = 321;
char ch = i; // ch 的值是 65 （321 % 256 的余值）
```

上面例子中，变量`ch`是`char`类型，宽度是8个二进制位。变量`i`是`int`类型，将`i`赋值给`ch`，后者只能容纳`i`（二进制形式为`101000001`，共9位）的后八位，前面多出来的二进制位被丢弃，保留后八位就变成了`01000001`（十进制的65，相当于字符`A`）。

浮点数赋值给整数类型的值，也会发生截值，浮点数的小数部分会被截去。

```c
double pi = 3.14159;
int i = pi; // i 的值为 3
```

上面示例中，`i`等于`3`，`pi`的小数部分被截去了。

### 混合类型的运算

不同类型的值进行混合计算时，必须先转成同一个类型，才能进行计算。转换规则如下：

（1）整数与浮点数混合运算时，整数转为浮点数类型，与另一个运算数类型相同。

```c
3 + 1.2 // 4.2
```

上面示例是`int`类型与`float`类型的混合计算，`int`类型的`3`会先转成`float`的`3.0`，再进行计算，得到`4.2`。

（2）不同的浮点数类型混合运算时，宽度较小的类型转为宽度较大的类型，比如`float`转为`double`，`double`转为`long double`。

（3）不同的整数类型混合运算时，宽度较小的类型会提升为宽度较大的类型。比如`short`转为`int`，`int`转为`long`等，有时还会将带符号的类型`signed`转为无符号`unsigned`。

下面例子的执行结果，可能会出人意料。

```c
int a = -5;
if (a < sizeof(int)）
  do_something();
```

上面示例中，变量`a`是带符号整数，`sizeof(int)`是`size_t`类型，这是一个无符号整数。按照规则，signed int 自动转为 unsigned int，所以`a`会自动转成无符号整数`4294967291`（转换规则是`-5`加上无符号整数的最大值，再加1），导致比较失败，`do_something()`不会执行。

所以，最好避免无符号整数与有符号整数的混合运算。因为这时 C 语言会自动将`signed int`转为`unsigned int`，可能不会得到预期的结果。

### 整数类型的运算

两个相同类型的整数运算时，或者单个整数的运算，一般来说，运算结果也属于同一类型。但是有一个例外，宽度小于`int`的类型，运算结果会自动提升为`int`。

```c
unsigned char a = 66;

if ((-a) < 0) printf("negative\n");
else printf("positive\n");
```

上面示例中，变量`a`是 unsigned char 类型，这个类型不可能小于0，但是`-a`不是 unsigned char 类型，会自动转为 int 类型，导致上面的代码输出 negative。

再看下面的例子。

```c
unsigned char a = 1;
unsigned char b = 255;
unsigned char c = 255;

if ((a - 5) < 0) do_something();
if ((b + c) > 300) do_something();
```

上面示例中，表达式`a - 5`和`b + c`都会自动转为 int 类型，所以函数`do_something()`会执行两次。

### 函数

函数的参数和返回值，会自动转成函数定义里指定的类型。

```c
int dostuff(int, unsigned char);

char m = 42;
unsigned short n = 43;
long long int c = dostuff(m, n);
```

上面示例中，参数变量`m`和`n`不管原来的类型是什么，都会转成函数`dostuff()`定义的参数类型。

下面是返回值自动转换类型的例子。

```c
char func(void) {
  int a = 42;
  return a;
}
```

上面示例中，函数内部的变量`a`是`int`类型，但是返回的值是`char`类型，因为函数定义中返回的是这个类型。

## 类型的显式转换

原则上，应该避免类型的自动转换，防止出现意料之外的结果。C 语言提供了类型的显式转换，允许手动转换类型。

只要在一个值或变量的前面，使用圆括号指定类型`(type)`，就可以将这个值或变量转为指定的类型，这叫做“类型指定”（casting）。

```c
(unsigned char) ch
```

上面示例将变量`ch`转成无符号的字符类型。

```c
long int y = (long int) 10 + 12;
```

上面示例中，`(long int)`将`10`显式转为`long int`类型。这里的显示转换其实是不必要的，因为赋值运算符会自动将右边的值，转为左边变量的类型。

## 可移植类型

C 语言的整数类型（short、int、long）在不同计算机上，占用的字节宽度可能是不一样的，无法提前知道它们到底占用多少个字节。

程序员有时控制准确的字节宽度，这样的话，代码可以有更好的可移植性，头文件`stdint.h`创造了一些新的类型别名。

（1）精确宽度类型(exact-width integer type)，保证某个整数类型的宽度是确定的。

- `int8_t`：8位有符号整数。
- `int16_t`：16位有符号整数。
- `int32_t`：32位有符号整数。
- `int64_t`：64位有符号整数。
- `uint8_t`：8位无符号整数。
- `uint16_t`：16位无符号整数。
- `uint32_t`：32位无符号整数。
- `uint64_t`：64位无符号整数。

上面这些都是类型别名，编译器会指定它们指向的底层类型。比如，某个系统中，如果`int`类型为32位，`int32_t`就会指向`int`；如果`long`类型为32位，`int32_t`则会指向`long`。

下面是一个使用示例。

```c
#include <stdio.h>
#include <stdint.h>

int main(void) {
  int32_t x32 = 45933945;
  printf("x32 = %d\n", x32);
  return 0;
}
```

上面示例中，变量`x32`声明为`int32_t`类型，可以保证是32位的宽度。

（2）最小宽度类型（minimum width type），保证某个整数类型的最小长度。

- int_least8_t
- int_least16_t
- int_least32_t
- int_least64_t
- uint_least8_t
- uint_least16_t
- uint_least32_t
- uint_least64_t

上面这些类型，可以保证占据的字节不少于指定宽度。比如，`int_least8_t`表示可以容纳8位有符号整数的最小宽度的类型。

（3）最快的最小宽度类型（fast minimum width type），可以使整数计算达到最快的类型。

- int_fast8_t
- int_fast16_t
- int_fast32_t
- int_fast64_t
- uint_fast8_t
- uint_fast16_t
- uint_fast32_t
- uint_fast64_t

上面这些类型是保证字节宽度的同时，追求最快的运算速度，比如`int_fast8_t`表示对于8位有符号整数，运算速度最快的类型。这是因为某些机器对于特定宽度的数据，运算速度最快，举例来说，32位计算机对于32位数据的运算速度，会快于16位数据。

（4）可以保存指针的整数类型。

- `intptr_t`：可以存储指针（内存地址）的有符号整数类型。
- `uintptr_t`：可以存储指针的无符号整数类型。

（5）最大宽度整数类型，用于存放最大的整数。

- `intmax_t`：可以存储任何有效的有符号整数的类型。
- `uintmax_t`：可以存放任何有效的无符号整数的类型。

上面的这两个类型的宽度比`long long`和`unsigned long`更大。