一、移位运算
左移运算
左移运算符“<<”是双目运算符。其功能把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,
高位丢弃,低位补0。
例如: a<<4 指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。
右移运算
右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。
例如:设 a=15,a>>2 表示把000001111右移为00000011(十进制3)。
应该说明的是,对于有符号数,在右移时,符号位将随同移动。
当为正数时, 最高位补0,而为负数时,符号位为1,最高位是补0或是补1 取决于编译系统的规定。Turbo C和很多系统规定为补1。
二、使用按位异或运算符http://www.cnblogs.com/mz121star/archive/2008/05/24/bit.html
按位异或运算符的使用频率远远低于&和 |
运算符,有关它的使用例子也比较少。但它的一个重要应用是图形编程。在屏幕中创建动画的一种方式是绘制一个对象,删除它,再在一个新位置重新绘制。如果要
求动画很平滑,这个过程就需要重复得很快,其中删除是一个重要的部分。我们并不想删除和重新绘制整个屏幕,因为这非常费时,屏幕也会出现闪烁。最理想的
是,只删除屏幕上要移动的对象。使用所谓的异或模式就可以做到这一点,得到非常平滑的 动画。
异或模式的理念是,在屏幕上用给定的颜色绘制对象,如果接着用背景色重新绘制它,它就会消失。
以异或模式在屏幕上绘制对象时,每次绘制对象的颜色会自动在为对象所选的颜色和背景色之间来回变化。得到这一效果的关键是使用按位异或运算符快速而
自动地改变颜色。它使用异或运算符的一个特性,即如果对两个值进行异或操作,再对所得的结果和一个原始值执行异或操作,就会得到另一个值。这听起来很复
杂,下面就用一个例子来说明。
假定要在前景色(这里使用红色)和背景色(白色)之间来回切换。颜色通常用3个8位值来表示,分别对应于红、蓝、绿的亮度,存储在一个4字节的整数
中。通过改变颜色中的红、蓝和绿的比例,就可以获得大约1600万种不同的颜色,包括从白色到黑色之间的所有颜色。纯红色是0xFF0000,这时红色成
分设置为其最大值,其他两种颜色即蓝色和绿色的成分设置为0。在相同颜色模式下,绿色就是0xFF00,蓝色是0xFF。在白色中,红、蓝、绿的成分具有
相同的最大值,即0xFFFFFF。
可以用下面的语句定义表示红色和白色的变量:
unsigned long red=0XFF0000UL; //Color red
unsigned long white=0XFFFFFFUL; //Color white-RGB all maximum
接着创建一个掩码,用于在红色和白色之间来回切换,并把包含绘图颜色的变量初始化为红色:
unsigned long mask=red ^ white; //Mask for switching colors
unsigned long draw_color=red; //Drawing color
变量mask初始化为要切换的两种颜色的按位异或操作结果,因此:
red 1111 1111 0000 0000 0000 0000
white 1111 1111 1111 1111 1111 1111
mask(即red ^ white) 0000 0000 1111 1111 1111 1111
如果对mask和red执行异或操作,就会得到white,如果对mask和white执行异或操作,就会得到red。因此,使用draw_color中的颜色绘制对象,就可以通过下面的语句切换颜色:
draw_color ^= mask; //Switch the drawing color
当draw_color包含red时,其执行过程如下:
draw_color 1111 1111 0000 0000 0000 0000
mask 0000 0000 1111 1111 1111 1111
draw_color ^ mask 1111 1111 1111 1111 1111 1111
显然,draw_color的值从红色变为白色。再次执行这个语句,就会把颜色改回为红色:
draw_color^=mask; //Switch the drawing color
其执行过程如下:
draw_color 1111 1111 1111 1111 1111 1111
mask 0000 0000 1111 1111 1111 1111
draw_color ^ mask 1111 1111 0000 0000 0000 0000
draw_color又变成了红色。这个技术适用于任意两种颜色,当然它实际上与特定颜色没有一点关系,可以把它用于切换任意一对整型数值。
-------------------------------------------------------------------------------
- typedef long long off_t;
-
#define STREAM_BUFFER_SIZE 2048
-
newpos = pos & (~((off_t)STREAM_BUFFER_SIZE - 1));
-
-
如果pos = 2048, newpos的值是多少?
这是mplayer 中代码,这是要做什么?
是检验一个数是不是2次幂的高效的方法
--------------------------------------------------------------------------------
位运算应用口诀
清零取数要用与,某位置一可用或
若要取反和交换,轻轻松松用异或
移位运算
要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形。
2 "<<" 左移:右边空出的位上补0,左边的位将从字头挤掉,其值相当于乘2。
3 ">>"右移:右边的位被挤掉。对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统。
4 ">>>"运算符,右边的位被挤掉,对于左边移出的空位一概补上0。
位运算符的应用 (源操作数s 掩码mask)
(1) 按位与-- &
1 清零特定位 (mask中特定位置0,其它位为1,s=s&mask)
2 取某数中指定位 (mask中特定位置1,其它位为0,s=s&mask)
(2) 按位或-- |
常用来将源操作数某些位置1,其它位不变。 (mask中特定位置1,其它位为0 s=s|mask)
(3) 位异或-- ^
1 使特定位的值取反 (mask中特定位置1,其它位为0 s=s^mask)
2 不引入第三变量,交换两个变量的值 (设 a=a1,b=b1)
目标 操作 操作后状态
a=a1^b1 a=a^b a=a1^b1,b=b1
b=a1^b1^b1 b=a^b a=a1^b1,b=a1
a=b1^a1^a1 a=a^b a=b1,b=a1
即
a ^= b
b ^= a
b ^= b
这样3步,即可交换两个数字
且没有占用空间.
二进制补码运算公式:
(看到这些功能,似乎没必要了解补码的原理)
-x = ~x + 1 = ~(x-1)
~x = -x-1
-(~x) = x+1
~(-x) = x-1
x+y = x - ~y - 1 = (x|y)+(x&y)
x-y = x + ~y + 1 = (x|~y)-(~x&y)
x^y = (x|y)-(x&y)
x|y = (x&~y)+y
x&y = (~x|y)-~x
x==y: ~(x-y|y-x)
x!=y: x-y|y-x
x< y: (x-y)^((x^y)&((x-y)^x))
x<=y: (x|~y)&((x^y)|~(y-x))
x< y: (~x&y)|((~x|y)&(x-y))//无符号x,y比较
x<=y: (~x|y)&((x^y)|~(y-x))//无符号x,y比较
应用举例
(1) 判断int型变量a是奇数还是偶数
a&1 = 0 偶数
a&1 = 1 奇数
(2) 取int型变量a的第k位 (k=0,1,2……sizeof(int)),即a>>k&1 (先右移再与1)
(3) 将int型变量a的第k位清0,即a=a&~(1<
(4) 将int型变量a的第k位置1,即a=a|(1<
(5) int型变量循环左移k次,即a=a<>16-k (设sizeof(int)=16)
(6) int型变量a循环右移k次,即a=a>>k|a<<16-k (设sizeof(int)=16)
(7)整数的平均值
对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,但是我们知道它们的平均值是肯定不会溢出的,我们用如下算法:
- int average(int x, int y)
- {
- return (x & y) + ( (x^y)>>1 );
- }
(8)对于一个数 x >= 0,判断是不是2的幂。
- boolean power2(int x)
- {
- return ( (x&(x-1))==0) && (x!=0);
- }
(9)不用temp交换两个整数
- void swap(int x , int y)
- {
- x ^= y;
- y ^= x;
- x ^= y;
- }
(10)计算绝对值
- int abs( int x )
- {
- int y ;
- y = x >> 31 ;
- return (x^y)-y ;
- }
(11)取模运算转化成位运算 (在不产生溢出的情况下)
a % (2^n) 等价于 a & (2^n - 1)
(12)乘法运算转化成位运算 (在不产生溢出的情况下)
a * (2^n) 等价于 a<< n
(13)除法运算转化成位运算 (在不产生溢出的情况下)
a / (2^n) 等价于 a>> n
例: 12/8 == 12>>3
(14) a % 2 等价于 a & 1
(15) if (x == a)
x= b;
else x= a;
等价于 x= a ^ b ^ x;
(16) x 的 相反数 表示为 (~x+1)
(17)输入2的n次方:1 << 19
(18)乘除2的倍数:千万不要用乘除法,非常拖效率。只要知道左移1位就是乘以2,右移1位就是除以2就行了。比如要算25 * 4,用25 << 2就好啦
实例
功能 | 示例 | 位运算
----------------------+---------------------------+--------------------
去掉最后一位 | (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变成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))
判断奇数 (x&1)==1
判断偶数 (x&1)==0
---------------------------------------------------------------------------------------------
这是一道在《编程之美》上看的题目,这道题目也曾经被多家公司作为面试题,非常有趣,认真做这道题会得到很多的收获,所以整理一下这道题的解法,做个备忘。
题目的要求很简单:对于一个字节(8bit)的变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能的高。
方法一:
涉及二进制数的直接处理,并且要求执行效率,应该很容易想到位运算,于是第一种方法浮现出来,思路是将这个数的每一位与“1”做与
运算,根据1&1 = 1,1&0 = 0的规则,很容易得出结果,代码如下:
int binCount ( unsigned int v )
{
int count = 0;
while(v) /*直到v为0 循环结束*/
{
count += v&0x01;
v>>1; /*v向右移一位,为下一次与运算做准备*/
}
return count;
}
方法二:
方法二提供了一个效率更高的算法,但是这个方法就不容易想到,当然如果对数字敏感,也许能想到,反正我是没想到。上代码看看:
int binCount ( usigned int v )
{
int count = 0;
while ( v )
{
++count;
i&= (i-1);/*这是重点!*/
}
return count;
}
方法三:
方法三提供的方法是查表法,速度也很快,具体做法就是将所有的情况都列在一个表里,直接查表的速度很快,其实这是典型的空间换时间的方法,详细代码如下:
查表法,因为只有数据8bit,直接建一张表,包含各个数中1的个数,然后查表就行。复杂度O(1)。
int countTable[256] = { 0, 1, 1, 2, 1, ..., 7, 7, 8 };
int Count(int v)
{
return countTable[v];
}
当然这些方法只是一个思路,还有很多的其他方法,这里就不说了。在wiki上还有好多高效率的算法,就是不容易懂。
------------------------------------------------------------------------------------
引子:《程序员面试宝典》2C的P37的面试例题中有这样一道题:
unsigned char a = oxA5;
unsigned char b=~a>>4;
printf("%d",b);
书上给的答案是正确的,但是讲解是错误的:“>>”的优先级高于“~”。这个题作者之所以能够歪打正着的作对最后的结果,是因为在位运算中,不存8位的位运算,(X86,VC9以及GCC的编译环境中)编译器会把这个8位的字符提升为32位进行运算(实验结果,未找到文献)。
先给出一些位运算的一些应用:
1.补齐至某个数的倍数
stl里面的二级空间配置器里,对于小内存的分配是有独特的策略的(详见侯捷:《STL源码剖析》),当申请的内存小于128字节的时候,会将这个
内存大小提升为8的倍数,例如申请的内存是7个字节,那么配置器会把这个内存提升为8个字节。那么你将会怎么写这个简单的补齐至某个数的倍数函数呢?
enum {_ALLGN=8};
static size_t ROUND_UP(size_t BYTES)
{
return (BYTES+_ALLGN-1) & ~(_ALLGN-1);
}
仔细想想,这是为什么?
2.计算一个二进制串中“1”的个数(详见《编程之美》P119)
int Count_1(unsigned char i){
int count=0;
while(i!=0)
{
count+=i & 0x01;
i>>=1;
}
return count;
}
将二进制数与1进行&操作,能判断出最后一位是否为1。用这个方法也能判断出这个数的奇偶性,不再详述。
这个题在编程之美上还有更好的解法,算法复杂度只与1的个数相关:
想法引导:n如果只剩下一个“1” <--> n是2的m(m=0,1,2...ln n)次幂 <-->n&(n-1)==1
int count_1(unsigned char a)
{
int count=0;
while(a!=0)
{
a&=(a-1);
count++;
}
return count;
}