Menu Close

什么是C语言的按位运算符和移位运算符(Bitwise Operators)?

位运算符(Bitwise Operators)用于在位(bit)级别操作数据,也称为位( bit ) 级别编程。

位(bit)是计算机中处理数据的最小单位,其取值只能是 0 或 1。字节(Byte)是计算机处理数据的基本单位,通常系统中一个字节为 8 位。即:1 Byte=8 bit。

位运算符分按位运算符和移位运算符。按位运算符&,|,^,~。

移位运算符: <<, >>

位移运算符可以在二进制的基础上对数字进行平移, 以加快计算过程。

以下是“ C”编程语言提供的按位运算符的列表:

Operator (位运算符) Meaning (意义)
& Bitwise AND operator (位运算符与,按位与)
| Bitwise OR operator  (位运算符,按位或)
^ Bitwise exclusive OR operator (按位异或)
~  Complement Operator  (位运算,一元运算符,二进制取反)
<< Left shift operator (位移运算符左移)
>> Right shift operator (位移运算符右移)

位运算符通常与整数数据类型一起使用,不能直接应用于其他数据类型,例如float,double等。

位逻辑运算符从最右边最低有效位LSB(least significant bit)开始,朝着最左边的最高有效位 (MSB,Most Significant Bit)对数据进行逐位处理。

下表显示了位逻辑运算符的计算结果:

x y x & y x | y x ^ y
0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

按位与 (Bitwise AND)

这是最常用的逻辑按位运算符之一。 它由单个与号(&)表示。 两个整数表达式写在(&)运算符的每一侧。
如果两个位的值均为1,则按位与运算的结果为1;否则为0。 否则,结果始终为0。

让我们考虑一下,我们有2个变量op1和op2,其值如下:

Op1 = 0000 1101
Op2 = 0001 1001

变量op1和op2的AND运算结果为

Result = 0000 1001

如我们所见,两个变量被逐位比较。 每当两个变量中的位的值均为1时,结果将为1否则为0。

按位或 (Bitwise OR)

Op1 = 0000 1101
Op2 = 0001 1001

对变量op1和op2进行按位或运算的结果将为

Result = 0001 1101

如我们所见,两个变量被逐位比较。 每当两个变量中任一位的值为1时,结果将为1否则为0。

按位异或 (Bitwise Exclusive) OR

按位异或用符号(^)表示。 在(^)运算符的每一侧都写入了两个整数表达式。

如果只有一个表达式的值为1,则按位异或运算的结果为1; 否则,结果始终为0。

让我们考虑一下,我们有2个变量op1和op2,其值如下:

Op1 = 0000 1101
Op2 = 0001 1001

按位异或运算的结果

Result=00010100

对两个变量进行逐位比较。 只有一个变量的值为1,则结果为1; 否则结果为0

示例:

#include <stdio.h>
int main() 
{
	int a = 20;	/* 20 = 010100 */  
        int b = 21;	/* 21 = 010101 */
   	int c = 0;           

   	c = a & b;       /* 20 = 010100 */ 
   	printf("AND - Value of c is %d\n", c );

   	c = a | b;       /* 21 = 010101 */
   	printf("OR - Value of c is %d\n", c );

   	c = a ^ b;       /* 1 = 0001 */
   	printf("Exclusive-OR - Value of c is %d\n", c );

   	getch();
}

移位运算符

<< 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 60 << 2 将得到 240,即为 1111 0000
>> 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 60 >> 2 将得到 15,即为 0000 1111

示例:x是带有数据1111的整数表达式。执行移位运算后,结果将是:

x << 2 (left shift) = 1111<<2 = 1100
x >> 2 (right shift) = 1111>>2 = 0011

可以将移位运算符组合在一起,然后将其用于从整数表达式中提取数据。 让我们编写一个程序来演示按位移位运算符的用法。

#include <stdio.h>
int main() {
   int a = 20;	/* 20 = 010100 */  
   int c = 0;           

   c = a << 2;	/* 80 = 1010000 */
   printf("Left shift - Value of c is %d\n", c );

   c = a >> 2;	/*05 = 000101 */
   printf("Right shift - Value of c is %d\n", c );
   return 0;
}

执行左移操作后,该值将变为80,其二进制等效值为1010000。

执行右移操作后,该值将变为5,其二进制等效值为000101。

按位补码运算符 (Bitwise complement operator)

原码、反码和补码

原码:用最高位表示符号位,其余位表示数值位的编码称为原码。其中,正数的符号位为 0,负数的符号位为 1。

