Skip to content

C语言中的数据类型与转换

2026 大纲 二(三)整数的表示和运算(1 无符号整数、2 带符号整数);整型与浮点型之间的转换涉及 二(四)1 浮点数的表示

位串没变,变的是读它的那条指令

寄存器和内存单元里只有 0 和 1,类型不是数据本身的属性,而是一种解释方式。同样一串 8 位的 10000000,按无符号读是 128,按补码读是 128——位一个没动,动的是编译器生成的那条指令

这一句是本篇所有题目的总钥匙。类型转换题看起来花样很多(int 转 short、short 转 unsigned int、unsigned short 转 short……),但它们全都在问同一件事:这串位有没有被改动?改动之后又是按谁的规则读的?

拆开就是两步,而且顺序不能反:

🔴 补位问出身,读数问去向。 宽度与符号性同时变化时:第一步按"源类型"的符号性把位串扩展或截断到目标宽度,第二步按"目标类型"的符号性去解释这串位。 顺序反了,答案会差出几十亿。

两步各自的规则很短。扩展:源类型有符号就做符号扩展(高位全填符号位),源类型无符号就做零扩展(高位全填 0),两者都保值。截断:直接丢弃高位只留低位,数值上等价于对 2n 取模,再按目标类型的符号性解释这个余数——丢掉的位再也回不来,而且不会触发任何错误提示

有一个很稳的自查点:

🔴 (unsigned)(-1) 恒等于该类型的最大值。 1 的补码是全 1,全 1 按无符号读就是 2n1。任何位宽下都成立,算完拿它验一下能挡掉不少低级错。

一、位串本身没有类型

类型编码方式8 位范围
无符号整数纯二进制,所有位都是数值位0255
有符号整数补码,最高位是符号位128127

现代计算机的有符号整数一律用补码,理由在 真值与机器数:零唯一、加减法统一为加法、比原码多表示一个最小负数。

C 语言整数类型典型位宽有符号范围无符号范围
char81281270255
short163276832767065535
int32231231102321
long32/64取决于平台取决于平台

未写 unsigned 则默认为有符号类型。位宽随机器和编译器不同而不同——long 在 32 位机上通常是 4 字节、64 位机上通常是 8 字节,与机器字长一致;指针的宽度也等于机器字长

二、相同字长与变宽、变窄

c
short x = -4321;                 // 0xEF1F
unsigned short y = (unsigned short)x;   // 位模式不变,值 = 61215

int   y2 = x;              // 源类型有符号 → 符号扩展 → 0xFFFFEF1F,值仍为 -4321
unsigned short u = 61215;  // 0xEF1F
unsigned int   v = u;      // 源类型无符号 → 零扩展   → 0x0000EF1F,值仍为 61215

int   n = 165537;          // 0x000286A1
short m = (short)n;        // 截断为 0x86A1,按 short 读 = -31071

165537 超出 short 的范围,截断后语义完全改变而不报错165537mod65536=34465=0x86A1,最高位为 1,按 short 读就是 3446565536=31071

🔴 顺带定一条判据:强制转换到底会不会改内存里的位?整型之间同宽度不会(只换解释)、变宽会(添了高位)、变窄会(丢了高位);整型与浮点型互转一定会(重新编码,位模式完全无关)。判据是编码方式变没变——没变就是纯解释,一旦跨越"整数编码"与"浮点编码",就是真的重算了一遍。

符号扩展为什么保值:一行代数(想弄清"复制符号位"凭什么不改变数值时展开)

用补码的按权展开就能一步看出来。n 位补码:

X=bn12n1+i=0n2bi2i

扩展到 n+1 位、把符号位复制一份:

X=bn12n+bn12n1+i=0n2bi2i=bn12n1+i=0n2bi2i=X

关键在于 2n+2n1=2n1新添的负权和被顶上去的旧符号位正好抵消回原来的负权。 复制多少份都一样,所以从 8 位扩到 32 位可以一次补完。

零扩展保值的理由更直白:无符号数的高位权都是正的,填 0 什么也没加。

三、宽度和符号性同时变

这是最容易出错的一种,因为两件事同时发生,而顺序是固定的

规则:扩展按"源类型"的符号性做,解释按"目标类型"来

按源类型符号性扩展/截断到目标宽度第一步按目标类型的符号性解释这串位第二步

先看源类型决定怎么补位,再看目标类型决定怎么读。

c
short si = -32767;
unsigned int ui = si;
做什么结果
0si 是 short,32767 的 16 位补码0x8001
1源类型 short 是有符号 符号扩展到 32 位0xFFFF8001
2目标类型 unsigned int 无符号解释这 32 位23232767=4294934529

