跳至主要內容

数据类型

Sankgao约 7816 字大约 26 分钟LanguagesC

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

变量和常量数据

在程序的指导下,计算机可以做许多事情,如:数值计算、名字排序、执行语言等。要完成这些任务,程序需要使用数据,即承载信息的数字和字符。

有些数据类型在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,这些称为 常量constant)。其他数据类型在程序运行期间可能会改变或被赋值,这些称为 变量variable)。

位、字节和字

位、字节和字是描述计算机数据单元或存储单元的术语。这里主要指存储单元。

最小的存储单元是位(bit),可以存储 01(或者说,位用于设置 “开” 或 “关”)。虽然 1 位存储的信息有限,但是计算机中位的数量十分庞大。位是计算机内存的基本构建块。

字节(byte)是常用的计算机存储单位。对于几乎所有的机器,1 字节均为 8 位。这是字节的标准定义,至少在衡量存储单位时是这样。既然 1 位可以表示 01,那么 8 位字节就有 25628 次方)种可能的 01 的组合。通过二进制编码(仅用 01 便可表示数字),便可表示 0 ~ 255 的整数或一组字符。

字(word)是设计计算机时给定的自然存储单位。对于 8 位的微型计算机(如:最初的苹果机),1 个字长只有 8 位。从那以后,个人计算机字长增至 16 位、32 位,直到目前的 64 位。计算机的字长越大,其数据转移越快,允许的内存访问也更多。

数据类型

C 通过识别一些基本的 数据类型 来区分和使用这些不同的数据类型。K&R C 给出了 7 个与类型相关的关键字。C90 标准添加了 2 个关键字,C99 标准又添加了 3 个关键字。

类型关键字类型关键字存储大小
intsigned_Bool
longvoid_Complex
short_Imaginary
unsigned
char
float
double

int 关键字来表示基本的整数类型。后 3 个关键字(longshortunsigned)和 C90 新增的 signed 用于提供基本整数类型的变式,例如:unsigned short intchar 关键字用于指定字母和其他字符(如:#$%*)。另外,char 类型也可以表示较小的整数。floatdoublelong double 表示带小数点的数。_Bool 类型表示布尔值(truefalse),_Complex_Imaginary 分别表示复数和虚数。

通过这些关键字创建的类型,按计算机的存储方式可分为两大基本类型:整数类型浮点数类型。

类型关键字类型关键字存储大小值范围
字符型char1 字节-2(8-1) ~ 2(8-1) - 1 即 -128 ~ 127
有符号字符型signed char1 字节-2(8-1) ~ 2(8-1) - 1 即 -128 ~ 127
无符号字符型unsigned char1 字节0 ~ 2(8) - 1 即 0 ~ 255
整型int4 字节-2(32-1) ~ 2(32-1) - 1 即 -2,147,483,648 ~ 2,147,483,647
有符号整型signed int4 字节-2(32-1) ~ 2(32-1) - 1 即 -2,147,483,648 ~ 2,147,483,647
无符号整型unsigned int4 字节0 ~ 2(32) - 1 即 0 ~ 4,294,967,295
短整型short int2 字节-2(16-1) ~ 2(16-1) - 1 即 -32,768 ~ 32,767
有符号短整型signed short int2 字节-2(16-1) ~ 2(16-1) - 1 即 -32,768 ~ 32,767
无符号短整型unsigned short int2 字节0 ~ 2(16) - 1 即 0 ~ 65,535
长整型long int4 字节-2(32-1) ~ 2(32-1) - 1 即 -2,147,483,648 ~ 2,147,483,647
有符号长整型signed long int4 字节-2(32-1) ~ 2(32-1) - 1 即 -2,147,483,648 ~ 2,147,483,647
无符号长整型unsigned long int4 字节0 ~ 2(32) - 1 即 0 ~ 4,294,967,295
长长整型long long int8 字节-2(64-1) ~ 2(64-1) - 1 即 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
有符号长长整型signed long long int8 字节-2(64-1) ~ 2(64-1) - 1 即 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
无符号长长整型unsigned long long int8 字节0 ~ 2(64) - 1 即 0 ~ 18,446,744,073,709,551,615
单精度浮点数float4 字节
双精度浮点数double8 字节
长双精度浮点数long double16 字节

字符类型

字符类型指的是单个字符,类型声明使用 char 关键字。例如:

char c = 'B';

在 C 语言中,用单引号括起来的单个字符被称为 字符常量character constant)。

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

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

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

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

两个字符类型的变量可以进行数学运算,字符类型变量 ab 相加,视同两个整数相加。例如:

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

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

单引号本身也是一个字符,如果要表示这个字符常量,必须使用反斜杠转义。使用特殊的符号序列表示一些特殊的字符。这些符号序列叫作 转义序列escape sequence)。例如:

char t = '\'';

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

常见的转义序列:

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

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

  • \nn:字符的八进制写法,nn 为八进制值
  • \xnn:字符的十六进制写法,nn 为十六进制值

以下四种写法都是等价的:

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

整数类型

简介

整数类型用来表示较大的整数,类型声明使用 int 关键字。例如:

int 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 一般都省略不写,但是写了也不算错。例如:

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

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

unsigned int a;

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

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

unsigned a;

字符类型 char 也可以设置 signedunsigned。例如:

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 个字节
short int a;
long int b;
long long int c;

默认情况下,shortlonglong long 都是带符号的(signed),即 signed 关键字省略了。它们也可以声明为不带符号(unsigned),使得能够表示的最大值扩大一倍。例如:

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

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

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 作为前缀,比如:0170377。例如:

int a = 012;  // 八进制,相当于十进制的 10

十六进制使用 0x0X 作为前缀,比如:0xf0X10。例如:

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

有些编译器使用 0b 前缀,表示二进制数,但不是标准。例如:

int x = 0b101010;

注意

不同的进制只是整数的书写方法,不会对整数的实际存储方式产生影响。

所有整数都是二进制形式存储,跟书写方式无关。

不同进制可以混合使用,比如:10 + 015 + 0x20 是一个合法的表达式。

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

  • %d:十进制整数
  • %o:八进制整数
  • %x:十六进制整数
  • %#o:显示前缀 0 的八进制整数
  • %#x:显示前缀 0x 的十六进制整数
  • %#X:显示前缀 0X 的十六进制整数
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 * be 的形式,存储一个数值,m 是小数部分,b 是基数(通常是 2),e 是指数部分。这种形式是精度和数值范围的一种结合,可以表示非常大或者非常小的数。

浮点数的类型声明使用 float 关键字,可以用来声明浮点数变量。例如:

float c = 10.5;

float 类型占用 4 个字节(32 位),其中 8 位存放指数的值和符号,剩下 24 位存放小数的值和符号。float 类型至少能够提供(十进制的)6 位有效数字,指数部分的范围为(十进制的)-37 ~ 37,即数值范围为 10-37 ~ 1037

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

  • double:占用 8 个字节(64 位),至少提供 13 位有效数字
  • long double:通常占用 16 个字节

注意

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

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

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

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

e 后面如果是加号 +,加号可以省略。注意:科学计数法里面 e 的前后,不能存在空格。

另外,科学计数法的小数部分如果是 0.xx.0 的形式,那么 0 可以省略。例如:

0.3E6
// 等同于
.3E6

3.0E6
// 等同于
3.E6

布尔类型

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

int x = 1;

if (x) {
    printf("x is true!\n");
}

变量 x 等于 1,C 语言就认为这个值代表真,从而会执行判断体内部的代码。

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

_Bool isNormal;

isNormal = 1;

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

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

#include <stdbool.h>

bool flag = false;

加载头文件 stdbool.h 以后,就可以使用 bool 定义布尔值类型,以及 falsetrue 表示真伪。

字面量的类型

字面量literal)指的是代码里面直接出现的值。例如:

int x = 123;

x 是变量,123 就是字面量。

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

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

小数(比如:3.14)会被指定为 double 类型。

