| 目录 |

37 定宽整数类型

C 有各种小的、大一点的和最大的整数类型,比如 int、long 之类。你可以查看讲限制的那一节,看看用 INT_MAX 等宏能得到的最大的 int 是多少。

这些类型有多大?也就是说,它们占多少个字节?我们可以用 sizeof 得到答案。

但如果我想反过来呢?如果我需要一个正好 32 位(4 字节)的类型,或者至少 16 位之类的类型呢?

我们怎样才能声明一个特定大小的类型呢?

头文件 <stdint.h> 给了我们办法。

37.1 指定位宽的类型

无论有符号还是无符号整数,我们都能指定一个特定位数的类型,当然,这有一些注意事项。

这些类型主要有三大类(在这些示例中,N 会替换成具体的位数):

fast 到底快多少?肯定也许快了那么一些吧。大概吧。规范并没有说快多少,只说它们在这套架构上是最快的。不过大多数 C 编译器都相当不错,所以你大概只会在需要保证尽可能快的场合才见到它(而不是只指望编译器生成相当快的代码——而它确实能做到)。

最后,这些无符号类型会在前面加一个 u 来区分。

例如,这些类型分别具有如下含义:

int32_t w;        // w 恰好是 32 位,有符号
uint16_t x;       // x 恰好是 16 位,无符号

int_least8_t y;   // y 至少是 8 位,有符号

uint_fast64_t z;  // z 是最快的表示形式,至少 64 位,无符号

以下类型保证会被定义:

int_least8_t      uint_least8_t
int_least16_t     uint_least16_t
int_least32_t     uint_least32_t
int_least64_t     uint_least64_t

int_fast8_t       uint_fast8_t
int_fast16_t      uint_fast16_t
int_fast32_t      uint_fast32_t
int_fast64_t      uint_fast64_t

也可能还有其他不同宽度的类型,但那些都是可选的。

嘿!像 int16_t 这样的定宽类型哪儿去了?原来它们完全是可选的……除非满足某些条件189。如果你用的是普普通通的现代计算机系统,那些条件多半是满足的。而如果满足,你就会有这些类型:

int8_t      uint8_t
int16_t     uint16_t
int32_t     uint32_t
int64_t     uint64_t

其他不同宽度的变体也可能被定义,但它们是可选的。

37.2 最大整数类型

有一种类型可以保存系统上能表示的最大整数,有符号和无符号的都有:

intmax_t
uintmax_t

想尽可能取大值时,就用这些类型。

显然,同符号的任何其他整数类型的值都必然能放进这个类型。

37.3 使用定宽常量

如果你有一个常量,希望它能放进特定的位数里,可以用这些宏自动给数字加上合适的后缀(例如 22L 或 3490ULL)。

INT8_C(x)     UINT8_C(x)
INT16_C(x)    UINT16_C(x)
INT32_C(x)    UINT32_C(x)
INT64_C(x)    UINT64_C(x)
INTMAX_C(x)   UINTMAX_C(x)

再次强调,这些宏只对整数常量值有效。

例如,我们可以用其中一个宏像这样给常量赋值:

uint16_t x = UINT16_C(12);
intmax_t y = INTMAX_C(3490);

37.4 定宽整数的限制

我们还定义了一些限制,让你能取得这些类型的最大值和最小值:

INT8_MAX           INT8_MIN           UINT8_MAX
INT16_MAX          INT16_MIN          UINT16_MAX
INT32_MAX          INT32_MIN          UINT32_MAX
INT64_MAX          INT64_MIN          UINT64_MAX

INT_LEAST8_MAX     INT_LEAST8_MIN     UINT_LEAST8_MAX
INT_LEAST16_MAX    INT_LEAST16_MIN    UINT_LEAST16_MAX
INT_LEAST32_MAX    INT_LEAST32_MIN    UINT_LEAST32_MAX
INT_LEAST64_MAX    INT_LEAST64_MIN    UINT_LEAST64_MAX

INT_FAST8_MAX      INT_FAST8_MIN      UINT_FAST8_MAX
INT_FAST16_MAX     INT_FAST16_MIN     UINT_FAST16_MAX
INT_FAST32_MAX     INT_FAST32_MIN     UINT_FAST32_MAX
INT_FAST64_MAX     INT_FAST64_MIN     UINT_FAST64_MAX

INTMAX_MAX         INTMAX_MIN         UINTMAX_MAX

注意所有无符号类型的 MIN 都是 0,因此也就不存在对应的宏。

37.5 格式说明符

要打印这些类型,必须给 printf() 传正确的格式说明符。(用 scanf() 获取输入时也有同样的问题。)

但你怎么知道这些类型在底层是多大的呢?幸运的是,C 又一次提供了一些宏来帮忙。

所有这些都在 <inttypes.h> 里。

现在我们有了一大堆宏,简直是宏的复杂度爆炸。所以我就不再逐一列出了,只在本该根据你的需要填 8、16、32 或 64 的地方放一个小写字母 n。

来看看用于打印有符号整数的宏:

PRIdn    PRIdLEASTn    PRIdFASTn    PRIdMAX
PRIin    PRIiLEASTn    PRIiFASTn    PRIiMAX

留意其中的规律。你能看到定宽、least、fast 和 max 几种类型的变体。

此外还有小写的 d 和小写的 i,它们分别对应 printf() 的格式说明符 %d 和 %i。

所以如果我有一个如下类型的值:

int_least16_t x = 3490;

我就可以用 PRIdLEAST16,以等价于 %d 的格式说明符把它打印出来。

但怎么做?我们究竟该怎么用那个宏?

首先,那个宏给出的字符串里,包含 printf() 打印该类型时需要用到的那个或那几个字母。比如,它可能是 "d" 或 "ld"。

所以我们只要把它嵌进传给 printf() 调用的格式字符串里就行了。

为此,我们可以利用 C 的一个你也许已经忘了的事实:相邻的字符串字面量会自动拼接成单个字符串。例如:

printf("Hello, " "world!\n");   // 打印 "Hello, world!"

既然这些宏都是字符串字面量,我们就可以像这样使用它们:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void)
{
    int_least16_t x = 3490;

    printf("The value is %" PRIdLEAST16 "!\n", x);
}

我们还有一大堆用于打印无符号类型的宏:

PRIon    PRIoLEASTn    PRIoFASTn    PRIoMAX
PRIun    PRIuLEASTn    PRIuFASTn    PRIuMAX
PRIxn    PRIxLEASTn    PRIxFASTn    PRIxMAX
PRIXn    PRIXLEASTn    PRIXFASTn    PRIXMAX

在这里,o、u、x 和 X 分别对应 printf() 文档中说明的格式说明符。

和之前一样,其中的小写 n 应替换为 8、16、32 或 64。

可就在你以为宏已经够多的时候,原来我们还有一整套与之配套的宏,专供 scanf() 使用!

SCNdn    SCNdLEASTn    SCNdFASTn    SCNdMAX
SCNin    SCNiLEASTn    SCNiFASTn    SCNiMAX
SCNon    SCNoLEASTn    SCNoFASTn    SCNoMAX
SCNun    SCNuLEASTn    SCNuFASTn    SCNuMAX
SCNxn    SCNxLEASTn    SCNxFASTn    SCNxMAX

记住:当你想用 printf() 或 scanf() 打印一个固定大小的整数类型时,要从 <inttypes.h> 里取出对应的正确格式说明符。


| 目录 |