位运算

写在前面:本文章来自于在学习过程中的总结,供大家参考。因水平有限,博客中难免会有不足,恳请大佬们不吝赐教!


位运算概览

符号描述运算规则
&两个位都为1时,结果才为1
|两个位都为0时,结果才为0
^异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

➢>>>运算符将用0填充高位; >>运算符用符号位填充高位,没有<<<运算符
➢对于int型,1<<35与1<<3是相同的,而左边的操作数是long型时需对右侧操作数模64

按位与运算符(&)

定义:参加运算的两个数据,按二进制位进行“与”运算。

运算规则:

0&0=0 0&1=0 1&0=0 1&1=1
总结:两位同时为1,结果才为1,否则结果为0。

例如:3&5 即 0000 0011& 0000 0101 = 0000 0001,因此 3&5 的值得1。

注意:负数按补码形式参加按位与运算。

与运算的用途:

1)清零

如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。

2)取一个数的指定位

比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。

3)判断奇偶

只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。

按位或运算符(|)

定义:参加运算的两个对象,按二进制位进行“或”运算。

运算规则:

0|0=0 0|1=1 1|0=1 1|1=1
总结:参加运算的两个对象只要有一个为1,其值为1。

例如:3|5即 0000 0011| 0000 0101 = 0000 0111,因此,3|5的值得7。

注意:负数按补码形式参加按位或运算。

或运算的用途:

1)常用来对一个数据的某些位设置为1

比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。

异或运算符(^)

定义:参加运算的两个数据,按二进制位进行“异或”运算。

运算规则:

0^0=0 0^1=1 1^0=1 1^1=0
总结:参加运算的两个对象,如果两个相应位相同为0,相异为1。

异或的几条性质:

1、交换律

2、结合律 (a ^ b) ^ c == a ^ (b ^ c)

3、对于任何数x,都有 x ^ x=0,x ^ 0=x

4、自反性: a ^ b ^ b=a ^ 0=a;

异或运算的用途:

1)翻转指定位

比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。

2)与0相异或值不变

例如:1010 1110 ^ 0000 0000 = 1010 1110

3)交换两个数

void Swap(int &a, int &b){
    if (a != b){
        a ^= b;
        b ^= a;
        a ^= b;
    }
}

取反运算符 (~)

定义:参加运算的一个数据,按二进制进行“取反”运算。
运算规则:

~1=0
~0=1
总结:对一个二进制数按位取反,即将0变1,1变0。

异或运算的用途:

1)使一个数的最低位为零

使a的最低位为0,可以表示为:a & 1。1的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0。因为“ ~”运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。

左移运算符(<<)

定义:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。

若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

右移运算符(>>)

定义:将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。

原码、反码、补码

原码:一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补1,称为原码。
比如 00000000 00000000 00000000 00000101 是 5的 原码。
10000000 00000000 00000000 00000101 是 -5的 原码。
反码: 正数的反码与原码相同,负数的反码为对该数的原码除符号位外各位取反。

比如:正数00000000 00000000 00000000 00000101 的反码还是 00000000 00000000 00000000 00000101

负数10000000 00000000 00000000 00000101每一位取反(除符号位),得11111111 11111111 11111111 11111010。
称:11111111 11111111 11111111 11111010 是 10000000 00000000 00000000 00000101 的反码。
反码是相互的,所以也可称:
10000000 00000000 00000000 00000101 和 11111111 11111111 11111111 11111010互为反码。
补码: 正数的补码与原码相同,负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1(即反码加1).
比如:10000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。
那么,补码为:
11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011

在计算机内,数据都是以二进制形式的补码存储和运算,正数的补码为它的原码,而负数的补码=反码+1
所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。
再举一例,我们来看整数-1在计算机中如何表示。
假设这也是一个int类型,那么:

1、先取-1的原码:10000000 00000000 00000000 00000001
2、得反码: 11111111 11111111 11111111 11111110(除符号位按位取反)
3、得补码: 11111111 11111111 11111111 11111111

可见,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF
C++中的测试代码:
cout << bitset<32>(-1) << endl;

这个知识点的运用可以看:习题2:使用位运算求绝对值

习题

习题1:找出唯一成对的数

1- 1000这1000个数放在含有1001个元素的数组中,只有唯一的
一个元素值重复,其它均只出现一次。每个数组元素只能访问一
次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一
个算法实现?

算法思路:

创建一个数组,最后一个元素为随机数

创建一个随机下标,将该下标的元素与最后一个元素互换

这样第一步就将这个数组创建好了
2.
使用异或进行运算

因为异或有着这样的规律:A^A=0 B^0=B

由此我们可以联想到:累计异或有消除重复的作用

所以我们可以使用(123…1000)(该数组的异或累计)的方式算出那个最终结果(不唯一的数),因为重复数都被异或运算消去了。

