提醒:本页面将不再更新、维护或者支持,文章、评论所叙述内容存在时效性,涉及技术细节或者软件使用方面不保证能够完全有效可操作,请谨慎参考!

原文发表于2008年11月22日

在我刚刚接触C语言时,对于10进制转2进制这种数学运算很是反感,记得某次实验课上老师要求写一段10进制转换为2进制的C语言程序,我竟然忙活了半天没有搞定,本来以为很简单的,不就是除以二判断余数吗?但偏偏弄不好,后来接触到数据结构课知道每次余数判断需要将结果进栈,然后输出结果时出栈即可,代码后来自己搞定了。后来上过汇编课后了解了位运算。于是我写了如下代码,对,这就是进制转换,很简单。当然,如果你有更好的欢迎共同交流。

#include <stdio.h>   
int main(void)   
{ 
    int usr_input;   
    short i;   
    puts("Enter a number:");   
    scanf("%d", &usr_input);   
    for(i = ((sizeof(usr_input) << 3) - 1);
	i > -1; i--)   
    {   
        printf("%d", ((usr_input &
		     (0x1 << i)) == 0 ?
				0 : 1));   
        if((i & 3) == 0)   
            putchar(' ');    /* 这个if也可以省掉 */  
    }   
    putchar('\n');   
    return 0;   
}

位操作的魅力就在这里,代码简洁,当然最重要的一点还是位操作的效率比较高。缺点就是代码不易理解,不过我认为你对二进制位有深刻了解并且经常写一些位操作的代码的话这些代码你也会很容易理解的。

下面解释下这段进制转换的代码。

((sizeof(usr_input) << 3) - 1) 获取数据的位数,这里是int型,sizeof后为4,左移3位,左移1位即乘以2,这里即乘以8,结果是32,也就是说int型为32位(注:不同的机器可能sizeof的值是不同的,这里仅仅以Intel x86机型为例)。一般情况下二进制我们从右向左读,第1位也就是我们这里说的第0位,那么输出这个二进制,需要先输出最高的一位,也就是输出第31位。怎么判断这一位呢?这是我们下面要做的工作,学过汇编的知道,使用TEST指令即可判断位,而TEST指令做的是不写原值的按位与操作,所以要判断哪一位我们需要将那一位与1与,其余位与0与即可,比如这里的判断需要与的是 1000 0000 0000 0000 0000 0000 0000 0000 ,如果结果是0,说明这一位是0,反之若不为0则这一位为1。但如何实现判断下一位和下下位的循环判断呢?其实我们可以设定一个初始值 0000 0000 0000 0000 0000 0000 0000 0001 即0x1,让其随循环次数进行左移即可。i & 3 其实这句是 i % 4,主要是为了美观,每四位输出空格。求余(模)一般对于求的是2的倍数的余数都可以用类似的按位与实现。

这段代码理解后可以看看下面的代码:

/* 转化为大写 */  
char *UCase(char *Source)   
{   
 long i=0;   
 do  
  if(*(Source+i) <= 'z' &&
	*(Source+i) >= 'a')   
   *(Source+i) &= 223; /* 将二进制第六位置为0 */  
 while(*(Source+i++));   
  return Source;   
}   
  
/* 转化为小写 */  
char *LCase(char *Source)   
{   
 long i=0;   
 do  
  if(*(Source+i) <= 'Z' &&
	*(Source+i) >= 'A')   
   *(Source+i) |= 32; /* 将二进制第六位置为1 */  
 while(*(Source+i++));   
  return Source;   
}

这段代码应该是比较好理解的,其实你只要用心观察过ASCII码表,分析其二进制后你就会发现一定的规律,然后也可以像上面的代码一样轻松的用位操作实现常用的功能。

如果我要让你写一段判断奇偶数的代码呢?很多人肯定认为,这还不简单,直接与2求余即可,学过汇编后,我才知道有比这个还方便的位操作方法,即直接判断最后一位是否为1,为1即为奇数,反之为偶数。怎么判断呢,上面说过了,用按位与即可,对0x1按位与。你会发现和刚才提到的求余的位操作简化有着相同之处,即模2。

再介绍个我较早接触的位操作,即交换两个整形变量。老师在授课时绝大多会和你讲,交换两个数需要临时变量什么的,然后要你写一个swap函数,其实在书上位操作那章介绍了一个不需要临时变量的整形交换方法。即异或。下次老师再要求你写交换两个整形的函数你就可以这样去写:

void swap(int *a, int *b)   
{   
    *a ^= *b;   
    *b ^= *a;   
    *a ^= *b;   
}

不相信吗?自己调试看看。这段代码类似与使用位运算的简单应用实现的密钥加密解密的功能。

下面提供一些,我写的用来操作位的宏:

#define BIT_TEST(src, pos) \
	((((src) & (0x1 << (pos))) == 0) ? 0 : 1)   
#define BIT_SET(src, pos) \
	((src) | (0x1 << (pos)))   
#define BIT_CLEAR(src, pos) \
	((src) & (~(0x1 << (pos))))   
#define BIT_TURN(src, pos) \
	((src) ^ (0x1 << (pos)))   
#define BIT_SIZE(type)     \
	(sizeof((type)) << 3)   
#define BIT_MOD(src0, src1) \
	((src0) & ((src1) - 1)) /* src1 must be 2*n */

解释:

BIT_TEST 输出src的pos位是0还是1。
BIT_SET 设置src的pos位为1。
BIT_CLEAR 设置src的pos位为0。
BIT_TURN 翻转src的pos位,即0变成1,1变成0。
BIT_SIZE 输出type型的位数。
BIT_MOD 求余(模),即将src0 % src1,特别提醒的是 src1必须是2的倍数。

最后,我想说的是,这里提到的位操作均适用于整形数,最好不要尝试在浮点型上应用,以避免意想不到的后果。