什么是位运算
位运算是计算机中进行二进制操作的一种方法。
它可以对二进制数的单个比特位进行操作,包括位移、按位与、按位或、按位异或、按位取反等。
位运算可以用于很多场景,例如:
-
对于需要对程序中的某一或几位进行操作时,比如需要将一个数的某一位设为1或者设为0,这时就可以使用按位运算。
- 在内存管理中,程序常常需要对内存地址进行位运算,以判断对齐、分页和分段等问题。
-
位运算还可以用于加密和解密算法中,提高运算效率并增加加密强度。
-
位运算可以进行逻辑推理,例如二进制数的“灰码编码”等,常用于工业自动化和数字信号处理领域等。
总之,位运算可以广泛应用于计算机领域的各个方面,特别是在处理二进制数据和优化性能时非常有用。常见的语言,比如 Python、C、Java 等都提供了丰富的位运算符,可以方便地进行位运算操作。
按位与——“&”
按位与运算是一种位运算,通常用符号“&”表示。按位与的原理是对两个二进制数的每一位进行与运算,相同位上的数值进行与运算的结果都是1,则结果位1,否则为0。具体来说,按位与操作的运算规则如下:
- 首先将两个二进制对其,使它们的位数相同。如果一个二进制数比另一个二进制数短,则在它左侧填充零直到位数相等。
- 对于每一位,进行按位与运算。按位与运算规则是:如果两位数字对应位都为1,则该位结果为1,否则为0。
- 将位运算结果组成一个新的二进制数,该二进制数就是原始两个二进制数的按位与结果。
举个🌰(例子)假设我们要计算两个二进制数的按位与结果(二进制数用空格分开以方便表示):
1101 1010
& 1011 0011
首先,我们将它们对齐:
1101 1010
& 1011 0011
-----------
然后,对每一位进行按位与运算,得到一下结果:
1 1 0 1 1 0 1 0
& 1 0 1 1 0 0 1 1
-------------------
1 0 0 1 0 0 1 0
因此,按位与运算的结果使 1001 0010,它的十进制十 146。
按位或——“|”
按位或运算也成为按位或,其符号通常为“|”,是一种常用的位运算。按位或的原理是对二进制的每一位进行或运算,如果相同位上的数值有一个为1,则两个按位或运算后的结果在此位上为1,否则为0。按位或的运算规则如下:
- 首先将两个二进制对其,使它们的位数相同。如果一个二进制数比另一个二进制数短,则在它左侧填充零直到位数相等。
- 对于每一位,进行按位或运算。按位与运算规则是 :对于两个二进制数的同一位,如果相同位上的数值有一个为1,则改为结果为1,否则为0。
- 将位运算结果组成一个新的二进制数,该二进制数就是原始两个二进制数的按位与结果。
举个🌰(例子)假设我们要计算两个二进制数的按位或结果(二进制数用空格分开以方便表示):
1101 1010
& 1011 0011
首先,我们将它们对齐:
1101 1010
& 1011 0011
-----------
然后,对每一位进行按位或运算,得到一下结果:
1 1 0 1 1 0 1 0
& 1 0 1 1 0 0 1 1
-------------------
1 1 1 1 1 0 1 1
因此,按位或运算的结果使 1111 10111,它的十进制十 251。
按位异或——“^”
按位异或运算符的符号通常为“^”,它也是一种位运算。按位异或的原理是:如果相同位上的数值不同时,则运算结果在此位上为1,否则为0。按位异或的运算规则如下:
- 首先将两个二进制对其,使它们的位数相同。如果一个二进制数比另一个二进制数短,则在它左侧填充零直到位数相等。
- 对于每一位,进行按位或运算。按位与运算规则是 :如果相同位上的数值不同时,则运算结果在此位上为1,否则为0。
- 将位运算结果组成一个新的二进制数,该二进制数就是原始两个二进制数的按位与结果。
举个🌰(例子)假设我们要计算两个二进制数的按位异或结果(二进制数用空格分开以方便表示):
1101 1010
& 1011 0011
首先,我们将它们对齐:
1101 1010
& 1011 0011
-----------
然后,对每一位进行按位异或运算,得到一下结果:
1 1 0 1 1 0 1 0
& 1 0 1 1 0 0 1 1
-------------------
0 1 1 0 1 0 0 1
因此,按位异或运算的结果使 0110 1001,它的十进制十 105。
按位取反——“~”
按位异或运算符的符号通常为“~”,它也是一种位运算。按位异或的原理是:如果相同位上的数值不同时,则运算结果在此位上为1,否则为0。按位异或的运算规则如下:
- 首先将两个二进制对其,使它们的位数相同。如果一个二进制数比另一个二进制数短,则在它左侧填充零直到位数相等。
- 对于每一位,进行按位或运算。按位与运算规则是 :如果相同位上的数值不同时,则运算结果在此位上为1,否则为0。
- 将位运算结果组成一个新的二进制数,该二进制数就是原始两个二进制数的按位与结果。
举个🌰(例子)假设我们要计算两个二进制数的按位异或结果(二进制数用空格分开以方便表示):
1101 1010
& 1011 0011
首先,我们将它们对齐:
1101 1010
& 1011 0011
-----------
然后,对每一位进行按位异或运算,得到一下结果:
1 1 0 1 1 0 1 0
& 1 0 1 1 0 0 1 1
-------------------
0 1 1 0 1 0 0 1
因此,按位异或运算的结果使 1111 10111,它的十进制十 89。
左移操作(<<)和 右移操作(>>)
左移和右移是计算机编程中常用的位操作,它们用于将数值的二进制位向左或向右移动,从而改变数值的大小。理解这些操作对处理低级别数据、优化代码性能以及执行特定算法(如加密和解密)都是非常有用的。
左移操作——“<<”
左移操作符 << 会将一个数的所有二进制位向左移动指定的位数。右侧空出的位用 0 填充。左移操作实际上相当于乘以 2 的幂。
uint8_t a = 0b00001111; // 二进制表示,十进制 15
a = a << 2;
初始值:a = 0b00001111 (二进制 15)
执行 a << 2 后,a = 0b00111100 (二进制 60)
在这个例子中,a 的所有位向左移动了两位。从十进制的角度来看,左移两位相当于 15 * 2^2 = 60。即原来是15,左移两位后变成60。
数学意义:
从十进制的角度来看,左移 n 位,相当于乘以 2^n。例如:
x << 1 等同于 x * 2^1
x << 2 等同于 x * 2^2
以此类推…
这种位操作是非常高效的,因为在硬件层面上,位移操作比乘法更快。
右移操作——“>>”
右移操作符 >> 会将一个数的所有二进制位向右移动指定的位数。左侧空出的位根据操作数是有符号的还是无符号的,以及编译器的实现,可能会填充 0 或符号位(即符号扩展)。
uint8_t b = 0b11000000; // 二进制表示,十进制 192
b = b >> 2;
初始值:b = 0b11000000 (二进制 192)
执行 b >> 2 后,b = 0b00110000 (二进制 48)
在这个例子中,b 的所有位向右移动了两位。从十进制的角度来看,右移两位相当于192 ÷ 2^2 = 48。即原来是192,右移两位后变成48。
数学意义:
从十进制的角度来看,右移 n 位,相当于除以 2^n。例如:
x >> 1 等同于 x ÷ 2^1
x >> 2 等同于 x ÷ 2^2
以此类推…
但是要注意,右移操作会截断数值。例如,5 >> 1 的结果是 2,而不是 2.5。
特别注意:
1. 无符号数和有符号数的区别:
- 对于无符号数,右移时左侧填充 0。
- 对于有符号数(如 int 类型的负数),右移时可能会进行符号扩展,即用原符号位填充左侧。例如,-2 的二进制为 11111110(假设 8 位),右移一位可能会变成 11111111(保持负数)。
2. 超出范围的移位:
- 如果移位的位数超过了数据类型的位数(例如,uint8_t 只有 8位),移位的结果是未定义的(依赖于具体的编译器实现),因此一般要确保移位数小于类型的位宽。
特别要注意的一点就是,左移、右移操作并不会改变值的本身!
参考文章: