6. C语言的数据类型

C 语言的每一种数据,都是有类型(type)的,编译器必须知道数据的类型,才能操作数据。所谓“类型”,就是相似的数据所拥有的共同特征,那么一旦知道某个值的数据类型,就能知道该值的特征和操作方式。
基本数据类型有三种:字符(char)、整数(int)和浮点数(float)。复杂的类型都是基于它们构建的。

字符类型

  • 字符类型指的是单个字符,类型声明使用char关键字。
    char c = 'B';
    
  • 上面示例声明了变量c是字符类型,并将其赋值为字母B
  • C 语言规定,字符常量必须放在单引号里面。
  • 在计算机内部,字符类型使用一个字节(8位)存储。C 语言将其当作整数处理,所以字符类型就是宽度为一个字节的整数。每个字符对应一个整数(由 ASCII 码确定),比如B对应整数66
  • 字符类型在不同计算机的默认范围是不一样的。一些系统默认为-128127,另一些系统默认为0255。这两种范围正好都能覆盖0127的 ASCII 字符范围。
  • 只要在字符类型的范围之内,整数与字符是可以互换的,都可以赋值给字符类型的变量。
    char c = 66;
    // 等同于
    char c = 'B';
    
  • 上面示例中,变量c是字符类型,赋给它的值是整数66。这跟赋值为字符B的效果是一样的。
  • 两个字符类型的变量可以进行数学运算。
    char a = 'B'; // 等同于 char a = 66;
    char b = 'C'; // 等同于 char b = 67;
    printf("%d\n", a + b); // 输出 133
    
  • 上面示例中,字符类型变量ab相加,视同两个整数相加。占位符%d表示输出十进制整数,因此输出结果为133。
  • 单引号本身也是一个字符,如果要表示这个字符常量,必须使用反斜杠转义。
    char t = '\'';
    
  • 上面示例中,变量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;
    
  • 上面示例声明了一个整数变量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 类型的最小值-128SCHAR_MAX代表 signed char 类型的最大值127
  • 为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用这些常量。
    • SCHAR_MINSCHAR_MAX:signed char 的最小值和最大值。
    • SHRT_MINSHRT_MAX:short 的最小值和最大值。
    • INT_MININT_MAX:int 的最小值和最大值。
    • LONG_MINLONG_MAX:long 的最小值和最大值。
    • LLONG_MINLLONG_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 * b^e^ 的形式,存储一个数值,m是小数部分,b是基数(通常是2),e是指数部分。这种形式是精度和数值范围的一种结合,可以表示非常大或者非常小的数。
  • 浮点数的类型声明使用float关键字,可以用来声明浮点数变量。
    float c = 10.5;
    
  • 上面示例中,变量c的就是浮点数类型。
  • float类型占用4个字节(32位),其中8位存放指数的值和符号,剩下24位存放小数的值和符号。float类型至少能够提供(十进制的)6位有效数字,指数部分的范围为(十进制的)-3737,即数值范围为10^-37^ 到10^37^ 。
  • 有时候,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;
    
  • 上面代码中,字面量123有后缀L,编译器就会将其指定为long类型。这里123L写成123l,效果也是一样的,但是建议优先使用L,因为小写的l容易跟数字1混淆。
  • 八进制和十六进制的值,也可以使用后缀lL指定为 Long 类型,比如020L0x20L
    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
    
  • 总结一下,常用的字面量后缀有下面这些。
    • fFfloat类型。
    • lL:对于整数是long int类型,对于小数是long double类型。
    • llLL:Long Long 类型,比如3LL
    • uU:表示unsigned int,比如15U0377U
  • u还可以与其他整数后缀结合,放在前面或后面都可以,比如10UL10ULL10LLU都是合法的。
  • 下面是一些示例。
    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)。
  • 一般来说,编译器不会对溢出报错,会正常执行代码,但是会忽略多出来的二进制位,只保留剩下的位,这样往往会得到意想不到的结果。所以,应该避免溢出。
    unsigned char x = 255;
    x = x + 1;
    printf("%d\n", x); // 0
    
  • 上面示例中,变量x1,得到的结果不是256,而是0。因为xunsign 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_MAX是 unsigned 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);
    // 参数为变量
    int i;
    sizeof(i);
    // 参数为数值
    sizeof(3.14);
    
  • 上面的第一个示例,返回得到int类型占用的字节数量(通常是48)。第二个示例返回整数变量占用字节数量,结果与前一个示例完全一样。第三个示例返回浮点数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类型的值。
    printf("%zd\n", sizeof(int));
    
  • 上面代码中,不管sizeof返回值的类型是什么,%zd占位符(或%zu)都可以正确输出。
  • 如果当前系统不支持%zd%zu,可使用%u(unsigned int)或%lu(unsigned long int)代替。

