常用的移位操作是<< 和 >> ,分别为左移和右移。我们在C语言中有算数移位和逻辑移位两种。
算数移位:区分符号的移位 {C语言中直接是定义char m = 3}
逻辑移位:不区分符号的移位 {C语言中用unsigned char m = 3}
算数移位和逻辑移位有什么不同,举例说明,例如:
(1)对无符号数3来说:
x<<1往左移一位,最左边的位移掉了,最右边的移进来的位补零。变成00000110,所以结果是6;[1*2^2 + 1*2^1 + 0*2^0 = 6]
x>>1往右边移一位,由于是无符号数,所以逻辑右移,最右边一位移掉,最左边移进来的位补零,变成00000001,所以结果是1。[1*2^0 = 1]
(2)对于有符号数3来说:
x<<1往左移一位,最左边的位移掉了,最右边的移进来的位补零。变成00000110,所以结果是6;
x>>1往右边移一位,由于是有符号数,可能发生逻辑右移,也可能发生算术右移,这一点,C标准并没有明确地指定是使用逻辑右移还是算术右移。但大多数的机器都使用算术右移,变成00000001,所以结果还是1。
但是请注意,这只是说大多数的机器是这样的,你敢保证自己不会碰到特殊情况吗?
(3)对于有符号数-3来说:
x<<1往左移一位,最左边的位移掉了,最右边的移进来的位补零。变成11111010,结果是-6;[-(1*2^2 + 0*2^1 + 1*2^0 +1) = -6]
x>>1往右移一位,由于是有符号数,可能发生逻辑右移,也可能发生算术右移。大多数机器使用算术右移,变成11111110,结果是-2。[-(1*2^0 +1) = -2]
总结:
左移时总是移位和补零;
右移时无符号数是移位和补零,此时称为逻辑右移;
而有符号数大多数情况下是移位和补最左边的位(也就是补最高有效位),移几位就补几位,此时称为算术右移。
补充:
1.汇编语言中的逻辑右移(SHR)是将各位依次右移指定位数,然后在左侧补0,算术右移(SAR)是将各位依次右移指定位数,然后在左侧用原符号位补齐
2.高级语言右移运算符(>>)是将一个数的二进位全部右移若干位,低位移出部分舍弃,左补0
3.高级语言右移和汇编语言中的逻辑右移功能一样,但不同于算术右移
理解有误的认知:对于有符号整型数的简单认识就是:最高位为符号位,0为正,1为负。那么剩下几位应该如何表示呢?理所当然的认为:既然是1表示为0000 0001(假设为8位整型数,下同),那么-1就应该表示为1000 0001了。
实际上在C语言里,-1的正确表示应该是1111 1111,即0xFF。因为C语言里,对整型数是采用Two’s complement表示法,而前面我的理解则是Sign-Magnitude表示法(浮点数采用该法)。在Two’s complement表示法里,1000 0001表示的是-127。
几个特殊值,比如:
正值的最大表示为0111 1111,即127。[1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 +1*2^0 = 127]
负值的最小表示为1000 0000,即-128。[-(1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 +1*2^0 + 1) = -128]
并因此带来几个有趣的现象,比如从8位有符号数转成16位有符号数的填充问题。以前想当然的是填0,但这是错的,应该是填充符号位的值。比如-1=0xFF填充符号位1就应该变成0xFFFF。而如果填充的是0,那么变成0x80FF就不对了。
Two’s complement是不对称的。一个数的正负值表示,除了最高位之外,其余位数也是不尽相同的。在C语言里,当在无符号数和有符号数之间进行转换的时候,数值的二进制序列是不会改变的,改变的只是对该序列的解读模式。比如有符号数-1转换成无符号数就变成了255,虽然其二进制表示都是0xFF。如果不理解这一点,就会出Bug。