我一直在尝试在业余时间学习 C,其他语言(C#、Java 等)具有相同的概念(并且通常是相同的运算符)…
我想知道的是,在核心层面上,位移 ( <<, >>, >>>) 有什么作用,它可以帮助解决什么问题,以及在拐弯处潜伏着什么陷阱?换句话说,绝对是初学者的移位指南。
<<
>>
>>>
位移运算符的作用与它们的名字所暗示的完全一样。他们移动位。这是对不同班次运算符的简要(或不那么简要)介绍。
所有这些运算符都可以应用于整数值(int, long, 可能short和byteor char)。在某些语言中,将移位运算符应用于小于int自动将操作数调整为int.
int
long
short
byte
char
请注意,这<<<不是运算符,因为它是多余的。
<<<
另请注意,C 和 C++ 不区分右移运算符。它们仅提供>>运算符,并且右移行为是为有符号类型定义的实现。答案的其余部分使用 C#/Java 运算符。
(在包括 GCC 和 Clang/LLVM 在内的所有主流 C 和 C++ 实现中,>>有符号类型是算术的。一些代码假设这一点,但这不是标准所保证的。但它不是undefined;标准要求实现来定义它方式或另一种方式。但是,负符号数的左移是未定义的行为(有符号整数溢出)。因此,除非您需要算术右移,否则使用无符号类型进行位移通常是个好主意。)
整数作为一系列位存储在内存中。例如,存储为 32 位的数字 6int将是:
00000000 00000000 00000000 00000110
将此位模式向左移动一个位置 ( 6 << 1) 将产生数字 12:
6 << 1
00000000 00000000 00000000 00001100
如您所见,数字向左移动了一个位置,而右边的最后一个数字用零填充。您可能还注意到,左移相当于乘以 2 的幂。所以6 << 1等价于6 * 2,并且6 << 3等价于6 * 8。一个好的优化编译器会尽可能用移位代替乘法。
6 * 2
6 << 3
6 * 8
请注意,这些不是循环班次。将此值向左移动一个位置 ( 3,758,096,384 << 1):
3,758,096,384 << 1
11100000 00000000 00000000 00000000
结果为 3,221,225,472:
11000000 00000000 00000000 00000000
“结束”移动的数字丢失了。它不会环绕。
逻辑右移与左移相反。它们不是向左移动位,而是向右移动。例如,移动数字 12:
向右移动一个位置 ( 12 >>> 1) 将返回我们原来的 6:
12 >>> 1
所以我们看到向右移动相当于除以 2 的幂。
但是,移位不能回收“丢失”的位。例如,如果我们改变这种模式:
00111000 00000000 00000000 00000110
向左 4 个位置 ( 939,524,102 << 4),我们得到 2,147,483,744:
939,524,102 << 4
10000000 00000000 00000000 01100000
然后向后移动 ( (939,524,102 << 4) >>> 4) 我们得到 134,217,734:
(939,524,102 << 4) >>> 4
00001000 00000000 00000000 00000110
一旦丢失位,我们就无法恢复原始值。
算术右移与逻辑右移完全相同,只是它不是用零填充,而是用最高有效位填充。这是因为最高有效位是符号位,或区分正数和负数的位。通过用最高有效位填充,算术右移是符号保留的。
例如,如果我们将此位模式解释为负数:
我们有数字 -2,147,483,552。用算术移位 (-2,147,483,552 >> 4) 将其向右移动 4 个位置会给我们:
11111000 00000000 00000000 00000110
或数字 -134,217,722。
所以我们看到我们通过使用算术右移而不是逻辑右移来保留负数的符号。再一次,我们看到我们正在执行 2 的幂除法。