顺序反了会怎样:若先按无符号解释再扩展,把 si 当 unsigned short 读成 21632767=32769,再零扩展仍得 32769,与正确结果相差 40 亿。扩展的依据永远是源类型的声明,与目标类型无关。(另需留意 32767=(2151),故结果是 232215+1,不是 2322151。)

长转短再转回来会发生什么,以及同一串位为什么会被补成两种样子(想确认多一次显式转换到底改变了什么时展开)

反向的例子(长转短再转回来):

c
int   i  = 32777;     // 0x00008009
short si = i;         // 截断低 16 位 → 0x8009,按 short 读 = -32759
int   j  = si;        // si 是 short(有符号)→ 符号扩展 → 0xFFFF8009 = -32759

j=32759,不是 32777——截断丢掉的高位回不来了,而截断后最高位恰好是 1,符号扩展只会把它当负数补回去。

再看同一串位的两种补法(32 位机器):

c
short        x = -4321;
unsigned int y = x;
unsigned int z = (unsigned short)x;

x 的 16 位补码:655364321=61215=0xEF1F

  • y:源类型是 short有符号 符号扩展到 32 位 0xFFFFEF1F,按无符号读 =2324321=4294962975
  • z:先被显式转成 unsigned short(位模式不变,值变为 61215),此时源类型是无符号 零扩展 0x0000EF1F =61215

两者相差 232216多一次显式转换,改变的不是位串,而是扩展时依据的符号性。

同一现象换个写法(源类型直接声明成无符号):

写法源类型符号性扩展方式结果
short si = -32767;有符号符号扩展232215+1
unsigned short us = -32767;无符号零扩展216215+1=32769

四、整型与浮点型之间

转换方向会发生什么根源
int → float不溢出,但可能丢精度int 最多 31 位有效数字 > float 的 24 位
int → double安全double 有 53 位有效位,覆盖 32 位 int
float → double安全范围和精度都只增不减
double → float可能溢出,也可能丢精度范围更小、有效位更少
float/double → int小数部分向零截断,可能溢出int 没有小数部分,范围也更小

int 与 float 都是 32 位、位数没少,但分配方式变了:float 拿出 8 位去存量级,只剩 24 位有效数字。换来的是范围——范围和精度的这笔交易,就是浮点格式的本质。

🔴 int → float 丢的是精度,不是范围。 float 的范围(约 1038)远大于 int(约 2×109),任何 int 都落在里面;但 int 的有效位可达 31 位而 float 只有 24 位,所以必须舍入。反过来 float → int 两种问题都有:小数部分被截断、大数值放不进。

🔴 浮点转整型是"向零取整",不是四舍五入,也不是向下取整。 1.999911.99991。负数这一侧特别要留意:向零截断得到的 1 比原值;若按"向下取整"应该得 2

往返转换是否无损,只看中间那个类型能不能同时装下源类型的范围与有效位数(int)(double)i 恒等于 i(53 位 > 32 位);(int)(float)i 不恒等(只有 24 位);(float)(double)f 恒等(double 是 float 的超集);(float)(int)f 不恒等(int 没有小数部分)。另外 f == -(-f) 恒成立——浮点取负只翻转符号位 S,尾数和阶码一位不动。

三行代码看清"32 位换 32 位却丢东西"(想要一个能自己跑一遍的证据时展开)
c
int   n = 0x7FFFFFFF;   // 2147483647,有效位 31 位
float f = (float)n;     // 只装得下 24 位有效数字,必须舍入
int   m = (int)f;       // m 不等于 n

n 用满了 31 位有效数字,而 float 的尾数只有 24 位有效位,转换时低位必须舍入丢掉;再转回 int 时,丢掉的那几位无从恢复,所以 mnfloat 能表示 1038 量级、int 只到 2×109,多出来的范围正是拿有效位数换的。

五、混合运算:无符号会"传染"

C 语言规定:同一个表达式里同时出现无符号数和带符号数时,带符号数被转换成无符号数,整个比较/运算按无符号进行。后果相当反直觉:

c
int x = -1;
if (x < 0u) { /* 不会执行 */ }

1 被当成 2321,比 0 大得多。同理,用 unsigned 做循环变量再写 i >= 0 会成为永真条件,循环无法退出。

隐式类型提升顺序charintlongdoublefloat,低类型自动向高类型转换。注意这条链讲的是宽度/种类的提升,与"无符号优先"是两回事。

六、判据:怎么判断一个表达式会不会按无符号算

