C++描述的位运算总结

2021.05.07更新:添加异或运算的性质
2020.03.23更新:添加位运算符~的应用
2020.02.24更新:添加目录、调整格式。
2020.01.04更新:反码的缺点和使用,以及补码的优点。


一、前言

之前大一上课的时候,老师说位运算不考,当时就没有认真去学习。后来学算法的时候,发现大佬的代码中总会掺杂着一些位运算操作,为了理解那些代码,前前后后也看了几次位运算操作,但是没有很好地记录下来。现在又遇到了,借此机会写个总结。

由于位运算都是直接对整数在内存中的二进制数进行操作的,所以位运算的效率会相对比较高。

二、数的二进制表示

二进制数最高位为符号位,0表示正,1表示负(一般我们都不写出来)。所以C语言中的int(32位整数)的范围是 − 2 31 -2^{31} 231 2 31 − 1 2^{31}-1 2311,第32位用来做符号位了。
正数的二进制表示即为它的原码,负数的二进制表示为它的补码。

1. 原码

把一个整数的绝对值转换为二进制数,再加上符号位就是该整数的原码。例如:
+9的原码为0 0001001(即为+9的二进制表示)
-9的原码为1 0001001

2. 反码

正数的反码与它的原码相同,负数的反码符号位不变,数值部分为原码的按位取反。例如:
+9的反码为0 0001001
-9的反码为1 1110110

由于反码0的表示不唯一、表示数的范围比补码少一个最小负数、运算时必须考虑循环进位,因此反码在计算机中很少被使用,有时用作数码变换的中间表示形式。

3. 补码

正数的补码与它的原码相同,负数的补码为它的反码+1。例如:
+9的补码为0 0001001
-9的补码为1 1110111(即为-9的二进制表示)

因为补码0的表示是唯一的,于是减少了+0和-0之间的转换;少占用了一个编码表示,使得补码能比原码多表示一个最小负数。

三、运算符

1. 按位与 &

对两个等长的(不够在前面用0补)二进制数进行按位与运算,如果对应位都为1,则该位为1,否则该位为0。
例如:求9&3,将它们转换为二进制数(以下都是先这样操作)

1001  
0011  
-----
0001

应用:

  1. 通常我们可以用n&1来判断n是奇数还是偶数,因为奇数的二进制最后一位都是1。

    #include <iostream>
    using namespace std;
    int main()
    {
    	for (int i = 0; i < 10; i++)
    	    if (i & 1) cout << i << " ";
    	return 0;
    }
    

    输出结果
    1 3 5 7 9

  2. n&(-n) 求非负数在二进制表示下最低位1以及它后面的0构成的数值。(这一求解应用在树状数组中)

  3. 对于任意整数 x x x,令 x = x & ( x − 1 ) x=x \&(x-1) x=x&(x1),该运算将 x x x 的二进制表示的最后一个 1 1 1 变成 0 0 0。于是,我们可以利用该性质快速求一个数二进制位有多少个1。

    #include <iostream>
    using namespace std;
    int main()
    {
    	int x, i;
    	cin >> x;
    	for (i = 0; x != 0; i++)
    		x &= (x - 1);
    	cout << i << endl;
    	return 0;
    }
    

2. 按位或 |

对两个等长的(不够在前面用0补)二进制数进行按位或运算,如果对应位都为0,则该位为0,否则该位为1。
例如:求9|3

1001  
0011  
----- 
1011

3. 按位取反 ~

~a表示对a的二进制表示的每一位进行取反,即0变为1,1变为0。例如:求~9

1001  
-----
0110

应用:
用于求相反数,公式为:~n + 1

#include <stdio.h>
int main ()
{
    int n = 10;
    printf("%d ", ~n + 1);
    return 0;
}

输出结果
-10

4. 按位异或 ^

对等长二进制模式按位或二进制数的每一位执行逻辑按位异或操作,操作的结果是如果某位不同则该位为1, 否则该位为0。
(摘自百度百科)

例如:求9^3(很多新手会误以为是 9 3 9^3 93

1001  
0011  
-----
1010

⊕ \oplus 为异或运算,异或运算满足以下性质:

  1. x ⊕ x = 0 x \oplus x = 0 xx=0
  2. x ⊕ y = y ⊕ x x \oplus y = y \oplus x xy=yx(交换律);
  3. ( x ⊕ y ) ⊕ z = x ⊕ ( y ⊕ z ) (x \oplus y) \oplus z = x \oplus (y \oplus z) (xy)z=x(yz)(结合律);
  4. x ⊕ y ⊕ y = x x \oplus y \oplus y = x xyy=x(自反性);
  5. ∀ i ∈ Z ∀ i ∈ Z , 有 4 i ⊕ ( 4 i + 1 ) ⊕ ( 4 i + 2 ) ⊕ ( 4 i + 3 ) = 0 \forall i \in Z∀i∈Z,有 4i \oplus (4i+1) \oplus (4i+2) \oplus (4i+3) = 0 iZiZ4i(4i+1)(4i+2)(4i+3)=0

应用:

  1. 利用按位异或的自反性,我们可以不需要用辅助变量也能实现两个数的交换(装逼用【被打】)
    #include <iostream>
    using namespace std;
    int main()
    {
    	int a = 10, b = 5;
    	a = a ^ b;
    	b = a ^ b;
    	a = a ^ b;
    	cout << a << " " << b << endl;
    	return 0;
    }
    

    输出结果
    5 10

4. 左移 <<

a<<n 表示将 a 的二进制表示数向左移动 n 位,即在后面添加 n 个 0。同样,也就相当于 a 乘以 2 n 2^n 2n

#include <iostream>
using namespace std;
int main()
{
	for (int i = 0; i < 10; i++)
		cout << (1<<i) << " ";
	return 0;
}

输入结果
1 2 4 8 16 32 64 128 256 512

5. 右移 >>

a>>n 与上面的左移刚好相反,表示将a的二进制表示数往右移动n位,即直接去掉a后面的n位,相当于a除以 2 n 2^n 2n。通常我们可以利用 n>>=1 来代替 n /= 2来提高运算效率。

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值