程序中的全部数在计算机内存中都是以二进制的形式储存的。位运算直接对整数在内存中的二进制位进行操做。因为位运算直接对内存数据进行操做,不须要转成十进制,所以处理速度很是快。算法
(1),与(&)运算编程
"&"运算一般用于二进制取位操做,例如一个数 & 1 的结果就是取二进制的最末位。这能够用来判断一个整数的奇偶,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数。windows
(2),或(|)运算数据结构
”|“运算一般用于二进制特定位上的无条件赋值,例如一个数 | 1 的结果就是把二进制最末位强行变成1。若是须要把二进制最末位变成0,对这个数| 1以后再减1就能够了,其实际意义就是把这个数强行变成最接近的偶数。函数
(3),异或(^)运算加密
"^"运算一般用于对二进制的特定一位进行取反操做,由于异或能够这样定义: 0和1 异或0都不变,异或1则取反。对象
"^"运算的逆运算是它自己,也就是说两次异或同一个数最后结果不变,即(a^b)^b = a。”^"运算能够用于简单的加密,好比你想对一个朋友说1314520,但怕别人知道,因而双方约定拿生日19800205做为密钥,1314520 ^ 19800205 = 20590165, 因而就把20590165告诉这个朋友。你的朋友再次计算20590165^19800205的值,获得1314520,因而它就明白了你的意图。内存
加法和减法互为逆运算,而且加法知足交换律。能够写出一个不须要临时变量的swap函数it
void swap(int a, int b)io
{
a = a + b;
b = a - b;
a = a - b;
}
因为^的逆运算是它自己,因而就有了一个很特别的swap过程:
void swap(int a, int b)
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
(4),取反(~)运算
“~”运算的定义是把内存中的0和1所有取反。使用“~”运算时要格外当心,须要注意整数类型有没有符号。若是“~”的对象是无符号整数(不能表示负数),那么获得的值就是它与该类型上界的差。由于无符号类型的数是用00到$FFFF依次表示的。下面的程序返回65435。
#include <stdio.h>
int main()
{
unsigned short a = 100;
a = ~a;
printf("%d\n",a);
return 0;
}
若是“~”的对象是有符号的整数,状况就不同了, 稍后会提到。
(5),左移(<<)运算
a << b 表示把 a 转为二进制后左移 b 位(在后面添b个0)。如100的二进制为1100100,而110010000转成十进制是400.能够看出 a<< b的值实际上就是a 乘以 2的 b次方,由于在二进制数后面添加一个0 就至关于该数乘以2.
一般认为 a <<1 比 a * 2更快,由于前者是更底层的操做,所以程序中乘以2的操做请尽可能用左移一位来代替。
定义一些常量可能会用到 <<运算。能够方便地用(1 << 16) -1来表示65535.不少算法和数据结构要求数据规模必须是2的幂,此时能够用<<来定义Max_N等常量。
(6),右移(>>)运算
和<<类似,a >> b 表示二进制右移b位(去掉末b位),至关于a除以2的b次方(取整)。此外也常常用 >> 1来代替除以2,好比二分查找, 堆的插入操做等。想办法用>>代替除法运算,可使程序效率大大提升。最大公约数的二进制算法用除以2操做来代替mod运算,效率能够提升60%。
常见的二进制位的变换操做
功能 | 示例 | 位运算 |
去掉最后一位 | (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--->101001,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 | (100101111--->10010110) | x & ( x - 1) |
把右边连续的1变成0 | (100101111--->100100000) | x & ( x + 1) |
把右起第一个0 变成1 | (100101111--->100111111) | x | (x + 1) |
把右边连续的0变成1 | (11011000--->11011111) | x | (x - 1) |
去掉右起第一个1 的左边 | (100101000--->1000) | x & (x ^(x -1)) |
取右边连续的1 | (100101111--->1111) | (x ^(x+1)) >> 1 |
在实际的编程过程当中,每每会用一个整数的不一样位表示不一样的数据信息。在访问该整数时,就须要经过位运算来得到或者改变整数的某几位数值。好比在windows中建立时使用的create数据结构:
struct{
PIO_SECURITY_CONTEXT SecurityCOntext;
ULONG Options;
USHORT POINTER_ALIGNMENT FileAttributes;
USHORT ShareAccess;
ULONG POINTER_ALIGNMENT Ealength;
PVOID EaBuffer;
LARGE_INTEGER AllocationSize;
}Create;
一般会引用其中的Options 以下:
Data->Iopb->Parameters.Create.Options
ULONG Options 是一个Windows文件建立过程当中的无符号长整数,指示在建立和打开文件时的不一样选项。其中高8位指示了CreateDisposition参数(如FILE_OPEN, FILE_CREATE), 低24位指示了CreateOptions参数(如FILE_DELETE_ON_CLOSE)。
为了获得CreateDisposition的值,采起下面的位操做:
(Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff;
将该整数右移24位,再与0xff进行与操做, 便可得到CreateDisposition的值。
(2)将第n 位置位或清零。
#define BITN (1 << n)
置位: a |= BITN;
清零: a &= ~BITN;
(3)清除整数a最右边的1
方法: a & (a -1)
问题: 如何判断整数x的二进制中含有多少个1?
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x & (x -1);
}
return countx;
}
(4)将一个整数对齐到n:
a = (a + n -1) & ~(n -1)