Appearance
C语言中的数据类型与转换
2026 大纲 二(三)整数的表示和运算(1 无符号整数、2 带符号整数);整型与浮点型之间的转换涉及 二(四)1 浮点数的表示。
位串没变,变的是读它的那条指令
寄存器和内存单元里只有 0 和 1,类型不是数据本身的属性,而是一种解释方式。同样一串 8 位的 10000000,按无符号读是
这一句是本篇所有题目的总钥匙。类型转换题看起来花样很多(int 转 short、short 转 unsigned int、unsigned short 转 short……),但它们全都在问同一件事:这串位有没有被改动?改动之后又是按谁的规则读的?
拆开就是两步,而且顺序不能反:
🔴 补位问出身,读数问去向。 宽度与符号性同时变化时:第一步按"源类型"的符号性把位串扩展或截断到目标宽度,第二步按"目标类型"的符号性去解释这串位。 顺序反了,答案会差出几十亿。
两步各自的规则很短。扩展:源类型有符号就做符号扩展(高位全填符号位),源类型无符号就做零扩展(高位全填 0),两者都保值。截断:直接丢弃高位只留低位,数值上等价于对
有一个很稳的自查点:
🔴
(unsigned)(-1)恒等于该类型的最大值。的补码是全 1,全 1 按无符号读就是 。任何位宽下都成立,算完拿它验一下能挡掉不少低级错。
一、位串本身没有类型
| 类型 | 编码方式 | 8 位范围 |
|---|---|---|
| 无符号整数 | 纯二进制,所有位都是数值位 | |
| 有符号整数 | 补码,最高位是符号位 |
现代计算机的有符号整数一律用补码,理由在 真值与机器数:零唯一、加减法统一为加法、比原码多表示一个最小负数。
| C 语言整数类型 | 典型位宽 | 有符号范围 | 无符号范围 |
|---|---|---|---|
| char | 8 | ||
| short | 16 | ||
| int | 32 | ||
| long | 32/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🔴 顺带定一条判据:强制转换到底会不会改内存里的位?整型之间同宽度不会(只换解释)、变宽会(添了高位)、变窄会(丢了高位);整型与浮点型互转一定会(重新编码,位模式完全无关)。判据是编码方式变没变——没变就是纯解释,一旦跨越"整数编码"与"浮点编码",就是真的重算了一遍。
符号扩展为什么保值:一行代数(想弄清"复制符号位"凭什么不改变数值时展开)
用补码的按权展开就能一步看出来。
扩展到
关键在于
零扩展保值的理由更直白:无符号数的高位权都是正的,填 0 什么也没加。
三、宽度和符号性同时变
这是最容易出错的一种,因为两件事同时发生,而顺序是固定的。
规则:扩展按"源类型"的符号性做,解释按"目标类型"来
先看源类型决定怎么补位,再看目标类型决定怎么读。
c
short si = -32767;
unsigned int ui = si;| 步 | 做什么 | 结果 |
|---|---|---|
| 0 | si 是 short, | 0x8001 |
| 1 | 源类型 short 是有符号的 | 0xFFFF8001 |
| 2 | 目标类型 unsigned int |
顺序反了会怎样:若先按无符号解释再扩展,把 si 当 unsigned short 读成
长转短再转回来会发生什么,以及同一串位为什么会被补成两种样子(想确认多一次显式转换到底改变了什么时展开)
反向的例子(长转短再转回来):
c
int i = 32777; // 0x00008009
short si = i; // 截断低 16 位 → 0x8009,按 short 读 = -32759
int j = si; // si 是 short(有符号)→ 符号扩展 → 0xFFFF8009 = -32759再看同一串位的两种补法(32 位机器):
c
short x = -4321;
unsigned int y = x;
unsigned int z = (unsigned short)x;x 的 16 位补码:
y:源类型是short(有符号)符号扩展到 32 位 0xFFFFEF1F,按无符号读z:先被显式转成unsigned short(位模式不变,值变为 61215),此时源类型是无符号的零扩展 0x0000EF1F
两者相差
同一现象换个写法(源类型直接声明成无符号):
| 写法 | 源类型符号性 | 扩展方式 | 结果 |
|---|---|---|---|
short si = -32767; | 有符号 | 符号扩展 | |
unsigned short us = -32767; | 无符号 | 零扩展 |
四、整型与浮点型之间
| 转换方向 | 会发生什么 | 根源 |
|---|---|---|
| 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 的范围(约
)远大于 int(约 ),任何 int 都落在里面;但 int 的有效位可达 31 位而 float 只有 24 位,所以必须舍入。反过来 float → int 两种问题都有:小数部分被截断、大数值放不进。
🔴 浮点转整型是"向零取整",不是四舍五入,也不是向下取整。
、 。负数这一侧特别要留意:向零截断得到的 比原值大;若按"向下取整"应该得 。
往返转换是否无损,只看中间那个类型能不能同时装下源类型的范围与有效位数:(int)(double)i 恒等于 i(53 位 > 32 位);(int)(float)i 不恒等(只有 24 位);(float)(double)f 恒等(double 是 float 的超集);(float)(int)f 不恒等(int 没有小数部分)。另外 f == -(-f) 恒成立——浮点取负只翻转符号位
三行代码看清"32 位换 32 位却丢东西"(想要一个能自己跑一遍的证据时展开)
c
int n = 0x7FFFFFFF; // 2147483647,有效位 31 位
float f = (float)n; // 只装得下 24 位有效数字,必须舍入
int m = (int)f; // m 不等于 n五、混合运算:无符号会"传染"
C 语言规定:同一个表达式里同时出现无符号数和带符号数时,带符号数被转换成无符号数,整个比较/运算按无符号进行。后果相当反直觉:
c
int x = -1;
if (x < 0u) { /* 不会执行 */ }unsigned 做循环变量再写 i >= 0 会成为永真条件,循环无法退出。
隐式类型提升顺序:
六、判据:怎么判断一个表达式会不会按无符号算
完整判断只有三步,按顺序走:
- 先做整型提升:比
int窄的类型(char、short,无论带不带unsigned)一律先升成int。因为int装得下unsigned short的全部取值(),升完两边都是 int,根本走不到第 2 步。 - 提升后比宽度:无符号的那一方不窄于带符号的一方时,带符号数转成无符号——"传染"只在这一格发生。
- 带符号的一方更宽时:它装得下无符号方的全部取值,于是把无符号数转成这个更宽的带符号类型,整个表达式按带符号算。
| 表达式(32 位 int) | 提升后两边的类型 | 按什么算 | 结果 |
|---|---|---|---|
-1 < 0u | int vs unsigned int(等宽) | 无符号 | 假 |
-1 < (unsigned short)0 | int vs int(第 1 步就化解了) | 带符号 | 真 |
-1 < 0ul(long 为 64 位) | long vs unsigned long(等宽) | 无符号 | 假 |
-1L < 0u(long 为 64 位) | long vs long(第 3 步,64 位装得下 32 位无符号) | 带符号 | 真 |
🔴 所以"只要出现 unsigned 就按无符号算"是半对的话——宽度不同时它是错的。标准之所以选"带符号转无符号"这一边,是因为这样位串不动;反过来把无符号转成同宽度的带符号,超过
的那一半会变成负数、信息彻底错乱。它只保证行为确定,不保证符合直觉。
⚠️ 最后记一下哪些写法会悄悄把无符号带进来:字面量后缀 u / U / ul;声明为 unsigned 的变量;以及最隐蔽的 sizeof——它的返回类型是 size_t,无符号,所以 int i = -1; if (i < sizeof(x)) 恒为假。这三样都没出现,表达式就一律按带符号算。
考点速记
- 类型是解释方式而不是数据属性;同宽度转换只换解释(
),变宽按源类型的符号性做符号扩展或零扩展,变窄截断高位且不报错。 - 宽度与符号性同时变时顺序固定——补位问出身,读数问去向:先按源类型符号性扩展/截断,再按目标类型解释。
- int → float 不溢出但可能丢精度(24 位有效位),浮点 → int 向零截断且可能溢出;混合运算按三步判据走:先整型提升 → 等宽时带符号转无符号 → 带符号更宽时按带符号算,
sizeof返回无符号是最隐蔽的传染源。
这一节在真题里被考过的形式(下方「真题练习」里属于本篇的那几道,本章题量最集中的一篇):
- 窄的无符号转宽的无符号(如
unsigned short→unsigned int):源类型无符号 ⇒ 零扩展,高位补 0,值不变。 - 窄的带符号转宽的无符号(如
short→unsigned int):先按源类型符号扩展到 32 位,再按无符号解释这串位。先扩成 FFFF8001H,按无符号读就是。⚠️ 这一道最能验证"补位问出身、读数问去向"有没有真的掌握——反过来做会得到完全不同的数。 - 同宽度换符号性(
short↔unsigned short):位模式一个不动,只换解释。按无符号读是 ; 按带符号读是 。 - 宽转窄再转回来(
int→short→int):先截断只留低 16 位,再按 short 有符号扩展回 32 位。截断后最高位为 1,回来时被符号扩展成负数。丢掉的位回不来。 - 不同类型混合运算(
int与short相加赋给int):short 先整型提升为 int(符号扩展),再按 int 相加,结果按 int 存。 - 同一机器数按 int 或 float 解释,问值可能是多少:这是"位串没有类型"的直接考法——把同一串位分别按补码和 IEEE 754 各读一遍,看哪个选项对得上。
易错:把"扩展"和"解释"的顺序做反。补位看源类型,读数看目标类型。
易错:
unsigned与signed同宽度转换时以为位会变。位一个不动。
易错:认为 int 转 float 会溢出。丢的是精度不是范围。
易错:把浮点转整型当成四舍五入或向下取整。是向零截断。
易错:以为"表达式里有 unsigned 就一定按无符号算"。要先做整型提升,
short与unsigned short都会先升成int。
教材出处
- C 语言中数值数据类型的宽度(表 2.7),指针与长整数宽度等于机器字长:袁春风《计算机组成与系统结构》第 3 版 §2.5.1,印刷页 p46
- 带符号数与无符号数混合运算时按无符号运算,
-1按%u输出为:袁春风《计算机系统基础》§2.2.2 C 语言中的整数及其相互转换,印刷页 p43 - int / float / double 之间强制类型转换的四条数值转换结果,以及往返转换是否保值的判定(例 2.25):同书 §2.3.4 C 语言的浮点数类型,印刷页 p49–p50
相关知识
真值与机器数:四种编码的定义|定点数编码的转换与书写|浮点数表示(IEEE 754)|数据对齐与大小端