1 找出一个整数的二进制表示位中1的个数
如:36 100100,其中有2个'1'。
首先来看一个最牛的算法,没有跳转,可以并行处理的:
#define POW(c) (1<<(c))
None.gif
#define MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))
None.gif
#define ROUND(n, c) (((n) & MASK(c)) + ((n) >> POW(c) & MASK(c)))
None.gif
None.gif
int bit_count(unsigned int n)
{
     n
= ROUND(n, 0);
     n
= ROUND(n, 1);
     n
= ROUND(n, 2);
     n
= ROUND(n, 3);
     n
= ROUND(n, 4);
    
return n;
}
其实是分治算法的应用:
1 0 1 1 1 1 0 0 0 1 1 0 0 0 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1
0 1
1 0
1 0
0 0
0 1
0 1
0 0
1 0
0 1
1 0
1 0
0 1
1 0
1 0
1 0
1 0
0 0 1 1
0 0 1 0
0 0 1 0
0 0 1 0
0 0 1 1
0 0 1 1
0 1 0 0
0 1 0 0
0 0 0 0 0 1 0 1
0 0 0 0 0 1 0 0
0 0 0 0 0 1 1 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0
1位数目计数的好方法是,首先设置每个2位字段为原来的两个位的和,然后,求相临2位字段的和,把结果放入相应的4位字段,以此类推.应此,对32位无符号整数,实际上要做的是
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f);
x = (x & 0x00ff00ff) + ((x >> 8) & 0x00ff00ff);
x = (x & 0x0000ffff) + ((x >> 16) & 0x0000ffff);
上面的算法中:
MASK(0) = 0x55555555 = 01010101010101010101010101010101 b
None.gifMASK(
1) = 0x33333333 = 00110011001100110011001100110011
b
None.gifMASK(
2) = 0x0f0f0f0f = 00001111000011110000111100001111
b
None.gifMASK(
3) = 0x00ff00ff = 00000000111111110000000011111111
b
None.gifMASK(
4) = 0x0000ffff = 00000000000000001111111111111111 b
ROUND中对n的处理:(n & MASK) + (n >> POW & MASK)
-------------------
普通的算法
int bit_count(unsigned int n)
{
    
int count;
    
for(count = 0; n; n >>= 1)
    
{
         count
+= n & 1;
     }

    
return count;
}
int bit_count(unsigned int n)
{
    
int count;
    
for(count = 0; n; n &= n - 1)
    
{
         count
++;
     }

    
return count;
}
解析:
如何只数'1'的个数?如果一个数字至少包含一个'1'位,那么这个数字减1将从最低位开始依次向高位借位,直到遇到第一个不为'0'的位。依次借位使得经过的位由原来的'0'变为'1',而第一个遇到的那个'1'位则被借位变为'0'。
36 d = 100100 b
36-1 d = 100011 b
如果最低位本来就是'1',那么没有发生借位。
现在把这2个数字做按位与:n & n-1的结果:
36 & 36-1 d = 100000 b去掉了后面的1。
现在再做一次100000 & 011111 = 0。所有的1都去掉。最后计数出来就是1的个数。
n&(n-1)还有一个妙用,用于判断一个数是否是2的幂. 其特点就是 只有最高位为1,其余位都为0. 1特殊为2的0次幂.0要排除.
则n&(n-1) == 0 即为2的n次幂
2 位运算的一些用法
用位运算实现两个数的交换
x ^=y;
y^=x;
x^=y;
原因:两次异或运算即为原值.
求绝对值:
int abs( int x )
{
int y ;
y = x >> 31 ;
return (x^y)-y ;        //or: (x+y)^y
}
3 位运算与大小端问题
用宏实现小端到大端格式的转换:
unsigned long int htonl(unsigned long int hostlong) {
#if __BYTE_ORDER==__LITTLE_ENDIAN
return (hostlong>>24) | ((hostlong&0xff0000)>>8) |
((hostlong&0xff00)<<8) | (hostlong<<24);
#else
return hostlong;
#endif
}

linux 中对16位, 32位高低调换的做法:
#define __bswap_constant_16(x) \
     ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))

#define __bswap_constant_32(x) \
     ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) |       \
      (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))

嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
0x4000
0x4001
存放内容
0x34
0x12
而在 Big-endian 模式 CPU 内存中的存放方式则为:
内存地址
0x4000
0x4001
存放内容
0x12
0x34
32bit 宽的数 0x12345678 Little-endian 模式 CPU 内存中的存放方式(假设从地址 0x4000 开始存放)为:
内存地址
0x4000
0x4001
0x4002
0x4003
存放内容
0x78
0x56
0x34
0x12
而在 Big-endian 模式 CPU 内存中的存放方式则为:
内存地址
0x4000
0x4001
0x4002
0x4003
存放内容
0x12
0x34
0x56
0x78
判断大小端的方法:
1)
联合体 union 的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了 CPU 对内存采用 Little-endian 还是 Big-endian 模式读写。
int checkCPU( )
{
              union w
           {  
                  int a;
                  char b;
           } c;
    c.a = 1;
    return(c.b ==1);
}
返回真:小端格式,否则大端格式.
short int x;
char x0,x1;
x=0x1122;
x0=((char*)&x)[0]; //低地址单元
x1=((char*)&x)[1]; //高地址单元
若x0=0x11,则是大端; 若x0=0x22,则是小端......