完整判断只有三步,按顺序走:

  1. 先做整型提升:比 int 窄的类型(charshort无论带不带 unsigned)一律先升成 int。因为 int 装得下 unsigned short 的全部取值(065535<231),升完两边都是 int根本走不到第 2 步
  2. 提升后比宽度:无符号的那一方不窄于带符号的一方时,带符号数转成无符号——"传染"只在这一格发生。
  3. 带符号的一方更宽时:它装得下无符号方的全部取值,于是把无符号数转成这个更宽的带符号类型,整个表达式按带符号算。
表达式(32 位 int)提升后两边的类型按什么算结果
-1 < 0uint vs unsigned int(等宽)无符号
-1 < (unsigned short)0int vs int(第 1 步就化解了)带符号
-1 < 0ullong 为 64 位)long vs unsigned long(等宽)无符号
-1L < 0ulong 为 64 位)long vs long(第 3 步,64 位装得下 32 位无符号)带符号

🔴 所以"只要出现 unsigned 就按无符号算"是半对的话——宽度不同时它是错的。标准之所以选"带符号转无符号"这一边,是因为这样位串不动;反过来把无符号转成同宽度的带符号,超过 2n1 的那一半会变成负数、信息彻底错乱。它只保证行为确定,不保证符合直觉。

⚠️ 最后记一下哪些写法会悄悄把无符号带进来:字面量后缀 u / U / ul;声明为 unsigned 的变量;以及最隐蔽的 sizeof——它的返回类型是 size_t,无符号,所以 int i = -1; if (i < sizeof(x)) 恒为。这三样都没出现,表达式就一律按带符号算。

考点速记

  1. 类型是解释方式而不是数据属性;同宽度转换只换解释(u=x+2n),变宽按源类型的符号性做符号扩展或零扩展,变窄截断高位且不报错
  2. 宽度与符号性同时变时顺序固定——补位问出身,读数问去向:先按源类型符号性扩展/截断,再按目标类型解释。
  3. int → float 不溢出但可能丢精度(24 位有效位),浮点 → int 向零截断且可能溢出;混合运算按三步判据走:先整型提升 → 等宽时带符号转无符号 → 带符号更宽时按带符号算sizeof 返回无符号是最隐蔽的传染源。

这一节在真题里被考过的形式(下方「真题练习」里属于本篇的那几道,本章题量最集中的一篇):

  • 窄的无符号转宽的无符号(如 unsigned shortunsigned int):源类型无符号 ⇒ 零扩展,高位补 0,值不变。
  • 窄的带符号转宽的无符号(如 shortunsigned int):先按源类型符号扩展到 32 位,再按无符号解释这串位。32767 先扩成 FFFF8001H,按无符号读就是 232215+1。⚠️ 这一道最能验证"补位问出身、读数问去向"有没有真的掌握——反过来做会得到完全不同的数。
  • 同宽度换符号性shortunsigned short):位模式一个不动,只换解释。32767 按无符号读是 3276965535 按带符号读是 1
  • 宽转窄再转回来intshortint):先截断只留低 16 位,再按 short 有符号扩展回 32 位。32777 截断后最高位为 1,回来时被符号扩展成负数。丢掉的位回不来
  • 不同类型混合运算intshort 相加赋给 int):short 先整型提升为 int(符号扩展),再按 int 相加,结果按 int 存。
  • 同一机器数按 int 或 float 解释,问值可能是多少:这是"位串没有类型"的直接考法——把同一串位分别按补码和 IEEE 754 各读一遍,看哪个选项对得上。

易错:把"扩展"和"解释"的顺序做反。补位看源类型,读数看目标类型。

易错unsignedsigned 同宽度转换时以为位会变。位一个不动。

易错:认为 int 转 float 会溢出。丢的是精度不是范围。

易错:把浮点转整型当成四舍五入或向下取整。是向零截断

易错:以为"表达式里有 unsigned 就一定按无符号算"。要先做整型提升,shortunsigned short 都会先升成 int

教材出处
  • C 语言中数值数据类型的宽度(表 2.7),指针与长整数宽度等于机器字长:袁春风《计算机组成与系统结构》第 3 版 §2.5.1,印刷页 p46
  • 带符号数与无符号数混合运算时按无符号运算,-1%u 输出为 2321:袁春风《计算机系统基础》§2.2.2 C 语言中的整数及其相互转换,印刷页 p43
  • int / float / double 之间强制类型转换的四条数值转换结果,以及往返转换是否保值的判定(例 2.25):同书 §2.3.4 C 语言的浮点数类型,印刷页 p49–p50

相关知识

真值与机器数:四种编码的定义定点数编码的转换与书写浮点数表示(IEEE 754)数据对齐与大小端

真题练习