整理一下位操作,便于自己以后查看。例子全部来源于网络,不能一一指出出处,见谅!注意:以下的例子都是一个具体的题目,有很多的时候并不意味着像下面那样做得到的代码更加高效,应该具体问题具体分析。
1.异或操作:
定义:
异或(xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。运算法则为:a⊕b = (¬a ∧ b) ∨ (a ∧¬b).
0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0。同0异1.与其它语言不同,C语言和C++语言的异或不用xor,而是“^”,其他语言的"^"多表示乘方。
运算法则
(1).a ⊕ b = b ⊕ a;//交换率
(2)a ⊕b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c;//结合率
异或的特性:
(1).0异或任何数=任何数.a=0000,b=1011.a^b=1011,按位异或。
(2).a^a=0.
(3)a^b^a=b.
异或的一些例子:
(1)不引入第三个参数实现两个数的交换:
a=9=1001,b=11=1011.
a=a^b:1001^1011=0010
b=b^a;1011^0010=1001
a=a^b:0010^1001=1011
a=11,b=9.和a=a+b;b=a-b;a=a-b;有点像,位个人觉得就是用了异或特性的第三条。
(2)一个整数数组,除其中某一个元素只出现一次外,其他元素都出现了两次。求这个元素。
运用特性(3),代码如下:
int result=0;
for(int i=0;i<size;++i)
{
result^=num[i];
}
cout<<result;
(3)接上面的那个题,一个整数数组中,除了两个元素只出现一次外,其他的都出现了两次。求这两个元素。
假设这两个元素是a和b,那么按照上面的处理result=a^b;假设a=9,b=11.那么result=0010,那么也就说a和b第三位不同,否则就会为0.那么我们现在把原数组的数按第三位分为两组:0组和1组。则a和b肯定不属于同一组。取某一组的数那么这组数一定是这样的:有a或者b,剩下的数都出现两次。让result的和这组数中所有的进行异或,便可以得到a或者b了。这个方法个人感觉只是提供了一种思路,并不一定是最好的,看题目有没有别的限制。
2.与操作
与操作的法则:1&0=0,1&1=1,0&0=0。全1和一个数求与还是那个数,全0和一个数求与就是置0.
3.或操作
或操作的法则:1|0=1,1|1=1,0|0=0.全0和一个数求或还是那个数,全1和一个数求或就是将那个数置为全1.
位操作的一些综合应用:
(1).用位操作求一个正整数除以7的余数。负整数我还没有试验过,先规定是正整数。
假设这个正整数是a,令x=a>>3,y=a&0x7,即a=(7+1)*x+y,如果x+y>7接着上面的做法做,直到得到的余数小于7为止。除以其他非2的次幂的思路都差不多。
(2).用位操作实现加法.
首先:异或操作相当于两个二进制相加但是没有进位的结果。与操作相当于求两个二进制数相加哪一位会有进位。
int add(int a,int b)
{
int x=a&b;//进位
int y=a^b;//未考虑进位得到的结果
int t_x,t_y;//临时变量
while(x)
{
t_x=x<<1;//进位的结果
t_y=t;
//将进位的结果加到y中
x=t_x&t_y;
y=t_x^t_y;
}
}
(3)判断一个无符号的数是不是2的正整数次幂。
这个问题可以这么考虑:假设这个无符号的数为a,看a&(a-1)的结果,如果a是2的正整数次幂,那么结果应该是0,否则就不为0.
#include <iostream>
using namespace std;
int main()
{
unsigned int a;
cin>>a;
int result=a&(a-1);
if(result)
cout<<"no"<<endl;
else
cout<<"yes"<<endl;
}
这个问题可以进行一下拓展,就是判断一个无符号的正数(还是规定不为0吧,不然减掉1之后不知道表示什么了。)的二进制表示中有多少个1.代码如下:
#include <iostream>
using namespace std;
int main()
{
unsigned int a;
cin>>a;
int count=0;
while(a)
{
count++;
a&=(a-1);
}
cout<<count<<std::endl;
return 0;
}
实际每一次做&操作就去掉a中的一个1.(4).求int的绝对值,int是32位的。
#include <iostream>
using namespace std;
int main()
{
int x;
cin>>x;
int y=x>>31;
cout<<y<<endl;
cout<<(x^y)-y<<endl;
}
理解这段代码,需要好好理解计算机中补码移位的规则。
(5)将一个整数取反,如-8变成8,8变-8.8变-8在计算机中就是连同符号位在内所有位取反然后加1,所有位取反的方法就是和-1做异或。代码如下:
#include <iostream>
using namespace std;
int main()
{
int a,result;
cin>>a;
result=(a^(-1))+1;
cout<<result<<endl;
return 0;
}
(6)高低位交换:给出一个16位的无符号整数。称这个二进制数的前八位为“高位“,后八位为”低位“。现在写一个程序把这个二进制的数的高低位交换。例如;10000110 11011000变成11011000 10000110,设x=34520=10000110 11011000(二进制) 由于x为无符号数,右移时会执行逻辑右移即高位补0,因此x右移8位将得到0000000010000110。而x左移8位将得到11011000 00000000。可以发现只要将x>>8与x<<8这两个数相或就可以得到11011000 10000110。如果是有符号的数,那就要弄清楚补码的移位规则了。
(7)接上一个问题,二进制的逆续。第一步我们可以把这个二进制的数以两位为一组分成若干组,组内高低位交换。第二步以四位一组分成若干组,组内高低位交换,类推。代码如下:
#include <iostream>
using namespace std;
void display(int a)
{
int stack[32]={0};
int top=0;
while(a>0)
{
stack[top++]=a%2;
a/=2;
}
for(int i=31;i>=0;--i)
{
cout<<stack[i];
}
cout<<endl;
}
int main()
{
int a;
cin>>a;
cout<<"before reverse"<<endl;
display(a);
cout<<"after reverse"<<endl;
a=((a>>1)&0x55555555)|((a<<1)&0xAAAAAAAA);
a=((a>>2)&0x33333333)|((a<<2)&0xCCCCCCCC);
a=((a>>4)&0x0F0F0F0F)|((a<<4)&0xF0F0F0F0);
a=((a>>8)&0x00FF00FF)|((a<<8)&0xFF00FF00);
display(a);
}
我这里输出二进制的时候是以32位做标准的,前面添0了。