内容目录
在学习编程语言的位运算相关知识之前,请先点击查看另外一篇文章《计算机二进制以及原码、反码、补码入门详解》,以补充必要的基础知识。
在前面的文章中,我们已经介绍了计算机二进制以及原码、反码、补码的相关基础知识,这里我们结合具体的编程语言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
0 条评论
撰写评论