字面量后缀

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

int x = 123L;

这里 123L 写成 123l,效果也是一样的,但是建议优先使用 L,因为小写的 l 容易跟数字 1 混淆。

八进制和十六进制的值,也可以使用后缀 lL 指定为 Long 类型。例如:

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

如果希望指定为无符号整数 unsigned int,可以使用后缀 uU。例如:

int x = 123U;

LU 可以结合使用,表示 unsigned long 类型。LU 的大小写和组合顺序无所谓。例如:

int x = 123LU;

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

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

1.2345e+10F
1.2345e+10L

常用的字面量后缀:

  • f 和 Ffloat 类型
  • l 和 L:对于整数是 long int 类型,对于小数是 long double 类型
  • ll 和 LLLong Long 类型,比如:3LL
  • u 和 U:表示 unsigned int,比如:15U0377U

u 还可以与其他整数后缀结合,放在前面或后面都可以,比如:10UL10ULL10LLU 都是合法的。

溢出

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

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

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

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

变量 x1,得到的结果不是 256,而是 0。因为 xunsigned char 类型,最大值是 255(二进制 11111111),加 1 后就发生了溢出,256(二进制 100000000)的最高位 1 被丢弃,剩下的值就是 0

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_MAXunsigned int 类型的最大值。如果加 1,对于该类型就会溢出,从而得到 0;而 0 是该类型的最小值,再减 1,又会得到 UINT_MAX

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

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

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

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

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;

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

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

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

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

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

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

sizeof 运算符

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

// 参数为数据类型
int x = sizeof(int);  // 4 或 8

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

// 参数为数值
sizeof(3.14);  // 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 类型的值。例如:

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

不管 sizeof 返回值的类型是什么,%zd 占位符(或 %zu)都可以正确输出。

如果当前系统不支持 %zd%zu,可使用 %u(unsigned int)或 %lu(unsigned long int)代替。

类型的自动转换

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

赋值运算

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

  1. 浮点数赋值给整数变量

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

    int x = 3.14;
    

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

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

  2. 整数赋值给浮点数变量

    整数赋值给浮点数变量时,会自动转为浮点数。例如:

    float y = 12 * 2;
    

    变量 y 的值不是 24,而是 24.0,因为等号右边的整数自动转为了浮点数。

  3. 窄类型赋值给宽类型

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

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

    变量 x 的类型是 char,由于赋值给 int 类型,所以会自动提升为 int

  4. 宽类型赋值给窄类型

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

    int i = 321;
    char ch = i;  // ch 的值是 65 (321 % 256 的余值)
    

    变量 chchar 类型,宽度是 8 个二进制位。变量 iint 类型,将 i 赋值给 ch,后者只能容纳 i(二进制形式为 101000001,共 9位)的后八位,前面多出来的二进制位被丢弃,保留后八位就变成了 01000001(十进制的 65,相当于字符 A)。

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

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

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

混合类型的运算

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

  1. 整数与浮点数混合运算时,整数转为浮点数类型,与另一个运算数类型相同。例如:

    3 + 1.2  // 4.2
    

    int 类型的 3 会先转成 float3.0,再进行计算,得到 4.2

  2. 不同的浮点数类型混合运算时,宽度较小的类型转为宽度较大的类型。比如:float 转为 doubledouble 转为 long double

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

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

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。例如:

unsigned char a = 66;

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

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

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 - 5b + c 都会自动转为 int 类型,所以函数do_something()会执行两次。

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

int dostuff(int, unsigned char);

char m = 42; unsigned short n = 43; long long int c = dostuff(m, n); 上面示例中,参数变量m和n不管原来的类型是什么,都会转成函数dostuff()定义的参数类型。

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

char func(void) { int a = 42; return a; } 上面示例中,函数内部的变量a是int类型,但是返回的值是char类型,因为函数定义中返回的是这个类型。

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

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

(unsigned char) ch 上面示例将变量ch转成无符号的字符类型。

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。

下面是一个使用示例。

#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更大。