一、字节
C语言的整形变量有如下几个类型:
先来看char类型的变量,char在内存中占用1个byte,也就是说它拥有8个bit位,最多可以表示256个数字,但在C语言中有符号数的最高位为符号位,0代表正数,1代表负数,所以可以使用的bit位数实际上只有7位,加上正负符号char类型变量的数字表示范围为-128 ~ 127,这也就是为什么ASCII码只有128个(-128 ~ -1保留,为其它编码使用,例如汉字等)。
注意,本教程中明确写出来的内存地址,例如0x100、0x200、0x2000等等,这些地址是为了便于读者理解而假设的地址,程序实际运行时i的地址并不会是0x100、0x200、0x2000。本教程中其它例子也是一样。
short类型变量在内存中占用2个byte,也就是16个bit位,但是在不同操作系统编译器下所编译出来的程序这两个byte的顺序是不一样的,如下图:
假设在内存地址0x200处定义了一个short型变量,地址0x200处byte的值为0x11,地址0x201处byte的值为0x22,如果在大尾机中这个short变量的值为0x1122,而在小尾机中这个short变量的值为0x2211。大尾机是将数据的高字节存放在低地址,而小尾机则是将数据的高字节放在高地址。同样的道理,int类型、long类型和long long类型也都与short是一样的。例如我们定义一个int型变量并为其初始化:
int i = 0x11223344;
假设变量i的内存地址为0x200则在小尾机中的数据存储方式为:
在大尾机中的数据存储方式为:
下面我们来看讨论两个变量赋值的问题:
编写一段代码,编译并运行它,看看结果是什么:
#include <stdio.h> int main(int argc, char **args) { char i = 0x11; short j = 0x2233; j = i; printf("0x%x\n", j); return 0; }
运行结果为:
0x11
也就是说一个小范围变量赋值给一个大范围变量时,将小范围变量的有效内存数据复制到大范围变量的低字节区域,并将高字节区域清零,具体的过程如下:
再来看第二个问题,大范围的变量赋值给一个小范围变量:
#include <stdio.h> int main(int argc, char **args) { char i = 0x11; short j = 0x2233; i = j; printf("0x%x\n", i); return 0; }
运行结果为:
0x33
把一个short类型的变量赋值给一个char类型的变量结果为什么是0x33呢?这是因为当一个赋值操作的目标变量范围小于源变量范围时,编译器只会把低字节的数据复制到小范围变量中,而将无法容纳的字节(高字节)自动抛弃。
可以看到short j的低字节为0x33,高字节为0x22,赋值时只将低字节的0x33复制到变量char i中,而将高字节的0x22抛弃。
下面再来看一个非常有趣的赋值问题:
#include <stdio.h> int main(int argc, char **args) { char i = 34; short j = 129; i = j; printf("%d\n", i); return 0; }
运行结果为:
-127
结果出乎我们的意料,但却是正确的运行结果。来看一下这个赋值运算的原理:
可以看到i被j赋值之后值变为了129,它的2进制表示为1000 0001,因为i是一个有符号数,所以它的最高位为符号位,所以1000 0001的值正好为-127。
下面就来了解一下负数的表示方式,在计算机中数通常采用补码的形式来表示,正数和0的补码为其本身,负数的补码为其绝对值取反再加1。
10进制数 | 绝对值 | 原码(不包括符号位) | 反码(不包括符号位) | 补码(不包括符号位) | 实际补码(包括符号位) |
-3 | 3 | 000 0011 | 111 1100 | 111 1101 | 1111 1101 |
-23 | 23 | 001 0111 | 110 1000 | 110 1001 | 1110 1001 |
-127 | 127 | 111 1111 | 000 0000 | 000 0001 | 1000 0001 |
上面例子中char i的值为-127,其补码为1000 0001正好是short j的129的原码。所以char i = 129;的结果是-127。
负数在计算机中通常以补码的形式表示,那为什么要采用补码来表示负数呢?我们来看一下9的补码为0000 1001。在计算机中为了能让CPU对所有的数,无论是正数还是负数都能够快速的进行正确的加减运算,例如要让CPU能够快速的识别并运行9 + (-9) = 0这样的运算法则。也就是说让0000 1001加上一个数(-9)能让结果快速的变为0,这个过程分为两步:
1.让9加上其本身的反码,即得到一个全1的结果:
0000 1001
+ 1111 0110
1111 1111
2.在全1的结果基础上再加1,即出现多米诺骨牌效应,所有位均为0,第9个bit位的1溢出,所以结果为0:
1111 1111
+ 0000 0001
0000 0000
把上面两步的运算合并成一步运算,即一个正数加上其反码再加1结果为0,为了能让CPU能够快速的计算正负数的加减法,所以负数通常采用“补码”的形式来表示。
再来看看两个负的加法:
(-9) + (-7) = -16
-9的补码为1111 0111,-7的补码为1111 1001,两个负数的加法结果为:
1111 0111
+ 1111 1001
1111 0000
1111 0000取其补码(符号位不变)的结果为1001 0000 = -16
无符号整数的位移运算非常简单,每向左位移1位相当于将其乘2,每向右位移1位相当于将其除2。例如,将无符号整数7向左位移3次再向右位移4次:
#include <stdio.h> int main(int argc, char **args) { unsigned int i = 7; i <<= 1; printf("%u\n", i); i <<= 1; printf("%u\n", i); i <<= 1; printf("%u\n", i); i >>= 1; printf("%u\n", i); i >>= 1; printf("%u\n", i); i >>= 1; printf("%u\n", i); i >>= 1; printf("%u\n", i); return 0; }
运行结果为:
14 28 56 28 14 7 3
需要注意的是,当向右位移时如果这个数是奇数,它的最低位将会被抛弃,也就是说7向右位移1位时0000 0111变为0000 0011。上面所说的移位运算只限于无符号整数,对于有符号型整数的运算法则不适用。因为当位移运算时可能会影响到其符号位,使其结果并不一定是乘2或除2。
再来看一个关于有符号数位运算的例子:
#include <stdio.h> int main(int argc, char **args) { char i = 11; i <<= 4; printf("%d\n", i); i = -7; i <<= 5; printf("%d\n", i); i = -7; i >>= 1; printf("%d\n", i); return 0; }
运行结果为:
-80 32 -4
结果很奇怪,但这是正确的运算结果,来分析一下这3个位移运算:
Copyright © 2015-2023 问渠网 辽ICP备15013245号