代码如下:

#include <iostream>
#include <ctime>
using namespace std;
int main()
{
	int N = 1001;
	int* array = new int[N];
	for (int i = 0; i < N - 1; i++)
	{
		array[i] = i + 1;
	}
	//最后一个数是随机数
	srand((unsigned)time(NULL));
	array[N - 1] = rand() % (N - 1) + 1;
	//随机下标
	int index = rand() % (N - 2);
	//交换两个数
	array[N-1] ^= array[index];
	array[index] ^= array[N - 1];
	array[N - 1] ^= array[index];
	//打印数组
	for (int i = 0; i < N; i++)
	{
		cout << array[i] << " ";
	}
	//计算从1到N-1的异或积累
	int x = 0;
	for (int i = 1; i <= N-1; i++)
	{
		x ^= i;
	}
	//将x与数组元素进行异或
	for (int i = 0; i < N; i++)
	{
		x ^= array[i];
	}
	cout << endl << x << endl;

	cout << "===========" << endl;
	//第二种方法:使用暴力破解,不符合题意(使用了辅助空间)
	int* helper = new int[N];
	for (int i = 0; i < N; i++)
	{
		helper[i] = 0;
	}
	for (int i = 0; i < N; i++)
	{
		helper[array[i]]++;
	}

	for (int i = 0; i < N; i++)
	{
		if (helper[i] == 2)
			cout << i << endl;
	}
	return 0;
}

习题2:使用位运算求绝对值

float求绝对值

out = out << 1;
out = out >> 1;

int求绝对值
因为整型的内部表示是反码,我们不能简单的通过符号位置0求绝对值,下面的算法很好的解决了这个问题:

int out = iNum;
int temp = iNum;
temp = temp >> 31;

out = out ^ temp;
out = out - temp; 

return out;

注:
1)对于代码
temp = temp >> 31;
out = out ^ temp;
out = out - temp;
如果iNum是正数:
temp = temp >> 31; //temp = 0
out = out ^ temp; //与0异或不变
out = out - temp; //减0不变

out的结果就是iNum,即正数的绝对值是其本身,没问题

如果iNum是负数:
temp = temp >> 31; //temp = oxffffffff
out = out ^ temp; //out为iNum求反
out = out - temp; // 此时temp = 0xffffffff = -1, 所以out = out + 1
把一个负数的补码连符号位求反后再加1,就是其绝对值了。比如对于-2来说:

原码反码补码补码全求反再加1
1000001011111101111111100000000100000010

另外,整数的绝对值还可以这样求解:

num=(~num)+1	//全部取反后加1,因为在计算机内部整数的二进制表示为补码
return num;

习题3:二进制中1的个数

请实现-一个函数,输入-一个整数,输出该数二进制表示中1的个数。
例: 9的二进制表示为1001,有2位是1

算法思路:
1.
因为整数有32位,所以我们将每一位都与1做与运算,这样就能算出二进制1的个数。
这里可以分为两种方法,一种是原数也就是要求的那个数做右移位,另一种是1做右移位。(这里分别对应方法1和方法2)
2.
这里重点讲另外一种思路,因为这种算法思路可以衍生很多题目。

我们可以将二进制数中的1都消去,这样统计消去1的次数也就是1的个数。
一个二进制数-1,会把它低位的1消去,低位的0变成1,这时将x-1与x做与运算,就可以将低位的1消去。如此循环往复可以将全部的1消去。
这样讲有点抽象,我们可以看下面的表格:

原数x(十进制)x(二进制)x-1(x-1)&x
10101010011000
20101001001110000
#include <iostream>
#include <bitset>
using namespace std;
int main()
{
	int N;
	cin >> N;
	cout <<bitset<32>(N) << endl;

	int count = 0;	//计数
	//第一种方法:输入的整数右移位
	for (int i = 0; i < 32; i++)
	{
		if ((N >>> i) & 1)
			count++;
	}
	cout << count << endl;

	//第二种方法:1左移位
	count = 0;
	for (int i = 0; i < 32; i++)
	{
		if (N & (1 << i))
			count++;
	}
	cout << count << endl;

	//第三种方法:消去二进制中的1,统计消去次数
	count = 0;
	//减1之后,低位的1会变成0,低位的0会变成1
	while (N!=0)
	{
		//与原数做与运算会消去低位上的1
		N = ((N - 1) & N);
		count++;
	}
	cout << count << endl;
	return 0;
}

参考链接:
https://www.cnblogs.com/yrjns/p/11246163.html
https://www.cnblogs.com/lukelook/p/11274795.html
https://www.cnblogs.com/baiyanhuang/archive/2009/09/16/1730739.html


由于水平有限,博客难免会有不足,恳请大佬们不吝赐教!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值