C 语言中数据类型及类型转换

  • 操作数本身没有数据类型,数据类型取决于指令

C 语言的整型变量及取值范围

NOTE

无论是有符号数还是无符号数,C 语言并不检测数据在加、减、乘等运算中产生的溢出现象。 溢出由程序员编写的应用程序进行判断和检查。

  • 基本整数类型:
    • char:字符类型,通常占用 1 字节。其有无符号性由编译器决定,可显式声明为 signed charunsigned char
    • short [int]:短整型,通常占用 2 字节。
    • int:整型,通常占用 4 字节,是 C 语言中最常用的整数类型。
    • long [int]:长整型,通常占用 4 字节或 8 字节(在 64 位系统上通常为 8 字节)。
    • `long long [int] 整型,C99 标准引入,至少占用 8 字节。
  • 有符号与无符号:
    • 除了 char 类型外,其他整型默认均为有符号类型(signed)。例如 int 等同于 signed int
    • 通过 unsigned 关键字可声明为无符号类型,例如 unsigned int。无符号数只能表示非负值,其所有位都用于表示数值。
    • 有符号数通常采用补码形式表示。最高位为符号位(0 表示正数,1 表示负数)。
  • 各类型典型大小 (字节) 及取值范围 (具体取决于系统和编译器):
类型典型大小 (字节)范围 (示例)
char1[-128, 127] 或 [0, 255]
short2[-32768, 32767]
int4[-2147483648, 2147483647]
long4 或 8[-2147483648, 2147483647] 或 [-9E18, 9E18] (约)
long long8[-9E18, 9E18] (约)
unsigned char1[0, 255]
unsigned int4[0, 4294967295]
  • 特殊整数类型:
    • size_tsizeof 运算符的结果类型,为无符号整型,用于表示内存大小或数组索引。
    • ptrdiff_t:两个指针相减的结果类型,为有符号整型。

不同字长整数之间的转换

  • 相同字长之间的转换:
    • 保持机器码不变
    • 示例: char c = -127; unsigned char uc = (unsigned char) c;。转换后机器码不变,符号位被当作数值位uc = 129
  • 小字长向大字长转换 (整型提升/拓宽转换):
    • 当较小的整型(如 charshort)用于表达式时,它们通常会被自动转换为 intunsigned int(通常为 int,除非原类型的值无法用 int 表示)。这有助于提高 CPU 运算效率。
    • 有符号数: 进行符号位扩展 (Sign Extension)。新分配的高位字节会填充原数值的符号位(如果原符号位是 0,则填充 0;如果原符号位是 1,则填充 1)。
    • 无符号数: 进行零扩展 (Zero Extension)。新分配的高位字节会全部填充 0。
  • 大字长向小字长转换 (截断转换):
    • 当一个大字长的整数赋值给一个小字长的整数类型时,会发生截断
    • 转换结果: 高位字节会被直接丢弃,只保留低位字节。
    • 潜在问题:
      • 数据丢失/溢出: 如果原数值超出了目标类型所能表示的范围,则会发生数据丢失,导致值改变或溢出。
      • 符号改变: 对于有符号数,截断后最高位可能变为 1,导致正数变为负数,或负数变为一个完全不同的值。
    • 示例: int i = 257; char c = (char)i;。由于 257 的二进制表示是 ...00000001 00000001 (假设 int 4 字节),截断为 char (1 字节) 后,只保留低 8 位 00000001,因此 c 的值为 1。

有符号数和无符号数转换

  • 隐式转换规则:
    • 当有符号数与无符号数在表达式中混合运算时,C 语言会执行整型提升,然后将有符号数隐式转换为无符号数
    • 转换的前提是无符号数的类型不小于有符号数的类型。如果无符号数类型较小,则会先进行整型提升,再进行有符号 - 无符号转换。
    • 转换结果: 转换过程中,数的位模式保持不变,但其解释方式会发生改变。
  • 转换影响:
    • 正数转换为无符号数: 值保持不变。
    • 负数转换为无符号数: 负数的补码表示会被解释为一个大的正数。例如,-1 (int) 转换为 unsigned int 会得到该类型所能表示的最大值 (所有位均为 1)。这可能导致非预期的逻辑错误或比较结果。
  • 显式转换 (强制类型转换):
    • 可以通过 (unsigned type) expression 的形式进行显式强制类型转换,其行为与隐式转换相同,但代码意图更明确。

int、float、double 之间的转换

  • 上述 3 种类型的机器码并不相同(int 位 32 位有符号整数,用补码表示;float 和 double 分别是 32 位和 64 位浮点数,阶码用移码,尾数用原码表示)
  • 上述 3 种类型的表示范围和精度也不相同
  • 因此转换过程编译器只能保证数值尽量相等,大多数情况下只是近似值
转换类型转换类别特点/注意事项
float double拓宽转换 (Widening)- float 的有效位 (24 位) 可被 double (53 位) 完全保留,尾数部分通过在低位补零来填充。
- 不会发生精度丢失float 能表示的任何值,double 都能精确表示。
double float窄化转换 (Narrowing)- 存在精度丢失风险double 尾数截断或舍入(通常四舍五入到偶数)。
- 存在溢出风险:如果 double 值超出 float 范围,结果可能为 +/- Infinity
- 存在下溢风险:如果 double 值太小且超出 float 最小正数,结果可能为 +/- 0.0
double/float int窄化转换 (Narrowing)- 截断小数部分:总是向零取整(例如 3.7 3, -3.7 -3)。
- 溢出风险:如果浮点数绝对值超出 int 范围,结果未定义(通常为 int 最大/最小值)。
- NaN 或 Infinity:转换为 int 也是未定义行为(通常为 int 最大/最小值)。
int float拓宽转换 (Widening)- int 的所有值都在 float 表示范围内。
- 可能发生精度丢失:当 int 绝对值 大于 (16777216) 时,int 值可能无法被 float 精确表示,会发生舍入。
int double拓宽转换 (Widening)- int 的所有值都在 double 表示范围内。
- 不会发生精度丢失double 的尾数有效位 (53 位) 远大于 int 的位数 (32 位),所有 int 值都可以被 double 精确表示