位操作基础篇之位操作全面总结

  1 Title:       位操作基础篇之位操作全面总结
  2 Author:     MoreWindows
  3 E-mail:      morewindows@126.com
  4 KeyWord:   C/C++ 位操作 位操作技巧 判断奇偶 交换两数 变换符号 求绝对值 位操作压缩空间 筛素数 位操作趣味应用 位操作笔试面试
  5 
  6 位操作篇共分为基础篇和提高篇,基础篇主要对位操作进行全面总结,帮助大家梳理知识。提高篇则针对各大IT公司如微软、腾讯、百度、360等公司的笔试面试题作详细的解答,使大家能熟练应对在笔试面试中位操作题目。
  7 
  8       下面就先来对位操作作个全面总结,欢迎大家补充。
  9 
 10 在计算机中所有数据都是以二进制的形式储存的。位运算其实就是直接对在内存中的二进制数据进行操作,因此处理数据的速度非常快。
 11 
 12 在实际编程中,如果能巧妙运用位操作,完全可以达到四两拨千斤的效果,正因为位操作的这些优点,所以位操作在各大IT公司的笔试面试中一直是个热点问题。因此本文将对位操作进行如下方面总结:
 13 
 14       一. 位操作基础,用一张表描述位操作符的应用规则并详细解释。
 15 
 16       二. 常用位操作小技巧,有判断奇偶、交换两数、变换符号、求绝对值。
 17 
 18       三. 位操作与空间压缩,针对筛素数进行空间压缩。
 19 
 20       四. 位操作的趣味应用,列举了位操作在高低位交换、二进制逆序、二进制中1的个数以及缺失的数字这4种趣味应用。
 21 
 22 希望读者能认真学习和亲自上机输入代码进行实验,相信通过本文及适当的练习可以使你对位操作有更加深入的了解,在笔试面试中遇到位操作相关试题能更加从容。
 23 
 24 一. 位操作基础
 25 基本的位操作符有与、或、异或、取反、左移、右移这6种,它们的运算规则如下所示:
 26 
 27 符号
 28   描述
 29   运算规则                        by MoreWindows
 30  
 31 &      
 32  33  两个位都为1时,结果才为1
 34  
 35 |  
 36  37  两个位都为0时,结果才为0
 38  
 39 ^     
 40  异或
 41  两个位相同为0,相异为1
 42  
 43 ~    
 44  取反
 45  0变1,1变0
 46  
 47 <<  
 48  左移
 49  各二进位全部左移若干位,高位丢弃,低位补0
 50  
 51 >> 
 52  右移
 53  各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)
 54  
 55 
 56 
 57 注意以下几点:
 58 
 59 1.  在这6种操作符,只有~取反是单目操作符,其它5种都是双目操作符。
 60 
 61 2.  位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。
 62 
 63 3.  对于移位操作,在微软的VC6.0和VS2008编译器都是采取算术称位即算术移位操作,算术移位是相对于逻辑移位,它们在右移操作中都一样,低位补0即可,但在左移中逻辑移位的高位补0而算术移位的高位是补符号位。如下面代码会输出-4和3。
 64 
 65 [cpp] view plaincopyprint?int a = -15, b = 15;  
 66 printf("%d %d\n", a >> 2, b >> 2);  
 67     int a = -15, b = 15;
 68     printf("%d %d\n", a >> 2, b >> 2); 
 69 因为15=0000 1111(二进制),右移二位,最高位由符号位填充将得到0000 0011即3。-15 = 1111 0001(二进制),右移二位,最高位由符号位填充将得到1111 1100即-4(见注1)。
 70 
 71 4.  位操作符的运算优先级比较低,因为尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙的结果。比如要得到像1,35,9这些2^i+1的数字。写成int a = 1 << i + 1;是不对的,程序会先执行i + 1,再执行左移操作。应该写成int a = (1 << i) + 1;
 72 
 73 5.  另外位操作还有一些复合操作符,如&=、|=、 ^=、<<=、>>= 74 
 75  
 76 
 77 二. 常用位操作小技巧
 78 下面对位操作的一些常见应用作个总结,有判断奇偶、交换两数、变换符号及求绝对值。这些小技巧应用易记,应当熟练掌握。
 79 
 80 1.判断奇偶
 81 只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if (a & 1 == 0)代替if (a % 2 == 0)来判断a是不是偶数。
 82 
 83 下面程序将输出0到100之间的所有奇数。
 84 
 85 [cpp] view plaincopyprint?for (i = 0; i < 100; ++i)   
 86     if (i & 1)   
 87         printf("%d ", i);   
 88 putchar('\n');  
 89     for (i = 0; i < 100; ++i)
 90         if (i & 1)
 91             printf("%d ", i);
 92     putchar('\n'); 
 93 2.交换两数
 94 一般的写法是:
 95 
 96 [cpp] view plaincopyprint?void Swap(int &a, int &b)   
 97 {   
 98     if (a != b)   
 99     {   
100         int c = a;   
101         a = b;   
102         b = c;   
103     }   
104 }  
105 void Swap(int &a, int &b)
106 {
107     if (a != b)
108     {
109         int c = a;
110         a = b;
111         b = c;
112     }
113 } 
114 可以用位操作来实现交换两数而不用第三方变量:
115 
116 [cpp] view plaincopyprint?void Swap(int &a, int &b)   
117 {   
118     if (a != b)   
119     {   
120         a ^= b;   
121         b ^= a;   
122         a ^= b;   
123     }   
124 }  
125 void Swap(int &a, int &b)
126 {
127     if (a != b)
128     {
129         a ^= b;
130         b ^= a;
131         a ^= b;
132     }
133 } 
134 可以这样理解:
135 
136 第一步  a^=b 即a=(a^b);
137 
138 第二步  b^=a 即b=b^(a^b),由于^运算满足交换律,b^(a^b)=b^b^a。由于一个数和自己异或的结果为0并且任何数与0异或都会不变的,所以此时b被赋上了a的值。
139 
140 第三步 a^=b 就是a=a^b,由于前面二步可知a=(a^b),b=a,所以a=a^b即a=(a^b)^a。故a会被赋上b的值。
141 再来个实例说明下以加深印象。int a = 13, b = 6;
142 
143 a的二进制为 13=8+4+1=1101(二进制)
144 
145 b的二进制为 6=4+2=110(二进制)
146 
147 第一步 a^=b  a = 1101 ^ 110 = 1011;
148 
149 第二步 b^=a  b = 110 ^ 1011 = 1101;即b=13
150 
151 第三步 a^=b  a = 1011 ^ 1101 = 110;即a=5
152 
153 3.变换符号
154 变换符号就是正数变成负数,负数变成正数。
155 
156 如对于-11和11,可以通过下面的变换方法将-11变成11
157 
158       1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)
159 
160 同样可以这样的将11变成-11
161 
162       0000 1011(二进制) –取反-> 0000 1010(二进制) –加1-> 1111 0101(二进制)
163 
164 因此变换符号只需要取反后加1即可。完整代码如下:
165 
166 [cpp] view plaincopyprint?//by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )      
167 #include <stdio.h>    
168 int SignReversal(int a)   
169 {   
170     return ~a + 1;   
171 }   
172 int main()   
173 {   
174     printf("对整数变换符号 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");   
175     int a = 7, b = -12345;   
176     printf("%d  %d\n", SignReversal(a), SignReversal(b));   
177     return 0;   
178 }  
179 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  
180 #include <stdio.h>
181 int SignReversal(int a)
182 {
183     return ~a + 1;
184 }
185 int main()
186 {
187     printf("对整数变换符号 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");
188     int a = 7, b = -12345;
189     printf("%d  %d\n", SignReversal(a), SignReversal(b));
190     return 0;
191 } 
192 4.求绝对值
193 位操作也可以用来求绝对值,对于负数可以通过对其取反后加1来得到正数。对-6可以这样:
194 
195       1111 1010(二进制) –取反->0000 0101(二进制) -加1-> 0000 0110(二进制)
196 
197 来得到6。
198 
199 因此先移位来取符号位,int i = a >> 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1。完整代码如下:
200 
201 [cpp] view plaincopyprint?//by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )    
202 int my_abs(int a)   
203 {   
204     int i = a >> 31;   
205     return i == 0 ? a : (~a + 1);   
206 }  
207 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )
208 int my_abs(int a)
209 {
210     int i = a >> 31;
211     return i == 0 ? a : (~a + 1);
212 }
213  
214 现在再分析下。对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:
215 
216 [cpp] view plaincopyprint?//by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )    
217 int my_abs(int a)   
218 {   
219     int i = a >> 31;   
220     return ((a ^ i) - i);   
221 }  
222 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )
223 int my_abs(int a)
224 {
225     int i = a >> 31;
226     return ((a ^ i) - i);
227 }
228  
229 注意这种方法没用任何判断表达式,而且有些笔面试题就要求这样做,因此建议读者记住该方法(^_^讲解过后应该是比较好记了)。
230 
231  
232 
233 三. 位操作与空间压缩
234 筛素数法在这里不就详细介绍了,本文着重对筛素数法所使用的素数表进行优化来减小其空间占用。要压缩素数表的空间占用,可以使用位操作。下面是用筛素数法计算100以内的素数示例代码(注2):
235 
236 [cpp] view plaincopyprint?//by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )    
237 #include <stdio.h>    
238 #include <memory.h>    
239 const int MAXN = 100;   
240 bool flag[MAXN];   
241 int primes[MAXN / 3], pi;   
242 //对每个素数,它的倍数必定不是素数。    
243 //有很多重复如flag[10]会在访问flag[2]和flag[5]时各访问一次    
244 void GetPrime_1()   
245 {   
246     int i, j;   
247     pi = 0;   
248     memset(flag, false, sizeof(flag));   
249     for (i = 2; i < MAXN; i++)   
250         if (!flag[i])   
251         {   
252             primes[pi++] = i;   
253             for (j = i; j < MAXN; j += i)   
254                 flag[j] = true;   
255         }   
256 }   
257 void PrintfArray()   
258 {   
259     for (int i = 0; i < pi; i++)   
260         printf("%d ", primes[i]);   
261     putchar('\n');   
262 }   
263 int main()   
264 {   
265     printf("用筛素数法求100以内的素数\n-- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");     
266     GetPrime_1();   
267     PrintfArray();   
268     return 0;   
269 }  
270 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )
271 #include <stdio.h>
272 #include <memory.h>
273 const int MAXN = 100;
274 bool flag[MAXN];
275 int primes[MAXN / 3], pi;
276 //对每个素数,它的倍数必定不是素数。
277 //有很多重复如flag[10]会在访问flag[2]和flag[5]时各访问一次
278 void GetPrime_1()
279 {
280     int i, j;
281     pi = 0;
282     memset(flag, false, sizeof(flag));
283     for (i = 2; i < MAXN; i++)
284         if (!flag[i])
285         {
286             primes[pi++] = i;
287             for (j = i; j < MAXN; j += i)
288                 flag[j] = true;
289         }
290 }
291 void PrintfArray()
292 {
293     for (int i = 0; i < pi; i++)
294         printf("%d ", primes[i]);
295     putchar('\n');
296 }
297 int main()
298 {
299     printf("用筛素数法求100以内的素数\n-- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");  
300     GetPrime_1();
301     PrintfArray();
302     return 0;
303 } 
304 运行结果如下:
305 
306 
307 
308 在上面程序是用bool数组来作标记的,bool型数据占1个字节(8位),因此用位操作来压缩下空间占用将会使空间的占用减少八分之一。
309 
310 下面考虑下如何在数组中对指定位置置1,先考虑如何对一个整数在指定位置上置1。对于一个整数可以通过将1向左移位后与其相或来达到在指定位上置1的效果,代码如下所示:
311 
312 [cpp] view plaincopyprint?//在一个数指定位上置1    
313 int j = 0;   
314 j |=  1 << 10;   
315 printf("%d\n", j);  
316     //在一个数指定位上置1
317     int j = 0;
318     j |=  1 << 10;
319     printf("%d\n", j);
320  
321 同样,可以1向左移位后与原数相与来判断指定位上是0还是1(也可以将原数右移若干位再与1相与)。
322 
323 [cpp] view plaincopyprint?   //判断指定位上是0还是1    
324 int j = 1 << 10;   
325 if ((j & (1 << 10)) != 0)   
326     printf("指定位上为1");   
327 else   
328     printf("指定位上为0");  
329     //判断指定位上是0还是1
330     int j = 1 << 10;
331     if ((j & (1 << 10)) != 0)
332         printf("指定位上为1");
333     else
334         printf("指定位上为0"); 
335 扩展到数组上,我们可以采用这种方法,因为数组在内存上也是连续分配的一段空间,完全可以“认为”是一个很长的整数。先写一份测试代码,看看如何在数组中使用位操作:
336 
337 [cpp] view plaincopyprint?//by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )      
338 #include <stdio.h>    
339 int main()   
340 {   
341     printf("     对数组中指定位置上置位和判断该位\n");   
342     printf("--- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");   
343     //在数组中在指定的位置上写1    
344     int b[5] = {0};   
345     int i;   
346     //在第i个位置上写1    
347     for (i = 0; i < 40; i += 3)   
348         b[i / 32] |= (1 << (i % 32));   
349     //输出整个bitset    
350     for (i = 0; i < 40; i++)   
351     {   
352         if ((b[i / 32] >> (i % 32)) & 1)   
353             putchar('1');   
354         else    
355             putchar('0');   
356     }   
357     putchar('\n');   
358     return 0;   
359 }  
360 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  
361 #include <stdio.h>
362 int main()
363 {
364     printf("     对数组中指定位置上置位和判断该位\n");
365     printf("--- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");
366     //在数组中在指定的位置上写1
367     int b[5] = {0};
368     int i;
369     //在第i个位置上写1
370     for (i = 0; i < 40; i += 3)
371         b[i / 32] |= (1 << (i % 32));
372     //输出整个bitset
373     for (i = 0; i < 40; i++)
374     {
375         if ((b[i / 32] >> (i % 32)) & 1)
376             putchar('1');
377         else 
378             putchar('0');
379     }
380     putchar('\n');
381     return 0;
382 } 
383 运行结果如下:
384 
385 
386 
387 可以看出该数组每3个就置成了1,证明我们上面对数组进行位操作的方法是正确的。因此可以将上面筛素数方法改成使用位操作压缩后的筛素数方法:
388 
389 [cpp] view plaincopyprint?//使用位操作压缩后的筛素数方法    
390 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )     
391 #include <stdio.h>    
392 #include <memory.h>    
393 const int MAXN = 100;   
394 int flag[MAXN / 32];   
395 int primes[MAXN / 3], pi;   
396 void GetPrime_1()   
397 {   
398     int i, j;   
399     pi = 0;   
400     memset(flag, 0, sizeof(flag));   
401     for (i = 2; i < MAXN; i++)   
402         if (!((flag[i / 32] >> (i % 32)) & 1))   
403         {   
404             primes[pi++] = i;   
405             for (j = i; j < MAXN; j += i)   
406                 flag[j / 32] |= (1 << (j % 32));   
407         }   
408 }   
409 void PrintfArray()   
410 {   
411     for (int i = 0; i < pi; i++)   
412         printf("%d ", primes[i]);   
413     putchar('\n');   
414 }   
415 int main()   
416 {   
417     printf("用位操作压缩后筛素数法求100以内的素数\n-- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");     
418     GetPrime_1();   
419     PrintfArray();   
420     return 0;   
421 }  
422 //使用位操作压缩后的筛素数方法
423 //by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) 
424 #include <stdio.h>
425 #include <memory.h>
426 const int MAXN = 100;
427 int flag[MAXN / 32];
428 int primes[MAXN / 3], pi;
429 void GetPrime_1()
430 {
431     int i, j;
432     pi = 0;
433     memset(flag, 0, sizeof(flag));
434     for (i = 2; i < MAXN; i++)
435         if (!((flag[i / 32] >> (i % 32)) & 1))
436         {
437             primes[pi++] = i;
438             for (j = i; j < MAXN; j += i)
439                 flag[j / 32] |= (1 << (j % 32));
440         }
441 }
442 void PrintfArray()
443 {
444     for (int i = 0; i < pi; i++)
445         printf("%d ", primes[i]);
446     putchar('\n');
447 }
448 int main()
449 {
450     printf("用位操作压缩后筛素数法求100以内的素数\n-- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) --\n\n");  
451     GetPrime_1();
452     PrintfArray();
453     return 0;
454 } 
455 同样运行结果为:
456 
457 
458 
459 另外,还可以使用C++ STL中的bitset类来作素数表。筛素数方法在笔试面试出现的几率还是比较大的,能写出用位操作压缩后的筛素数方法无疑将会使你的代码脱颖而出,因此强烈建议读者自己亲自动手实现一遍,平时多努力,考试才不慌。
460 
461  
462 
463 四. 位操作的趣味应用
464 位操作有很有趣的应用,下面列举出一些,欢迎读者补充。
465 
466 1.  高低位交换 
467 给出一个16位的无符号整数。称这个二进制数的前8位为“高位”,后8位为“低位”。现在写一程序将它的高低位交换。例如,数34520用二进制表示为:
468 
469       10000110 11011000
470 
471 将它的高低位进行交换,我们得到了一个新的二进制数:
472 
473       11011000 10000110
474 
475 它即是十进制的55430。
476 
477 这个问题用位操作解决起来非常方便,设x=34520=10000110 11011000(二进制) 由于x为无符号数,右移时会执行逻辑右移即高位补0,因此x右移8位将得到00000000 10000110。而x左移8位将得到11011000 00000000。可以发现只要将x>>8与x<<8这两个数相与就可以得到11011000 10000110。用代码实现非常简洁:
478 
479 [cpp] view plaincopyprint?//高低位交换 by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )      
480 #include <stdio.h>    
481 template <class T>   
482 void PrintfBinary(T a)   
483 {   
484     int i;   
485     for (i = sizeof(a) * 8 - 1; i >= 0; --i)   
486     {   
487         if ((a >> i) & 1)   
488             putchar('1');   
489         else    
490             putchar('0');   
491         if (i == 8)   
492             putchar(' ');   
493     }   
494     putchar('\n');   
495 }   
496 int main()   
497 {   
498     printf("高低位交换 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");   
499    
500     printf("交换前:    ");   
501     unsigned short a = 3344520;   
502     PrintfBinary(a);   
503    
504     printf("交换后:    ");   
505     a = (a >> 8) | (a << 8);   
506     PrintfBinary(a);   
507     return 0;   
508 }  
509 //高低位交换 by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  
510 #include <stdio.h>
511 template <class T>
512 void PrintfBinary(T a)
513 {
514     int i;
515     for (i = sizeof(a) * 8 - 1; i >= 0; --i)
516     {
517         if ((a >> i) & 1)
518             putchar('1');
519         else 
520             putchar('0');
521         if (i == 8)
522             putchar(' ');
523     }
524     putchar('\n');
525 }
526 int main()
527 {
528     printf("高低位交换 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");
529 
530     printf("交换前:    ");
531     unsigned short a = 3344520;
532     PrintfBinary(a);
533 
534     printf("交换后:    ");
535     a = (a >> 8) | (a << 8);
536     PrintfBinary(a);
537     return 0;
538 } 
539 运行结果如下:
540 
541 
542 
543 2.  二进制逆序
544 我们知道如何对字符串求逆序,现在要求计算二进制的逆序,如数34520用二进制表示为:
545 
546       10000110 11011000
547 
548 将它逆序,我们得到了一个新的二进制数:
549 
550       00011011 01100001
551 
552 它即是十进制的7009。
553 
554     回顾下字符串的逆序,可以从字符串的首尾开始,依次交换两端的数据。在二进制逆序我们也可以用这种方法,但运用位操作的高低位交换来处理二进制逆序将会得到更简洁的方法。类似于归并排序的分组处理,可以通过下面4步得到16位数据的二进制逆序:
555 
556 第一步:每2位为一组,组内高低位交换
557 
558       10 00 01 10  11 01 10 00 
559 
560   -->01 00 10 01 11 10 01 00
561 
562 第二步:每4位为一组,组内高低位交换
563 
564       0100 1001 1110 0100 
565 
566   -->0001 0110 1011 0001
567 
568 第三步:每8位为一组,组内高低位交换
569 
570       00010110 10110001 
571 
572   -->01100001 00011011
573 
574 第四步:每16位为一组,组内高低位交换
575 
576       01100001 00011011 
577 
578   -->00011011 01100001
579 
580 对第一步,可以依次取出每2位作一组,再组内高低位交换,这样有点麻烦,下面介绍一种非常有技巧的方法。先分别取10000110 11011000的奇数位和偶数位,空位以下划线表示。
581 
582       原 数   10000110 11011000
583 
584       奇数位 1_0_0_1_ 1_0_1_0_
585 
586       偶数位 _0_0_1_0 _1_1_0_0
587 
588 将下划线用0填充,可得
589 
590       原 数   10000110 11011000
591 
592       奇数位 10000010 10001000
593 
594       偶数位 00000100 01010000
595 
596 再将奇数位右移一位,偶数位左移一位,此时将这两个数据相与即可以达到奇偶位上数据交换的效果了。
597 
598       原 数          10000110 11011000
599 
600       奇数位右移 01000011 01101100
601 
602       偶数位左移 0000100 010100000
603 
604       相与得到     01001000 11100100
605 
606 可以看出,结果完全达到了奇偶位的数据交换,再来考虑代码的实现——
607 
608       取x的奇数位并将偶数位用0填充用代码实现就是x & 0xAAAA
609 
610       取x的偶数位并将奇数位用0填充用代码实现就是x & 0x5555
611 
612 因此,第一步就用代码实现就是:
613 
614        x = ((x & 0xAAAA) >> 1) | ((x & 0x5555) << 1);
615 
616 类似可以得到后三步的代码。完整程序如下:
617 
618 [cpp] view plaincopyprint?//二进制逆序 by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )      
619 #include <stdio.h>    
620 template <class T>   
621 void PrintfBinary(T a)   
622 {   
623     int i;   
624     for (i = sizeof(a) * 8 - 1; i >= 0; --i)   
625     {   
626         if ((a >> i) & 1)   
627             putchar('1');   
628         else    
629             putchar('0');   
630         if (i == 8)   
631             putchar(' ');   
632     }   
633     putchar('\n');   
634 }   
635 int main()   
636 {   
637     printf("二进制逆序 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");   
638    
639     printf("逆序前:    ");   
640     unsigned short a = 34520;   
641     PrintfBinary(a);   
642    
643     printf("逆序后:    ");    
644     a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1);   
645     a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);   
646     a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);   
647     a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);   
648     PrintfBinary(a);   
649 }  
650 //二进制逆序 by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  
651 #include <stdio.h>
652 template <class T>
653 void PrintfBinary(T a)
654 {
655     int i;
656     for (i = sizeof(a) * 8 - 1; i >= 0; --i)
657     {
658         if ((a >> i) & 1)
659             putchar('1');
660         else 
661             putchar('0');
662         if (i == 8)
663             putchar(' ');
664     }
665     putchar('\n');
666 }
667 int main()
668 {
669     printf("二进制逆序 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");
670 
671     printf("逆序前:    ");
672     unsigned short a = 34520;
673     PrintfBinary(a);
674 
675     printf("逆序后:    ");    
676     a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1);
677     a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2);
678     a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4);
679     a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8);
680     PrintfBinary(a);
681 } 
682 运行结果如下:
683 
684 
685 
686 3.  二进制中1的个数
687 统计二进制中1的个数可以直接移位再判断,当然像《编程之美》书中用循环移位计数或先打一个表再计算都可以。本文详细讲解一种高效的方法。以34520为例,可以通过下面四步来计算其二进制中1的个数二进制中1的个数。
688 
689 第一步:每2位为一组,组内高低位相加
690 
691       10 00 01 10  11 01 10 00 
692 
693   -->01 00 01 01  10 01 01 00
694 
695 第二步:每4位为一组,组内高低位相加
696 
697       0100 0101 1001 0100 
698 
699   -->0001 0010 0011 0001
700 
701 第三步:每8位为一组,组内高低位相加
702 
703       00010010 00110001 
704 
705   -->00000011 00000100
706 
707 第四步:每16位为一组,组内高低位相加
708 
709       00000011 00000100 
710 
711   -->00000000 00000111
712 
713 这样最后得到的00000000 00000111即7即34520二进制中1的个数。类似上文中对二进制逆序的做法不难实现第一步的代码:
714 
715        x = ((x & 0xAAAA) >> 1) + (x & 0x5555);
716 
717 好的,有了第一步,后面几步就请读者完成下吧,先动动笔再看下面的完整代码:
718 
719 [cpp] view plaincopyprint?//二进制中1的个数  by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )     
720 #include <stdio.h>    
721 template <class T>   
722 void PrintfBinary(T a)   
723 {   
724     int i;   
725     for (i = sizeof(a) * 8 - 1; i >= 0; --i)   
726     {   
727         if ((a >> i) & 1)   
728             putchar('1');   
729         else    
730             putchar('0');   
731         if (i == 8)   
732             putchar(' ');   
733     }   
734     putchar('\n');   
735 }   
736 int main()   
737 {   
738     printf("二进制中1的个数 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");   
739        
740     unsigned short a = 34520;   
741     printf("原数    %6d的二进制为:  ", a);   
742     PrintfBinary(a);   
743        
744     a = ((a & 0xAAAA) >> 1) + (a & 0x5555);   
745     a = ((a & 0xCCCC) >> 2) + (a & 0x3333);   
746     a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F);   
747     a = ((a & 0xFF00) >> 8) + (a & 0x00FF);      
748     printf("计算结果%6d的二进制为:  ", a);      
749     PrintfBinary(a);   
750     return 0;   
751 }  
752 //二进制中1的个数  by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) 
753 #include <stdio.h>
754 template <class T>
755 void PrintfBinary(T a)
756 {
757     int i;
758     for (i = sizeof(a) * 8 - 1; i >= 0; --i)
759     {
760         if ((a >> i) & 1)
761             putchar('1');
762         else 
763             putchar('0');
764         if (i == 8)
765             putchar(' ');
766     }
767     putchar('\n');
768 }
769 int main()
770 {
771     printf("二进制中1的个数 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");
772     
773     unsigned short a = 34520;
774     printf("原数    %6d的二进制为:  ", a);
775     PrintfBinary(a);
776     
777     a = ((a & 0xAAAA) >> 1) + (a & 0x5555);
778     a = ((a & 0xCCCC) >> 2) + (a & 0x3333);
779     a = ((a & 0xF0F0) >> 4) + (a & 0x0F0F);
780     a = ((a & 0xFF00) >> 8) + (a & 0x00FF);    
781     printf("计算结果%6d的二进制为:  ", a);    
782     PrintfBinary(a);
783     return 0;
784 } 
785 运行结果如下:
786 
787 
788 
789 可以发现巧妙运用分组处理确实是解决很多二进制问题的灵丹妙药。
790 
791 4.  缺失的数字
792 很多成对出现数字保存在磁盘文件中,注意成对的数字不一定是相邻的,如2, 3, 4, 3, 4, 2……,由于意外有一个数字消失了,如何尽快的找到是哪个数字消失了?
793 
794 由于有一个数字消失了,那必定有一个数只出现一次而且其它数字都出现了偶数次。用搜索来做就没必要了,利用异或运算的两个特性——1.自己与自己异或结果为0,2.异或满足交换律。因此我们将这些数字全异或一遍,结果就一定是那个仅出现一个的那个数。 示例代码如下:
795 
796 [cpp] view plaincopyprint?//缺失的数字  by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )     
797 #include <stdio.h>    
798 int main()   
799 {   
800     printf("缺失的数字 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");   
801        
802     const int MAXN = 15;   
803     int a[MAXN] = {1, 347, 6, 9, 13, 65, 889, 712, 889, 347, 1, 9, 65, 13, 712};   
804     int lostNum = 0;   
805     for (int i = 0; i < MAXN; i++)   
806         lostNum ^= a[i];   
807     printf("缺失的数字为:  %d\n", lostNum);      
808     return 0;   
809 }  
810 //缺失的数字  by MoreWindows( http://blog.youkuaiyun.com/MoreWindows ) 
811 #include <stdio.h>
812 int main()
813 {
814     printf("缺失的数字 --- by MoreWindows( http://blog.youkuaiyun.com/MoreWindows )  ---\n\n");
815     
816     const int MAXN = 15;
817     int a[MAXN] = {1, 347, 6, 9, 13, 65, 889, 712, 889, 347, 1, 9, 65, 13, 712};
818     int lostNum = 0;
819     for (int i = 0; i < MAXN; i++)
820         lostNum ^= a[i];
821     printf("缺失的数字为:  %d\n", lostNum);    
822     return 0;
823 }
824  
825  
826 
827 位操作是一种高效优美的方法,同时由于其高效的运算性能和掌握难度较大,位操作运算一直是笔试面试时的热门话题之一。本文详细总结了位操作的方法与技巧并列出4种位操作趣味应用,如果读者能亲自上机实现代码,相信必能更好应对笔试和面试时可能遇到的位操作问题。
828 
829 另外,欢迎各位能提供笔试面试中的位操作相关的题目给我,我将会在提高篇中加入这些。谢谢大家。
830 
831  
832 
833  
834 
835 注1.int类型一般占4字节,32位。因此15准确表达为
836 
837 15=00000000 00000000 00000000 00001111(二进制)
838 
839 -15准确表达为
840 
841 -15=11111111 11111111 11111111 11110001(二进制)
842 
843 为了简便起见,文章中使用15=00001111(二进制),-15=11110001(二进制)。
844 
845  
846 
847 注2.这种筛素数的方法很朴素,会多次重复访问数据,有什么办法能改进一下吗?请看《改进的筛素数方法》一文。
848 
849  
850 
851 转载请标明出处,原文地址:http://blog.youkuaiyun.com/morewindows/article/details/7354571
View Code

 

转载于:https://www.cnblogs.com/cwbo-win/articles/3393951.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值