编程珠玑开篇的一道题目是这样的:
如何使用位逻辑运算(如与、或、移位)来实现位向量?
一.何为位向量?
在许多情况下(如对象为满足或不满足某条性质的情况),用一个二进制位就足够表示一个对象了。但是,不能用一个变量名直接表示一个位(不存在单独为一位的数据类型)。于是,就考虑将多个位组成一个基本数据类型,通过对这个基本数据类型的操作,达到使用位的方法。同时,为了方便,把由位组成的基本数据类型组成数组,这样,就可以对一定范围的位数据集合进行操作。我们把这种形式的数据结构称为位向量。
接下来,再考虑一点,对位向量各位的操作我们不能通过名称去访问,只能通过位的位置去操作,即我们要操作第几位的数据。在我们看来位是由0——n连续的,实际上因为我们是用基本数据类型的数组进行存储的,因为,这些位是分割在不同的数组元素当中,处在不同数组元素的不同位置当中。假设我们以int类型做为基本数据类型,则一个int类型(c++)中,可以存储32个位。则对于特定的一个位(位置为:pos),我们首先考虑它处在哪个int数组元素当中(pos/32,即知道处在哪个数组元素当中),然后考虑该位处在该数组元素的哪个位置(即pos mod 32)。
对位向量的操作主要有三个:对特定位置1,对特定位清0,判断特定位。
二.实现代码
有了以上知识,我们看下编程珠玑中对该问题的实现代码。
1 #define BITSPERWORD 32 //使用的基本数据类型为32位,int类型 2 #define SHIFT 5 //与确定位处于哪个数组元素有关 3 #define MASK 0x1F //与确定位处于数组元素哪一位有关 4 #define N 10000000 //位长度 5 int a[1+N/BITSPERWORD]; //数组长度 6 7 //将对应位置1 8 void set(int i) { 9 a[i>>SHIFT] |=(1<<(i & MASK)); 10 } 11 12 //将对应位置0 13 void clr(int i){ 14 a[i>>SHIFT] &=~(1<<(i & MASK)); 15 } 16 17 //判断对应位 18 void test(int i){ 19 return a[i>>SHIFT] & (1<<(i & MASK)); 20 }
下面来分析下以上代码。
1)确定数组元素,即i/32,对于2的倍数的整除,我们可以使用位移运算,因为32=2^5,所以i/32=i>>5,故a[i>>SHIFT]确定了数组元素。
2)确定数组元素后,我们要确定位的相对位置,即为i mod 32,观察,当对i执行右移(5位)运算时,把低5位消除了,而这低5位正是余数,因此可以通过i&0x1F,获得低5位的值,进而得到位的相对位置,故如i & MASK。
3)确定位置后,我们进一步转成对应的位位置掩码,即1<<(i&MASK)。
通过以上三步,我们完成了定位的工作。
分析三条语句:
1)set():因为是置1操作,所以只需要把原数组元素与位位置掩码进行或操作即可;
2)clr():因为是置0操作,所以先把位位置掩码取非,使特定位位0,再与原数组元素进行与操作即可。
3)test():判断特定位,只需要原始数组元素与位位置掩码进行与操作,即可判断当前位是否为1。