您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
Ready 发布于2013年08月29日 07:09

原创 位运算入门详解

2145 次浏览 读完需要≈ 13 分钟

内容目录

在学习编程语言的位运算相关知识之前,请先点击查看另外一篇文章《计算机二进制以及原码、反码、补码入门详解》,以补充必要的基础知识。

在前面的文章中,我们已经介绍了计算机二进制以及原码、反码、补码的相关基础知识,这里我们结合具体的编程语言Java,来介绍一下在程序开发中常用的位运算符。虽然我们下面的例子使用的编程语言是Java,不过你也无须过多担心,因为诸如C#、PHP、JavaScript、C/C++等几乎所有编程语言的位运算符及运算规则都是如此。

位运算是一种比较特别的数学运算。众所周知,在计算机程序中,所有的数值在内存中都是以二进制形式来存储的,而位运算实际上就是直接对整数在内存中的二进制位进行操作。正因如此,一般情况下,位运算的运算效率比加减乘除等常规数学运算要高得多。此外,位运算具备一些常规数学运算所没有的特点和规律,我们可以利用位运算的相关特性来完成一些非常巧妙的程序设计。

首先,我们来认识一下位运算符。在大多数编程语言中,位运算符主要有如下几种:

位运算符 名称
~ 按位取反
& 按位与
| 按位或
>> 按位右移
<< 按位左移
^ 按位异或
>>> 按位无符号右移

下面我们结合具体的例子来逐个讲解位运算符。

按位取反(~)

位运算符"~"用于将指定的整数的二进制形式包括符号位在内进行逐位取反,如果原来的位上是1,就置为0,如果原位上是0,就置为1。

byte a = 13;
byte b = (byte) ~a;
System.out.println(b); // 输出:-14

/* 
 * 计算机系统中均以二进制的补码形式进行运算。
 * 13的二进制补码:0000 1101(非负数的补码与原码相同)
 * 将13的二进制补码逐位取反,得到b的补码:1111 0010
 * 推算出
 * b的反码:1111 0001
 * b的原码:1000 1110
 * b = -14
 */
byte x = -3;
byte y = (byte)~x;
System.out.println(y);	//输出:2
/*
 * -3的原码为:1000 0011
 * -3的反码为:1111 1100
 * -3的补码为:1111 1101
 * 将-3的补码逐位取反,得到y的补码:0000 0010
 * y的补码为正,所以y为正数,y的原码为:0000 0010
 * y = 2
 */

按位与(&)

位运算符"&"用于将两个整数的二进制形式进行逐位"与"操作(包括符号位),也就是将两个二进制数对应位上的数进行如下计算:如果两个数对应位上的数都为1,则结果数对应位上的数为1,否则为0。

正数按位与(&)位运算

负数按位与(&)位运算

按位或(|)

位运算符"|",与"&"恰好相反,用于将两个整数的二进制形式进行逐位"或"操作(包括符号位),也就是将两个二进制数对应为上的数进行如下计算:如果两个数对应位上的数至少有一个为1,这结果数对应位上的数位1,否则为0。

按位或(|)运算

按位右移(>>)

位运算符">>"用于将指定整数的二进制形式全部向右移动指定的位数(包括符号位),移动后,右侧超出范围的部分直接舍弃,左侧空出来的部分全部用符号位的数字来补充。

byte a = 25 >> 2;
/*
 * 25的补码为:0001 1001
 * 右移2位为:**00 0110(右侧超出的"01"被舍弃掉)
 * 右侧空出来的部分全部用符号位填充,符号位是0,所以得到的结果为:
 * 0000 0110
 * 换算为十进制为6
 */

byte b = 3 >> 5;
/*
 * 3的补码为:0000 0011
 * 右移5位为:**** *000(右侧超出的"0 0011"被舍弃掉)
 * 右侧空出来的部分用符号位填充得到:0000 0000
 * 换算为十进制为0 
 */

byte c = -9 >> 2;
/*
 * -9的原码为:1000 1001
 * -9的反码为:1111 0110
 * -9的补码为:1111 0111
 * 右移2位为:**11 1101(右侧超出的"11"被舍弃掉)
 * 右侧空出来的部分用符号位填充,符号位是1,所以得到的结果为:
 * 1111 1101
 * 计算机系统中均以二进制的补码形式来进行运算。
 * 所以其反码为:1111 1100
 * 其补码为:1000 0011
 * 换算为十进制为-3
 */

System.out.println(a); // 输出:6
System.out.println(b); // 输出:0
System.out.println(c); // 输出:-3

按位左移(<<)

位运算符"<<"用于将指定整数的二进制形式全部向左移动指定的位数(包括符号位),移动后,左侧超出范围的部分直接舍弃,左侧空出来的部分全部用0来补充。

byte a = 25 << 2;
/*
 * 25的补码为:0001 1001 
 * 左移2位为:0110 01**(左侧超出的"00"被舍弃掉)
 * 右侧空出来的部分全部用0来填充,所以得到的结果为:
 * 0110 0100
 * 换算为十进制为100
 */
System.out.println(a); // 输出:100

按位异或(^)

位运算符"^"用于将两个整数的二进制形式进行逐位"异或"操作(包括符号位),也就是将两个二进制数对应位上的数进行如下计算:如果两个数对应位上的数有且仅有一个为1,则结果数对应位上的数为1,否则为0。

byte a = 25 ^ 14;
/*
 * 25的补码:0001 1001
 * 14的补码:0000 1110
 * 进行异或:0001 0111(两个数对应位上只有一个数位1,结果才为1,否则为0)
 * 换算为十进制为23
 */System.out.println(a); // 输出:23

按位无符号右移(>>>)

位运算符">>>"用于将指定整数的二进制形式全部向右移动指定的位数(包括符号位),移动后,右侧超出范围的部分直接舍弃,左侧空出来的部分全部用0来补充。

byte a = 27 >>> 3;
/*
 * 27的补码:0001 1011
 * 右移三位:***0 0011(右侧超出的"011"被舍弃掉)
 * 无符号右移左侧空出来的部分用0来填充,得到:
 * 0000 0011
 * 换算成十进制为3
 */
byte b = -16 >>> 28;
/*
 *在Java中参与位运算的整数实际上为int类型(32位二进制数),在上面的例子中,为了易于理解,因此以8位二进制形式来表示。
 *这里我们还原为32位二进制来分析。 
 *-16的原码:10000000 00000000 00000001 00000000 
 *-16的反码:11111111 11111111 11111110 11111111
 *-16的补码:11111111 11111111 11111111 00000000
 * 右移28位:******** ******** ******** ****1111
 * 左侧空白用0填充,得到:
 * 00000000 00000000 00000000 00001111
 * 换算成十进制为15
 */

System.out.println(a);	//输出:3
System.out.println(b);	//输出:15

注意:在PHP中,没有无符号右移运算符(>>>)。 此外,众所周知,在大多数编程语言中,byte类型为8位二进制,int类型为32位二进制,如果我们将一个int类型的数左移或右移的位数超过了32位会怎么样呢? 实际上,当位移的位数超过数据类型的位数限制时,这样的运算是没有意义的。为了避免这种情况的出现,当位移的位数(bits)大于等于数据类型最大位数(limit)的限制时,编程语言会用bits除以limit,然后用得到的余数来替代bits(当然,编程语言为了效率,实际上用的是位运算:bits & (limit - 1),这里用除法求余来说明,以便于读者理解。)。请参考下面的例子。

// Java的int类型为32位二进制数
int x = 16 >> 33; // 33 % 32 = 1, 相当于:16 >> 1
int y = 17 << 34; // 34 % 32 = 2, 相当于:17 << 2
int z = -9 >>> 56;// 56 % 32 = 24, 相当于:-19 >>> 24

System.out.println(x); // 输出:8
System.out.println(y); // 输出:68
System.out.println(z); // 输出:255
  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

0 条评论

撰写评论