正数的原码、反码、补码均相同。

负数的反码:把原码的符号位保持不变,数值位逐位取反,即可得原码的反码。
负数的补码:在反码的基础上加 1 即得该原码的补码。

例如:
+11 的原码为: 0000 1011
+11 的反码为: 0000 1011
+11 的补码为: 0000 1011

-7 的原码为:1000 0111
-7 的反码为:1111 1000
-7 的补码为:1111 1001

注意,对补码再求一次补码操作就可得该补码对应的原码。

正数的补码是其本身,负数的补码为其反码加 1。 它是一元运算符。当我们对任何位执行取反操作时,所有1都变为0,反之亦然。

如果我们有一个包含0000 1111的整数表达式,那么在执行取反运算后,该值将变为1111 0000。

按位取反运算符用代字号(〜)表示。

应用:~a+1=-a 即对任意数按位取反后加 1,得该数的相反数。

例如,计算 10 按位取反的结果,如下所示:

由于计算机中位运算均是以补码形式操作的,正数的补码是其本身,负数的补码为其反码加 1。

所得显然是负数的补码,对补码 1111 0101 再做一次求补操作,即可得该补码对应的原码。 求 1111 0101 补码的过程如下所示:

反码 1000 1010 –符号位 1 保持不变,数值位按位取反
补码 1000 1011 –反码加1
根据 (补码)补码=原码
故补码1111 0101对应的原码为1000 1011=-11,即:~(10)D =~(0100 0110)B补= (1111 0101)B补=-11

由此可见,~10+1=-11+1=-10,即满足 ~a+1=-a

让我们编写一个程序来演示按位补码运算符的实现:

#include <stdio.h>
int main() {
   int a = 10;	/* 10 = 1010 */  
   int c = 0;           
   c = ~(a);      
   printf("Complement - Value of c is %d\n", c );
   return 0;
}

按位运算符和移位运算符详细举例:


#include <stdio.h>
main() {
   unsigned int x = 48;	/* 48 = 0011 0000 */  
   unsigned int y = 13;	/* 13 = 0000 1101 */
   int z = 0;           

   z =x & y;       /* 0 = 0000 0000 */ 
   printf("Bitwise AND Operator - x & y = %d\n", z );

   z = x | y;       /* 61 = 0011 1101 */
   printf("Bitwise OR Operator - x | y = %d\n", z );

   z= x^y;       /* 61 = 0011 1101 */
   printf("Bitwise XOR Operator- x^y= %d\n", z);

   z = ~x;          /*-61 = 1100 0011 */
   printf("Bitwise One's Complement Operator - ~x = %d\n", z);

   z = x << 2;     /* 192 = 1100 0000 */
   printf("Bitwise Left Shift Operator x << 2= %d\n", z );

   z= x >> 2;     /* 12 = 0000 1100 */
   printf ("Bitwise Right Shift Operator x >> 2= %d\n", z );}

负数的二进制表示

我们已经知道计算机中,所有数据最终都是使用二进制数表达。
我们也已经学会如何将一个10进制数如何转换为二进制数以及如何将如何将一个16进制数如何转换为二进制数,详见下图。

不过,我们仍然没有学习一个负数如何用二进制表达。
比如,假设有一 int 类型的数,值为5,那么,我们知道它在计算机中表示为:00000000 00000000 00000000 00000101
5转换成二制是101,不过int类型的数占用4字节(32位),所以前面填了一堆0。
现在想知道,-5在计算机中如何表示?
在计算机中,负数以其正值的补码形式表达
什么叫补码呢?这得从原码,反码说起。
原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
比如 00000000 00000000 00000000 00000101 是 5的 原码。
反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。
取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)
比如:将00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。
称:11111111 11111111 11111111 11111010是 00000000 00000000 00000000 00000101 的反码。
反码是相互的,所以也可称:
11111111 11111111 11111111 11111010 和00000000 00000000 00000000 00000101 互为反码。
补码:反码加1称为补码。
也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。
比如:00000000 00000000 00000000 00000101的反码是:11111111 11111111 11111111 11111010。
那么,补码为:
11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。
转换为十六进制:0xFFFFFFFB。
再举一例,我们来看整数-1在计算机中如何表示。
假设这也是一个int类型,那么:
1、先取1的原码:00000000 00000000 00000000 00000001
2、得反码:     11111111 11111111 11111111 11111110
3、得补码:     11111111 11111111 11111111 11111111
可见,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF。