408答疑
一、数制与编码
进位计数制及其相互转换
采用二进制编码的原因
在计算机系统内部,所有信息都是用二进制进行编码的,这样做的原因有以下几点:
-
二进制状态的物理实现:二进制只有两种状态,使用有两个稳定状态的物理器件就可以表示二进制数的每一位,制造成本比较低,例如用高、低电平或电荷的正、负极性都可以很方便地表示 0 和 1。
-
逻辑值对应:二进制位 1 和 0 正好与逻辑值“真”和“假”相对应,为计算机实现逻辑运算和程序中的逻辑判断提供了便利条件。
-
编码和运算规则:二进制的编码和运算规则都很简单,通过逻辑门电路能方便地实现算术运算。
进位计数制
常用的进位计数制有十进制、二进制、八进制、十六进制等。十进制数是日常生活中最常使用的,而计算机中通常使用二进制数、八进制数和十六进制数。
进位计数制的基本概念
在进位计数制中,每个数位所用到的不同数码的个数称为基数。十进制的基数为 10 (0~9),每个数位计满 10 就向高位进位,即“逢十进一”。十进制数 101,其中个位的 1 显然与百位的 1 所表示的数值是不同的。每个数码所表示的数值等于该数码本身乘以一个与它所在数位有关的常数,这个常数称为位权。一个进位数的数值大小就是它的各位数码按权相加。
一个 r r r 进制数 ( K n K n − 1 . . . K 0 K − 1 . . . K − m ) (K_nK_{n-1}...K_0K_{-1}...K_{-m}) (KnKn−1...K0K−1...K−m) 的数值可表示为
K n r n + K n − 1 r n − 1 + ⋯ + K 0 r 0 + K − 1 r − 1 + ⋯ + K − m r − m = ∑ i = n − m K i r i K_nr^n + K_{n-1}r^{n-1} + \cdots + K_0r^0 + K_{-1}r^{-1} + \cdots + K_{-m}r^{-m} = \sum_{i=n}^{-m} K_i r^i Knrn+Kn−1rn−1+⋯+K0r0+K−1r−1+⋯+K−mr−m=i=n∑−mKiri
式中, r r r 是基数; r i r^i ri 是第 i i i 位的位权; r r r 的取值可以是 0, 1, …, r − 1 r-1 r−1 共 r r r 个数码中的任意一个。
二进制
-
定义与特点:计算机中用得最多的是基数为 2 的计数制,即二进制。二进制只有 0 和 1 两种数码,计数“逢二进一”。它的任意数位的权为 2 i 2^i 2i, i i i 为所在位数。
-
与其他进制的转换:二进制中的 3 位数码编为一组就是 1 位八进制数码,两者之间的转换极为方便。
八进制
- 定义与特点:基数为 8,有 0~7 共 8 个不同的数码。计数逢八进一。因为 r = 8 = 2 3 r=8=2^3 r=8=23,所以把二进制中的 3 位数码编为一组就是 1 位八进制数码,两者之间的转换极为方便。
十六进制
- 定义与特点:基数为 16,有 0~9, A~F 共 16 个不同的数码,其中 A~F 分别表示 10~15。计数逢十六进一。因为 r = 16 = 2 4 r=16=2^4 r=16=24,所以 4 位二进制数码与 1 位十六进制数码相对应。
进位计数制的标识
可以用后缀字母标识一个数的进位计数制,用 B 表示二进制数,用 O 表示八进制数,用 D 表示十进制数(通常直接省略),用 H 表示十六进制数,有时也用前缀 0x 表示十六进制数。
不同进制数之间的相互转换
二进制数转换为八进制数和十六进制数
对于一个二进制小数(既包含整数部分,又包含小数部分),在转换时应以小数点为界。其整数部分,从小数点开始往左数,将一串二进制数分为3位(八进制)一组或4位(十六进制)一组,在数的最左边可根据需要加“0”补齐;对于小数部分,从小数点开始往右数,也将一串二进制数分为3位一组或4位一组,在数的最右边可根据需要加“0”补齐。最终使总的位数为3或4的整数倍,然后分别用对应的八进制数或十六进制数取代。
示例分析
将二进制数 1111000010.01101 1111000010.01101 1111000010.01101 分别转换为八进制数和十六进制数。
-
八进制转换:
- 将二进制数分为3位一组,高位补0,低位补0,得到:
- 整数部分:001 111 000 010
- 小数部分:011 010
- 转换为八进制数为 ( 1702.32 ) 8 (1702.32)_8 (1702.32)8。
- 将二进制数分为3位一组,高位补0,低位补0,得到:
-
十六进制转换:
- 将二进制数分为4位一组,高位补0,低位补0,得到:
- 整数部分:0011 1100 0010
- 小数部分:0110 1000
- 转换为十六进制数为 ( 3 C 2.68 ) 16 (3C2.68)_{16} (3C2.68)16。
- 将二进制数分为4位一组,高位补0,低位补0,得到:
八进制和十六进制数转换为二进制数时,只需将每位改为3位或4位二进制数即可。八进制数和十六进制数之间的转换也能方便地实现,先将十六进制(八进制)数转换为二进制数,然后由二进制数转换为八进制(十六进制)数较方便。
任意进制数转换为十进制数
将任意进制数的各位数码与它们的权值相乘,再把乘积相加,就得到了一个十进制数。这种方法称为按权展开相加法。例如, ( 11011.1 ) 2 = 1 × 2 4 + 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 1 × 2 0 + 1 × 2 − 1 = 27.5 (11011.1)_2 = 1 \times 2^4 + 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} = 27.5 (11011.1)2=1×24+1×23+0×22+1×21+1×20+1×2−1=27.5。
十进制数转换为任意进制数
一个十进制数转换为任意进制数,通常采用基数乘除法(注意,基数的值与进制相关)。这种转换方法对十进制数的整数部分和小数部分将分别进行处理,对整数部分采用除基取余法,对小数部分采用乘基取整法,最后将整数部分与小数部分的转换结果拼接起来。
示例分析
将十进制数 123.6875 123.6875 123.6875 转换成二进制数。
-
整数部分:
- 使用除基取余法,整数部分除以2,取余数,最后得到的余数为数的最高位(除基取余,先余为低,后余为高),商为0时结束。
- 整数部分 123 = ( 1111011 ) 2 123 = (1111011)_2 123=(1111011)2。
-
小数部分:
- 使用乘基取整法,小数部分乘以2,取整数,最后取得的整数为数的最高位(乘基取整,先整为高,后整为低),乘积为1.0(或满足精度要求)时结束。
- 小数部分 0.6875 = ( 0.1011 ) 2 0.6875 = (0.1011)_2 0.6875=(0.1011)2。
-
结果:
- 因此, 123.6875 = ( 1111011.1011 ) 2 123.6875 = (1111011.1011)_2 123.6875=(1111011.1011)2。
注意事项
十进制数转换为任意进制数的思考方式
关于十进制数转换为任意进制数为何采用除某取余法和乘某取整法,以及所取之数放置位置的原理,请结合 r r r 进制数的数值表示公式思考,而不应死记硬背。
十进制小数与二进制小数的表示差异
在计算机中,小数和整数不一样,整数可以连续表示,但小数是离散的,所以并不是每个十进制小数都可以准确地用二进制小数表示。例如 0.3,无论经过多少次乘二取整转换都无法得到精确的结果。但任意一个二进制小数都可以用十进制小数精确表示,希望读者引起重视。
定点数的编码表示
真值和机器数
- 在日常生活中,通常用正号、负号来分别表示正数(正号可省略)和负数,如+15、-8等。这种带“+”或“-”符号的数称为真值。真值是机器数所代表的实际值。
机器数的编码
- 在计算机中,通常将数的符号和数值部分一起编码,将数据的符号数字化。通常用“0”表示“正”,用“1”表示“负”。这种把符号“数字化”的数称为机器数。常用的有原码、补码和反码表示法。
原码表示法
- 原码表示法中,符号位用“0”表示正数,用“1”表示负数。例如, 0 , 101 0,101 0,101(这里的逗号“,”仅用于区分符号位与数值位)表示+5。
机器数的定点表示
定点表示和浮点表示
根据小数点的位置是否固定,在计算机中有两种数据格式:定点表示和浮点表示。在现代计算机中,通常用补码整数表示整数,用原码小数表示浮点数的尾数部分,用移码表示浮点数的阶码部分。定点表示法用来表示定点小数和定点整数。
定点小数
- 定点小数是纯小数,约定小数点位置在符号位之后、有效数值部分最高位之前。
- 若数据 X X X 的形式为 X = x 0 . x 1 x 2 . . . x n X = x_0.x_1x_2...x_n X=x0.x1x2...xn(其中 x 0 x_0 x0 为符号位, x 1 x_1 x1~ x n x_n xn 是数值的有效部分,也称尾数, x 1 x_1 x1 为最高有效位),则在计算机中的表示形式如下图所示。
定点整数
- 定点整数是纯整数,约定小数点位置在有效数值部分最低位之后。
- 若数据 X X X 的形式为 X = x 0 x 1 x 2 . . . x n X = x_0x_1x_2...x_n X=x0x1x2...xn(其中 x 0 x_0 x0 为符号位, x 1 x_1 x1~ x n x_n xn 是尾数, x n x_n xn 为最低有效位),则在计算机中的表示形式如下图所示。
总结
事实上,在机器内部并没有小数点,只是人为约定了小数点的位置。因此,在定点数的编码和运算中不用考虑对应的定点数是小数还是整数,而只需关心它们的符号位和数值位即可。
定点数的编码表示法主要有以下几种:原码、补码、反码和移码。
原码、补码、反码、移码
原码表示法
定义
用机器数的最高位表示数的符号,其余各位表示数的绝对值。原码的定义如下:
[
x
]
原
=
{
0
,
x
,
0
≤
x
<
2
n
2
n
−
x
=
2
n
+
∣
x
∣
,
−
2
n
<
x
≤
0
[x]_{\text{原}} = \begin{cases} 0, x, & 0 \leq x < 2^n \\ 2^n - x = 2^n + |x|, & -2^n < x \leq 0 \end{cases}
[x]原={0,x,2n−x=2n+∣x∣,0≤x<2n−2n<x≤0
其中
x
x
x 是真值,字长为
n
+
1
n+1
n+1。
示例
例如,若 x 1 = + 1110 x_1 = +1110 x1=+1110, x 2 = − 1110 x_2 = -1110 x2=−1110,字长为 8 位,则其原码表示为:
- [ x 1 ] 原 = 0 , 0001110 [x_1]_{\text{原}} = 0, 0001110 [x1]原=0,0001110
- [ x 2 ] 原 = 2 7 + 1110 = 1 , 0001110 [x_2]_{\text{原}} = 2^7 + 1110 = 1, 0001110 [x2]原=27+1110=1,0001110
其中最高位是符号位。
表示范围
若字长为 n + 1 n+1 n+1,则原码整数的表示范围为 − ( 2 n − 1 ) ≤ x ≤ 2 n − 1 -(2^n - 1) \leq x \leq 2^n - 1 −(2n−1)≤x≤2n−1(关于原点对称)。
注意事项
零的原码表示有正零和负零两种形式,即 [ + 0 ] 原 = 0 , 00000000 [+0]_{\text{原}} = 0, 00000000 [+0]原=0,00000000 和 [ − 0 ] 原 = 1 , 00000000 [-0]_{\text{原}} = 1, 00000000 [−0]原=1,00000000。
优点
- 与真值的对应关系简单、直观,与真值的转换简单。
- 用原码实现乘除运算比较简便。
缺点
- 0 的表示不唯一,有 ± 0 ±0 ±0 两个编码。
- 用原码实现加减运算比较复杂。
补码表示法
定义
补码表示法中的加减运算统一采用加法操作实现。正数的补码和原码相同,负数的补码等于模
(
n
+
1
)
(n+1)
(n+1) 位补码的模为
2
n
+
1
2^{n+1}
2n+1 与该负数绝对值之差。补码的定义如下:
[
x
]
补
=
{
0
,
x
,
0
≤
x
<
2
n
2
n
+
1
+
x
=
2
n
+
1
−
∣
x
∣
,
−
2
n
≤
x
<
0
(
mod
2
n
+
1
)
[x]_{\text{补}} = \begin{cases} 0, x, & 0 \leq x < 2^n \\ 2^{n+1} + x = 2^{n+1} - |x|, & -2^n \leq x < 0 \end{cases} \quad (\text{mod } 2^{n+1})
[x]补={0,x,2n+1+x=2n+1−∣x∣,0≤x<2n−2n≤x<0(mod 2n+1)
其中
x
x
x 是真值,字长为
n
+
1
n+1
n+1。
示例
例如,若 x 1 = + 1110 x_1 = +1110 x1=+1110, x 2 = − 1101 x_2 = -1101 x2=−1101,字长为 8 位,则补码表示为:
- [ x 1 ] 补 = 0 , 0001010 [x_1]_{\text{补}} = 0, 0001010 [x1]补=0,0001010
- [ x 2 ] 补 = 2 8 − [ x 2 ] 原 = 1 , 1110011 [x_2]_{\text{补}} = 2^8 - [x_2]_{\text{原}} = 1, 1110011 [x2]补=28−[x2]原=1,1110011
表示范围
若字长为 n + 1 n+1 n+1,则补码整数的表示范围为 − 2 n ≤ x ≤ 2 n − 1 -2^n \leq x \leq 2^n - 1 −2n≤x≤2n−1(比原码多表示 “ − 2 n -2^n −2n”)。
特殊数据的补码表示
- [ + 0 ] 补 = [ − 0 ] 补 = 0 , 00...0 [+0]_{\text{补}} = [-0]_{\text{补}} = 0, 00...0 [+0]补=[−0]补=0,00...0(含符号位共 n + 1 n+1 n+1 个 0),说明 0 的补码表示是唯一的。
- [ − 1 ] 补 = 2 n + 1 − 1 = 1 , 11...1 [-1]_{\text{补}} = 2^{n+1} - 1 = 1, 11...1 [−1]补=2n+1−1=1,11...1(含符号位共 n + 1 n+1 n+1 个 1)。
- [ 2 n − 1 ] 补 = 0 , 11...1 [2^n - 1]_{\text{补}} = 0, 11...1 [2n−1]补=0,11...1( n n n 个 1),即 n + 1 n+1 n+1 位补码能表示的最大整数。
- [ − 2 n ] 补 = 1 , 00...0 [-2^n]_{\text{补}} = 1, 00...0 [−2n]补=1,00...0( n n n 个 0),即 n + 1 n+1 n+1 位补码能表示的最小整数。
模运算(了解即可)
在模运算中,一个数与它除以 “模” 后得到的余数是等价的,如 A 、 B 、 M A、B、M A、B、M 满足 A = B + K M A= B + KM A=B+KM( K K K 为整数),记为 A ≡ B ( mod M ) A \equiv B (\text{mod } M) A≡B(mod M),即 A 、 B A、B A、B 各除以 M M M 后的余数相同。在补码运算中, [ A ] 补 − [ B ] 补 = [ A ] 补 + M − [ B ] 补 [A]_{\text{补}} - [B]_{\text{补}} = [A]_{\text{补}} + M - [B]_{\text{补}} [A]补−[B]补=[A]补+M−[B]补,而 M − [ B ] 补 = [ − B ] 补 M - [B]_{\text{补}} = [-B]_{\text{补}} M−[B]补=[−B]补,因此补码可以借助加法运算来实现减法运算。
补码与真值之间的转换
-
真值转换为补码:
- 对于正数,与原码的方式一样。
- 对于负数,符号位取 1,其余各位由数值部分 “按位取反,末位加 1” 得到。
-
补码转换为真值:
- 若符号位为 0,与原码的方式一样。
- 若符号位为 1,真值的符号为负,数值部分由补码数值部分 “按位取反,末位加 1” 得到。
变形补码
为便于判断运算结果是否溢出,还采用一种双符号位的补码表示,称为变形补码,也称模 4 补码。在双符号位中,左符表示真正的符号位,右符用于判断 “溢出”。假定变形补码的位数为
n
+
1
n+1
n+1(其中符号位占 2 位,数值位占
n
−
1
n-1
n−1 位),则变形补码可表示为:
[
x
]
变补
=
{
00
,
x
,
0
≤
x
<
2
n
−
1
2
n
+
1
+
x
=
2
n
+
1
−
∣
x
∣
,
−
2
n
−
1
≤
x
<
0
(
mod
2
n
+
1
)
[x]_{\text{变补}} = \begin{cases} 00, x, & 0 \leq x < 2^{n-1} \\ 2^{n+1} + x = 2^{n+1} - |x|, & -2^{n-1} \leq x < 0 \end{cases} \quad (\text{mod } 2^{n+1})
[x]变补={00,x,2n+1+x=2n+1−∣x∣,0≤x<2n−1−2n−1≤x<0(mod 2n+1)
变形补码的双符号位 00 表示正,11 表示负。
反码表示法(了解即可)
定义
在计算机内部,反码是原码转换补码的中间表示形式。负数的补码可采用“按位取反,末位加 1”的方法得到,若仅“按位取反”而末位不加 1,则就是负数的反码表示。正数的反码表示和相应的原码表示相同。
不足
反码表示存在以下几个方面的不足:
- 0 的表示不唯一(存在 ± 0 ±0 ±0)。
- 表示范围比补码少一个最小负数。因此,反码在计算机中很少使用。
移码表示法
定义
移码常用来表示浮点数的阶码,它只能表示整数。移码就是在真值
X
X
X 上加上一个常数(偏置值),通常这个常数取
2
n
2^n
2n,相当于
X
X
X 在数轴上向正方向偏移了若干单位,这就是“移码”一词的由来。移码的定义如下:
[
x
]
移
=
2
n
+
x
(
−
2
n
≤
x
<
2
n
)
[x]_{\text{移}} = 2^n + x \quad (-2^n \leq x < 2^n)
[x]移=2n+x(−2n≤x<2n)
其中机器字长为
n
+
1
n+1
n+1。
示例
例如,若正数 x 1 = + 10101 x_1 = +10101 x1=+10101, x 2 = − 10101 x_2 = -10101 x2=−10101,字长为 8 位,偏置值为 2 7 2^7 27,则其移码表示为:
- [ x 1 ] 移 = 2 7 + 10101 = 1 , 0010101 [x_1]_{\text{移}} = 2^7 + 10101 = 1, 0010101 [x1]移=27+10101=1,0010101
- [ x 2 ] 移 = 2 7 + ( − 10101 ) = 0 , 1101011 [x_2]_{\text{移}} = 2^7 + (-10101) = 0, 1101011 [x2]移=27+(−10101)=0,1101011
特点
移码(假设机器字长为 n + 1 n+1 n+1,偏置值为 2 n 2^n 2n)具有以下特点:
- 移码中零的表示唯一, [ + 0 ] 移 = 2 n + 0 = [ − 0 ] 移 = 2 n − 0 = 1 , 00...0 [+0]_{\text{移}} = 2^n + 0 = [−0]_{\text{移}} = 2^n - 0 = 1, 00...0 [+0]移=2n+0=[−0]移=2n−0=1,00...0( n n n 个 “0”)。
- 一个真值的移码和补码仅差一个符号位, [ x ] 补 [x]_{\text{补}} [x]补 的符号位取反即得 [ x ] 移 [x]_{\text{移}} [x]移(“1” 表示正,“0” 表示负,这与其他机器数的符号位取值正好相反),反之亦然。
- 移码全 0 时,对应真值的最小值 − 2 n -2^n −2n;移码全 1 时,对应真值的最大值 2 n − 1 2^n - 1 2n−1。
- 移码保持了数据原有的大小顺序,移码大真值就大,移码小真值就小。
总结
原码、补码、反码和移码这 4 种编码表示的总结如下:
- 原码、补码、反码的符号位相同,负数的机器码相同。
- 原码、反码的表示在数轴上对称,二者都存在 +0 和 −0 两个 0。
- 补码、移码的表示在数轴上不对称,零的表示唯一,它们比原码、反码多表示一个数。
- 原码很容易判断大小。而负数的补码、反码很难直接判断大小,可采用如下规则快速判断:对于负数,数值位部分越小,其绝对值越大,即负得越多。
整数的表示
无符号整数的表示
无符号整数
当一个编码的全部二进制位均为数值位而没有符号位时,该编码表示就是无符号整数,简称无符号数。此时,默认数的符号为正。因为无符号整数省略了一位符号位,所以在字长相同的情况下,它能表示的最大数比有符号整数能表示的大。一般在全部是正数运算且不出现负值结果的场合下,使用无符号整数表示。例如,可用无符号整数进行地址运算,或用它来表示指针。
示例
例如,对 8 位无符号整数,最小数为 0000 0000
(值为 0),最大数为 1111 1111
(值为
2
8
−
1
=
255
2^8 - 1 = 255
28−1=255),即表示范围为 0~255;而对于 8 位有符号整数(补码表示),最小数为 1000 0000
(值为
−
2
7
=
−
128
-2^7 = -128
−27=−128),最大数为 0111 1111
(值为
2
7
−
1
=
127
2^7 - 1 = 127
27−1=127),即表示范围为 -128~127。
特点
- 无符号整数省略了一位符号位,因此在字长相同的情况下,它能表示的最大数比有符号整数能表示的大。
- 无符号整数一般用于全是正数运算且不出现负值结果的场合。
有符号整数的表示
将符号数值化,并将符号位放在有效数字的前面,就组成了有符号整数。虽然前面介绍的原码、补码、反码和移码都可以用来表示有符号整数,但补码表示有其明显的优势。
补码表示法的优势
补码表示法在计算机系统中被广泛采用,其主要优势包括:
-
0 的补码表示唯一:与原码和反码相比,0 的补码表示唯一。
-
运算规则简单:与原码和移码相比,补码运算规则比较简单,且符号位可以和数值位一起参加运算。
-
表示范围更广:与原码和反码相比,补码比原码和反码多表示一个最小负数。
表示范围
计算机中的有符号整数都用补码表示,所以 n n n 位有符号整数的表示范围是 − 2 n − 1 ∼ 2 n − 1 − 1 -2^{n-1} \sim 2^{n-1} - 1 −2n−1∼2n−1−1。
总结
由于补码的这些优势,计算机系统中普遍采用补码来表示有符号整数,以简化运算规则并扩大表示范围。
C 语言中的整数类型及类型转换
C 语言变量之间的类型转换是统考中经常出现的题目,需要读者深入掌握这一内容。了解不同数据类型的表示范围和存储方式对于编写高效、可靠的程序至关重要。
C 语言中的整型数据类型
整型数据类型概述
C 语言中,有符号整数根据位数的不同,可分为短整型(short 或 short int,16 位)、整型(int,32 位)、长整型(long 或 long int,在 32 位机器中为 32 位,在 64 位机器中为 64 位)。要定义无符号整数,只需在 short/int/long 之前加上关键字 unsigned,如无符号短整型(unsigned short 或 unsigned short int)、无符号整型(unsigned int)、无符号长整型(unsigned long 或 unsigned long int)。若不指定 signed/unsigned 时,则默认认为有符号整数。
字符型
字符型(char,8 位)是 C 语言中的一个特殊类型,默认按无符号整数解释。
存储方式
上述类型都是按补码形式存储的,只是有符号整数的最高位代表符号,而无符号整数的全部二进制位都表示数值,没有符号位,相当于数的绝对值,因此两者所表示的数据范围有所不同。
有符号整数表示范围
- 短整型(short 或 short int):16 位
- 整型(int):32 位
- 长整型(long 或 long int):32 位(32 位机器)或 64 位(64 位机器)
无符号整数类型
- 无符号短整型(unsigned short 或 unsigned short int)
- 无符号整型(unsigned int)
- 无符号长整型(unsigned long 或 unsigned long int)
若不指定 signed/unsigned 时,则默认认为有符号整数。
有符号数和无符号数的转换
强制类型转换
C 语言允许在不同的数据类型间做类型转换。强制类型转换的代码格式为 (TYPE b = (TYPE)a)
,强制类型转换后,返回一个具有 TYPE 类型的数值。
short 型转换到 unsigned short 型
- 将位数相同的 short 型强制转换为 unsigned short 型,两个变量对应的每位都是相同的,即强制类型转换的结果是保持二进制各位的位值不变,仅改变解释这些位的方式。
示例分析
int main() {
short x = -4321;
unsigned short y = (unsigned short)x;
printf("x=%d, y=%u\n", x, y);
}
-
有符号整数
x
是一个负数,而无符号整数y
只能表示一个正数,它们在计算机内部都占 16 位,y
的表示范围显然不包括x
的值。 -
有符号整数
x = -4321
,无符号整数y = 61215
。 -
输出的结果中,得到的
y
值似乎与原来的x
没有一点关系。不过将这两个数转换为二进制表示时,我们就会发现其中的规律,如下表所示。 -
y与x的对比
变量 | 值 | 位15 | 位14 | 位13 | 位12 | 位11 | 位10 | 位9 | 位8 | 位7 | 位6 | 位5 | 位4 | 位3 | 位2 | 位1 | 位0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
X | -4321 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
y | 61215 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
unsigned short 型转换到 short 型
- 将无符号整数转换为有符号整数时,最高位解释为符号位,也可能发生数值的变化。
示例分析
int main() {
unsigned short x = 65535;
short y = (short)x;
printf("x=%u, y=%d\n", x, y);
}
- 将
x
和y
转换为二进制表示,变量x
对应的二进制表示为 16 位全 1,若按有符号数的规则解释,则 16 位全 1 对应的真值是 -1。 - 有符号整数
x = 65535
,无符号整数y = -1
。
若同时有无符号数和有符号数参与运算,则 C 语言标准规定按无符号数进行运算。
总结
- 位数相同的有符号数转换为无符号数时,符号位解释为数值的一部分,负数转换为无符号数时数值将发生变化。
- 同理,位数相同的无符号数转换为有符号数时,最高位解释为符号位,也可能发生数值的变化。
不同字长整数之间的转换
大字长变量向小字长变量转换
- 当大字长向小字长转换时,系统把多余的高位部分直接截断,低位部分直接赋值。
- 例如,考虑如下代码片段:
int main() {
int x = 165537, u = -34991; // int 型占用 4B
short y = (short)x, v = (short)u; // short 型占用 2B
printf("x=%d, y=%d\n", x, y);
printf("u=%d, v=%d\n", u, v);
}
- 运行结果如下:
x = 165537, y = -31071
u = -34991, v = 30545
- x, y, u, v 的十六进制表示分别为
0x000286a1
,0x86a1
,0xffff7751
,0x7751
。
小字长变量向大字长变量转换
- 例如,考虑如下代码片段:
int main() {
short x = -4321;
int y = x;
unsigned short u = (unsigned short)x;
unsigned int v = u;
printf("x=%d, y=%d\n", x, y);
printf("u=%u, v=%u\n", u, v);
}
- 运行结果如下:
x = -4321, y = -4321
u = 61215, v = 61215
- x, y, u, v 的十六进制表示分别为
0xef1f
,0xffffef1f
,0xef1f
,0x0000ef1f
。 - 从小字长转换为大字长时,要对高位部分进行扩展。
- 若原数字是无符号整数,则进行零扩展,扩展后的高位部分用 0 填充(例如,当 16 位无符号整数
u
强制转换为 32 位无符号整数v
时,高 16 位用 0 填充)。 - 若原数字是有符号整数,则进行符号扩展,扩展后的高位部分用原数字符号位填充(例如,当 16 位有符号整数
x
强制转换为 32 位有符号整数y
时,因为x
的符号位是 1,所以高 16 位用 1 填充)。
- 若原数字是无符号整数,则进行零扩展,扩展后的高位部分用 0 填充(例如,当 16 位无符号整数
- 注意,
char
型为 8 位无符号整数,其在转换为int
型时高位补 0 即可。
总结
- 在有符号数和无符号数的转换中,若两个变量的字长不同,则分两种情况进行讨论:
- 若从小字长转换到大字长,则要先对原数字的高位部分进行扩展,若原数字是无符号整数,则进行零扩展;若原数字是有符号整数,则进行符号扩展。
- 若从大字长转换到小字长,则直接截取低位部分。也就是说,先进行字长的转换,再进行符号的转换。
四、参考资料
鲍鱼科技课件
b站免费王道课后题讲解:
网课全程班: