写在前面:本文章来自于在学习过程中的总结,供大家参考。因水平有限,博客中难免会有不足,恳请大佬们不吝赐教!
文章目录
位运算概览
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为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 |
---|---|---|---|---|
10000010 | 11111101 | 11111110 | 00000001 | 00000010 |
另外,整数的绝对值还可以这样求解:
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 |
---|---|---|---|
10 | 1010 | 1001 | 1000 |
20 | 10100 | 10011 | 10000 |
#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
由于水平有限,博客难免会有不足,恳请大佬们不吝赐教!