类型的自动转换

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

赋值运算

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

  • 浮点数赋值给整数变量
    • 浮点数赋予整数变量时,C 语言直接丢弃小数部分,而不是四舍五入。
      int x = 3.14;
      
    • 上面示例中,变量x是整数类型,赋给它的值是一个浮点数。编译器会自动把3.14先转为int类型,丢弃小数部分,再赋值给x,因此x的值是3
    • 这种自动转换会导致部分数据的丢失(3.14丢失了小数部分),所以最好不要跨类型赋值,尽量保证变量与所要赋予的值是同一个类型。
    • 注意,舍弃小数部分时,不是四舍五入,而是整个舍弃。
      int x = 12.99;
      
    • 上面示例中,x等于12,而不是四舍五入的13
  • 整数赋值给浮点数变量
    • 整数赋值给浮点数变量时,会自动转为浮点数。
      float y = 12 * 2;
      
    • 上面示例中,变量y的值不是24,而是24.0,因为等号右边的整数自动转为了浮点数。
  • 窄类型赋值给宽类型
    • 字节宽度较小的整数类型,赋值给字节宽度较大的整数变量时,会发生类型提升,即窄类型自动转为宽类型。
    • 比如,charshort类型赋值给int类型,会自动提升为int
      char x = 10;
      int i = x + y;
      
    • 上面示例中,变量x的类型是char,由于赋值给int类型,所以会自动提升为int
  • 宽类型赋值给窄类型
    • 字节宽度较大的类型,赋值给字节宽度较小的变量时,会发生类型降级,自动转为后者的类型。这时可能会发生截值(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的小数部分被截去了。

混合类型的运算

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

  • 整数与浮点数混合运算时,整数转为浮点数类型,与另一个运算数类型相同。
    3 + 1.2 // 4.2
    
    • 上面示例是int类型与float类型的混合计算,int类型的3会先转成float3.0,再进行计算,得到4.2
  • 不同的浮点数类型混合运算时,宽度较小的类型转为宽度较大的类型,比如float转为doubledouble转为long double
  • 不同的整数类型混合运算时,宽度较小的类型会提升为宽度较大的类型。比如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");
    
  • 上面示例中,变量a是 unsigned char 类型,这个类型不可能小于0,但是-a会自动转为 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(a, b);
    
  • 上面示例中,参数变量ab不管原来的类型是什么,都会转成函数dostuff()定义的参数类型。
  • 下面是返回值自动转换类型的例子。
    char func(void) {
      int a = 42;
      return a;
    }
    
  • 上面示例中,函数内部的变量aint类型,但是返回的值是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创造了一些新的类型别名。
  • 精确宽度类型(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位的宽度。
  • 最小宽度类型(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位有符号整数的最小宽度的类型。
  • 最快的最小宽度类型(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位数据。
  • 可以保存指针的整数类型。
    • intptr_t:可以存储指针(内存地址)的有符号整数类型。
    • uintptr_t:可以存储指针的无符号整数类型。
  • 最大宽度整数类型,用于存放最大的整数。
    • intmax_t:可以存储任何有效的有符号整数的类型。
    • uintmax_t:可以存放任何有效的无符号整数的类型。
    • 上面的这两个类型的宽度比long longunsigned long更大。
下一节:指针是 C 语言最重要的概念之一,也是最难理解的概念之一。