基础语法
位运算针对整型数据转化成二进制按位操作,计算速度快于其他运算操作。在进行位运算时要注意类型转换,double和float要显式强转成int等,而char等“小整型”会自动隐式转换为int型,或更大。由于部分位运算对有符号整型(的符号位)的操作依赖于本地计算机系统,会带来极大不确定性,故一般之应用于无符号整型。位运算均遵循左结合律,优先级高于逻辑运算符,低于比较运算符,且从高到低依次为&、^、| 。注意不要把位运算和逻辑运算混淆(例如&和&&不是一回事)。
(以下假定char8位,int32位)
~位取反(~expr)
每一位都取反。(0变1,1变0)注意一般用于无符号整型。
eg. unsigned char bits =0227 【10010111】
变成【11111111,11111111,11111111,01101000】(先提升成int再按位取反).
<<左移(expr1<<expr2)
左侧操作数exper1为被移动数,右侧操作数exper2为移动位数(以十进制来表达,必定为正数),补位用0,因为移动是按二进制来计算的,所以移动k位实际上就是变成原来的2的k倍。(右移就是1/2的k次方)(在不超出类型范围限制的情况下,如果超限直接被移除出去).要特别注意,如果原数是负数,那么原数左侧的补位有可能是0或1,由本地计算机决定。
eg. unsigned char bits=0233(八进制) 【10011011】
exper<<8变成【00000000,00000000,10011011,00000000】
>>右移(expr1>>expr2)
操作类似左移,看例子(沿用上面的)。
exper>>3变成【00000000,00000000,00000000,00010011】
&位与(exper1&exper2)
&、|、^均是针对转化成二进制的整型数据的两数对应的每一位进行操作。
&:1&1=1,else=0;
|:0|0,else=1;
^:1^0=1,0^1=1,else=0;(相同为0,相异为1)
eg. unsigned char exper1 =0145【01100101】,
unsigned char exper2 =0257【10101111】.
exper1&exper2变成【前位为零,00100101】
|位或(exper1|exper2)
exper1|exper2变成【前位为零,11101111】
^位异或(exper1^exper2)
exper1^exper2变成【前位为零,11001010】
强转(慎用)
type (expr);//函数式强转
(type) expr;//c风格强转
只知道知识是没用的,刷题练一下。这篇文章我会时常维护,不定期加入一些自己的思考和补一些题。
简单技巧
&运算通常用于二进制取位操作
例如一个数&1的结果就是取出二进制最末位,而配合左移运算,可以取出任意一位:x&(1<<(k-1))。
| 运算通常用于二进制特定位上的无条件赋值:
例如一个数|1的结果就是把二进制最末位强行变为1,如果需要把二进制最末位变成0,对这个数 |1之后再减一就可以了,其实际意义就是把这个数强行变成最近接的偶数。配合左移运算,可以对任意一位进行同种操作。
^运算通常用于对二进制的特定一位进行取反操作:
^运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即x^y^y=x;
^运算可以用于简单的加密,比如原始值int a = 19880516;密钥 int key =1314520; 进行加密 int data=key^a = 20665500;解密 data^key == a;
^运算还可以实现两个值的交换而不需要中间变量,例如:
///先看加减法中交换实现
void swap(long int &a,long int &b)
{
a = a+b;
b = a-b;
a = a-b;
}
///b=a^b^b==a; a=a^b^a==b;
void swap(long int &a,long int &b)
{
a = a^b;
b = a^b;
a = a^b;
}
~运算全部取反:
~会把内存中的0和1全部取反,所以要格外小心,你需要注意整数类型有没符号,如果~的对象是无符号整数(不能表示负数),那么他的值就是它与它的上界限的之差,因为无符号类型的数是用0000到FFFF依次表示的。
左右移运算用于调取和乘除:
通常认为a<<1比a*2更快,因此程序中乘以2的操作尽量用左移一位来代替。
且我们经常用>>1来代替 /2(div 2),比如二分查找、堆的插入操作等等(gcd中用除以2(>>1)操作来代替慢的出奇的%(mod)运算可以极大提高效率)。想办法用>>代替除法运算可以使程序的效率大大提高。
左右移运算配合其他位运算可以实现对某一特定二进制位的操作。
具体应用
去掉最后一位 101101->10110 x>>1
在最后加一个0 101101->1011010 x<<1
在最后加一个1 101101->1011011 (x<<1)+1
把最后一位变成1 101100->101101 x | 1
把最后一位变成0 101101->101100 (x |1) - 1
最后一位取反 101101->101100 x ^ 1
把右数第K位变成1 101001->101101,k=3 x | (1<<(k-1))
把右数第K位变成0 101101->101101,k=3 x & ~(1<<(k-1))
右数第k位取反 101001->101101,k=3 x ^ (1<<(k-1))
取末三位 1101101->101 x &7
取末k位 1101101->1101,k=5 x & (1<<k-1)
取右数第k位 1101101->1,k=4 x >> (k-1)&1
把末k位变成1 101001->101111,k=4 x|(1<<k-1)
末k位取反 101001->100110,k=4 x^(1<<k-1)
把右边连续的1变成0 100101111->100100000 x&(x+1)
把右起第一个0变成1 100101111->100111111 x|(x+1)
把右边连续的0变成1 11011000->11011111 x|(x-1)
取右边连续的1 100101111->1111 (x^(x+1))>>1
去掉右起第一个1的左边 100101000->1000 x&(x^(x-1))
&1取二进制末位:
应用:判定奇偶
&1为1是奇数,&1为0为偶数
x&(x-1) 消去最后一位有效1:
应用1:O(1)时间判断某个数是否是2的幂:
2的幂的二进制表示只有最高位是1。如果x&(x-1)为0 则是2的幂,反之不是。
应用2:计算在一个 32 位的整数的二进制表示中有多少个 1 :
循环使用x&(x-1)并计数。
应用3:计算将整数A转换为B,需要改变多少个bit位:
即计算A和B有多少位是不同的,先A^B,异或后相同为0,不同为1。问题转化成A^B二进制表示中有多少个 1。
二进制子集枚举:
给定可数集,枚举所有子集:每个元素都有选(1)和不选(0)两种状态,用二进制记录与表示。
x^y^y=x:
(1^1^1=1 0^0^0=0 1^0^0=1 0^1^1=0)
用于特殊集合不重元素求取。
参考
https://blog.csdn.net/wuguai4/article/details/7311953
https://blog.csdn.net/deaidai/article/details/78167367