赞
踩
引言: 位(bit)这个概念在计算机基础or数字电路中可多次寻得其身影,且对于嵌入式开发人员而言也是一份极其重要的知识。理论上可以通过位运算来完成所有的运算和操作,但现在的计算机编程语言大多都不涉及这么细节和底层的操作,因而C语言才成为嵌入式开发的优选。不过对于多数读者而言,或许不会从事嵌入式开发的相关工作,但若习得一些位操作的知识,必然可以有效地提高所编写程序的运行效率。
文章向导
按位与(或)运算符的使用
按位异或运算符的使用
移位运算符的使用
一、按位与(或)运算符的使用
1.基础用法
按位与运算通常用来对某些位清零或保留某些位,而这种用法也被称之为“掩码”,下面以一个字节的数据来举例说明。
char test = 0x96; //10010110
char mask = 0x03; //00000011
test &= mask; //00000010
test |= mask; //10010111 设置位
test &= ~mask; //10010100 清零位
上述程序片段仅是为了说明问题,其中test与mask的值在多次运算中均是使用最初的设定值(实际程序肯定不是如此)。在按位与运算中,不透明的0掩盖了test中相应的位,透明的1则保留了test中相应的位;在按位或运算中,透明的1打开了test中相应的位,不透明的0则对test无影响;在最后所谓的“清零位”操作中,mask中为1的位取反后则清零了test中相应的位,而为0的位取反后则对test中相应的位无影响。
2.实用技巧
先引入一个问题“如何统计出变量a表示为二进制时,其中1或0的个数”?显然,涉及到单个位上的数据问题时,用位运算操作的方式来解决会比较高效和便捷。
下面先给出具体可用的示例程序,接着再剖析其原理(为不致于使读者理解起来过于复杂,故仅贴出main.c的代码, 其余文件中的代码仅给出截图,感兴趣的读者自行阅读):
#include "binbit.h" int fun1(int a, char* str){ //统计a的二进制中1的数目 int count = 0; while(a!=0x00){ itobs(a, str); //整数a转换为二进制字符串 printf("第%d次: ", count); show_bstr(str); //打印每次运算后的二进制字符串 putchar('\n'); a = a & (a-1); //相当于每次消灭一个1 count++; } return count; } int fun2(int a, char* str){ //统计a的二进制中0的数目 int count = 0; while(a!=0xFF){ itobs(a, str); //整数a转换为二进制字符串 printf("第%d次: ", count); show_bstr(str); //打印每次运算后的二进制字符串 putchar('\n'); a = a | (a+1); //相当于每次消灭一个0 count++; } return count; } int main(void){ char bin_str[SIZE + 1]; //+1: 末尾结束字符 printf("(1): %d\n", fun1(0xFF, bin_str)); //统计1的个数 printf("(0): %d\n", fun2(0x00, bin_str)); //统计0的个数 return 0; }
其他代码:
运行结果:
显然,此时问题的关键全都集中于如何正确理解a = a & (a-1); a = a | (a+1) 这两条语句各自的含义。
(1) a = a & (a-1), 为方便说明问题不妨设a=0x03 //00000011
由二进制的减法运算规则可知:若a最低位为1,则执行a-1时则消掉了最低位(最右边)的1;若a最低位为0, 则执行a-1时会向前借位从而又消掉一个1。
从而可知,a=0x03执行两次a-1后则用掉了a中所有的1。
(2) a = a | (a+1),同理不妨设a=0x00 //00000000
此时,读者应该可以自行分析出:根据二进制的加法运算规则,每执行一次a+1则会引入一个1。
二、按位异或运算符的使用
设有两个变量a和b, 若从微观层面(单个bit)来观察,按位异或运算规则可简单概况为“相异为1, 相同为0”;若从宏观的变量角度来观察,则可导出下列运算规则:
a ⊕ a = 0 a ⊕ b = b ⊕ a
a ⊕ b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c
d = a ⊕ b ⊕ c => a = d ⊕ b ⊕ c =a ⊕ (b ⊕ c ⊕ b ⊕ c) ,
a ⊕ b ⊕ a = b
上述运算规则乍一看似乎没什么太大用处,但这份运算规则中其实暗含着交换两个变量值的高效方法。关于这一点的体悟,笔者想通过一个实际的例子来向大家说明。
#include<stdio.h> #define SWAP1(a,b)\ { \ int t = a; \ a = b; \ b = t; \ } #define SWAP2(a,b)\ { \ a = a + b; \ b = a - b; \ a = a - b; \ } #define SWAP3(a,b)\ { \ a = a ^ b; \ b = a ^ b; \ a = a ^ b; \ } int main(void){ int a = 3, b= 4; printf("a = %d, b = %d\n", a, b); // 3 4 SWAP3(a,b); printf("a = %d, b = %d\n", a, b); //4 3 return 0; }
该程序中出现了三种交换变量的方法,其中SWAP1应该是大多数读者都熟悉的操作,而SWAP2与SWAP3或许则鲜为人知。论效率和实现来看,SWAP3效率最高。同时,SWAP2在多数情况下虽然也能完成变量值交换的任务,但实际隐藏着一个尴尬的bug(即当a,b数值较大时, 其部分和a+b则会发生溢出,从而使得之后的交换不能正确进行)。
三、移位运算符的使用
1.基本规则
int main(void){ char bin_str[SIZE + 1]; //+1: 末尾结束字符 int num, shift; puts("请输入左操作数与右操作数: "); while(scanf("%d %d", &num, &shift) == 2){ printf("num=%d: ", num); itobs(num, bin_str); show_bstr(bin_str); putchar('\n'); printf("移位后: "); itobs(num>>shift, bin_str); show_bstr(bin_str); putchar('\n'); } return 0; }
运行结果:
2. 实用技巧
刚才的例子只是为了说明移位运算符的基本规则,实际编程工作中倒并无太大用处。在文章的首部,我们提到了“理论上可通过位运算完成所有的运算和操作”,那么移位运算符的妙用则在于可快速进行2的n次幂的运算,以及提取较大单元中的单个字节的数值or组合多个字节单元的数值为一个整体。
下面通过例子来具体说明提取以及组合字节数值这点。
int main(void){ #define BYTE_MASK 0xFF char bin_str[SIZE + 1]; //+1: 末尾结束字符 unsigned long color = 0x002a162f; unsigned char blue, green, red; red = color & BYTE_MASK; //提取红色分量 green = (color>>8) & BYTE_MASK; //提取绿色分量 blue = (color>>16) & BYTE_MASK; //提取蓝色分量 printf("color = 0x%X, red = 0x%X, green = 0x%X, blue = 0x%X\n", color, red, green, blue); return 0; }
运行结果:
参阅资料
- 狄泰软件学院-C进阶剖析教程
- C primer plus 第6版
- 高质量嵌入式Linux C
- 算法精解-C语言描述
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。