Menu

C 语言数据类型:int、float、char、double 等

C 语言中的每个变量都有一个类型,它决定了变量的大小和能存放什么。本文介绍所有基本类型、它们的实际大小、有符号与无符号、sizeof、limits.h,以及溢出时会发生什么。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

C 语言中的每个变量都有一个类型,在声明时选定,并在整个生命周期内保持不变。类型决定了三件事:变量占多少字节,这些字节如何解读,以及对它进行哪些操作才有意义。

C 语言的类型列表很短,复杂之处在于修饰符。

四种基础类型

  • char 占一个字节。它用于单个字符('A')、字符串中的字节,有时也当作很小的整数使用。
  • int 是最常用的整数类型。循环计数器、大小、ID 都用它。
  • float 和 double 存放实数。double 的精度大约是 float 的两倍。
  • void 在某种意义上是第四种类型,但它表示“没有值”:你不能声明 void 变量,只能把它用作函数返回类型、空参数列表或无类型指针。

注意 3.14f 末尾的 f 后缀。没有它,这个字面量就是 double,把它赋给 float 会悄悄地损失精度。有些编译器会对此发出警告。

修饰符:short、long、unsigned

基础整数类型可以搭配修饰符,用来改变大小或符号。

每种整数类型都需要自己的格式说明符:short 用 %hd,int 用 %d,long 用 %ld,long long 用 %lld,unsigned 用 %u。用错了是未定义行为,而不是舍入误差。

unsigned 去掉符号位,使正数范围扩大一倍。unsigned int 能存放 0 到约 43亿,而不是 -21亿到 +21亿。对于确实不可能为负的东西(大小、字节数、位模式),它是正确的选择;而对于任何可能被减到零以下的值,它就是一个陷阱。

int、short、long 和 long long 默认都是 signed,所以你几乎从不写它。唯一需要注意的是 char:普通的 char 是有符号还是无符号,由实现定义,所以当符号很重要时,请写 signed char 或 unsigned char。

大小:你实际得到的是什么

C 标准规定的是最小值,而不是确切的大小。在任何现代 64 位 Linux、macOS 或 Windows 机器上,你会看到这样的结果:

类型常见大小常见范围
char1 字节-128 至 127(或 0 至 255)
short2 字节-32,768 至 32,767
int4 字节-2,147,483,648 至 2,147,483,647
long8 字节(Windows 上为 4)约 ±9.2×10¹⁸
long long8 字节约 ±9.2×10¹⁸
float4 字节约 7 位有效数字
double8 字节约 15 位有效数字
long double16 字节(因平台而异)比 double 更多

最容易让人上当的是 long 这一行:它在 Linux 和 macOS 上是 8 字节,在 64 位 Windows 上是 4 字节。假定 long 能存放 64 位值的代码是不可移植的。当大小本身就是需求的一部分时,请使用 long long,或者 stdint.h 中的定宽类型(int32_t、uint64_t)。

sizeof:问编译器

永远不要猜测大小,去测量它:

sizeof 是运算符而不是函数,它在编译时求值。它的结果是 size_t 类型,用 %zu 打印。

sizeof(char) 保证恰好是 1,这就是 C 语言中“字节”的定义。其他所有类型的大小都以它为单位来衡量。

limits.h 和 float.h

你的编译器的确切范围都以具名常量的形式提供:

当你需要知道某个运算会不会溢出时,就和这些值比较。在计算 a + b 之前先检查 if (a > INT_MAX - b),就能在溢出发生之前发现它。这一点很重要,因为有符号溢出发生之后是无法检测的。

整数溢出

当值超出类型的范围时会发生什么,完全取决于它的符号。

无符号溢出是有定义的:值按 2^N 取模回绕。

第二种情况是 bug 的真正来源。像 for (unsigned i = n - 1; i >= 0; i--) 这样的循环永远不会结束,因为无符号值永远 >= 0。

有符号溢出是未定义行为。 不是“回绕”,而是未定义。编译器可以假定它永远不会发生,并据此进行优化,这意味着事后才写的溢出检查可能会被删掉:

int sum = a + b;
if (sum < a) { /* the compiler may remove this entirely */ }

要事先检查,借助这些范围常量:

if (b > 0 && a > INT_MAX - b) {
    /* a + b would overflow - handle it */
}

浮点精度

float 和 double 以二进制存储数字,而大多数十进制小数都没有精确的二进制形式,就像 1/3 没有精确的十进制形式一样。

由此得出的规则是:永远不要用 == 比较浮点数。改为比较差的绝对值是否小于一个很小的容差。

也永远不要用浮点数表示金额。把分存成整数:用 long long 存放的分是精确的,用 double 存放的元却不是。

选择类型

一份涵盖大部分代码的简短决策清单:

  • 整数:没有特别理由就用 int。它是 CPU 处理起来最高效的类型,所有算术规则也都围绕它建立。
  • 超过 20亿的数:long long,或 stdint.h 中的 int64_t。
  • 来自 sizeof 或 strlen 的大小、长度、数组下标:size_t。它是无符号的,并保证足以表示任何对象的大小。
  • 小数:double。只有在大数组中需要节省一半内存,或者在没有双精度运算单元的嵌入式硬件上,才用 float。
  • 单个字符和原始字节:文本用 char,二进制数据用 unsigned char。
  • 真/假:stdbool.h 中的 bool,参见 C 语言布尔值。
  • 确切的位宽(文件格式、网络协议、硬件寄存器):stdint.h 中的 uint8_t、int16_t、uint32_t 等。

混合类型

当你在一个表达式中组合两种不同的类型时,C 语言会在做算术运算之前悄悄地转换它们。这通常有帮助,偶尔却是灾难:

第一个是整数除法:两个操作数都是 int,所以结果也是 int,小数部分被丢掉了。第三个更糟:把有符号值和无符号值比较时,有符号值会被转换成无符号值,于是 -1 变成了一个巨大的正数。

这些转换规则,以及如何用显式类型转换掌控它们,是 C 语言类型转换要讲的内容。

常见问题

C 语言有哪些基本数据类型?

四种基础类型:char 用于单个字符和字节,int 用于整数,float 和 double 用于小数。修饰符会改变它们的大小和符号(short、long、long long、signed 和 unsigned),从而组合出完整的类型集合。

C 语言中 int 占几个字节?

在现代桌面和服务器系统上几乎都是 4 字节(32 位),范围大约是 -21亿到 21亿。标准只保证至少 2 字节,16 位单片机上确实就是 2 字节。如果需要确切知道,请使用 sizeof(int)。

C 语言中 float 和 double 有什么区别?

float 占 4 字节,大约有 7 位有效十进制数字;double 占 8 字节,大约有 15 位。浮点字面量和数学函数默认使用 double,除非你要存放数百万个值或者面向嵌入式芯片,否则 double 才是正确的选择。

C 语言中 int 溢出会发生什么?

对于有符号 int,溢出是未定义行为:编译器可能让它回绕、饱和,或者干脆把检查代码优化掉。对于无符号 int,溢出有完全确定的定义:值按 2^N 取模回绕,所以 UINT_MAX + 1 等于 0。永远不要依赖有符号溢出。

Coddy 编程语言插图

和 Coddy 一起学编程

开始