开篇
本文是使用位逻辑(如与、或、移位)来实现位向量,对应的是《编程珠玑》第1章的课后习题2。本文中主体代码在课后习题答案中均有提及,但介于没有详细的注释,以及用于测试main函数,所以很多小白刚看到代码的时候,可能会有点懵,所以本文提供了对代码详细的解释,这些解释均被写在了代码注释里,同时,还提供了测试用的代码。
关键词:《编程珠玑》、位逻辑实现位向量、第1章课后习题2
分析
在C语言中,通常使用整数数组来模拟位向量,使用位操作来设置、查询、清除单个位。
设置位:将目标位设置为1(设置前为0);
清除位:将目标位设置为0(设置前为1);
查询位:查询校验目标位是否为1或0.
具体实现逻辑在代码中有及其详细的注释,此处不再赘述。
代码实现
#include <stdio.h>
#include <string.h>
// 定义每个数组元素可以表示的位数
#define BITSPERWOD 32
// 用于快速计算除以32的结果,2的5次方=32
#define SHIFT 5
// 用于 获取除以32的余数,0x1F是十六制,等于二进制的11111,即32-1,用于位操作中取余
#define MASK 0x1F
// 定义位图可以表示的最大的范围,此处是1000万
#define N 10000000
// 定义一个整型数组,用于容纳N个位
int a[1 + N/BITSPERWOD];
/**
* i>>SHIFT等同于除以32,用于计算i位于数组的哪个元素
* 1<<(i & MASK)计算i在该元素的哪一位
* |= 用于将那一位设置为1
*
* 举例: i = 63, 此时63/32 = 1,即62处于a[1]的位置 (即第2个元素)
* 63%32=31,即在第32为设置为1,此处i & MASK就相当于取余的操作,1<<(i & MASK)中左移的动作就是把第32位设置为了1
* a[i>>SHIFT] |= (1<<(i & MASK)); 这个操作总的来说,就是把a[1]的第32位赋值为1
*/
void set(int i) {
a[i>>SHIFT] |= (1<<(i & MASK));
}
/**
* 接着上面说,此处如果i = 63
* 此处 1<<(i & MASK) 生成了一个第32位为1,其余31位为0的掩码,
* 然后使用~取反,生成一个在32位为0,其余位为1的掩码,
* 随后,和 a[i>>SHIFT] 进行按位与操作(此时, a[i>>SHIFT]为第32位为1,其余位为0的掩码),
* 操作后,a[i>>SHIFT]就变成了全部位为0的掩码,达到了清除位的目的
*/
void clr(int i) {
a[i>>SHIFT] &= ~(1<<(i & MASK));
}
/**
此处作用是检查目标位是否被设置为了1
还是以上面的63为例,首先,a[i>>SHIFT] 确定了是a[1],如果被设置了,则此时它为第32位1,其余位为0的掩码,
1<<(i & MASK)使用这句代码,将第32位设置为了1,随后与 a[1]的掩码进行按位与操作,此时因为第32位为1,所以结果不是0
于是就返回true
*/
int test(int i) {
return (a[i>>SHIFT] & (1<<(i & MASK))) != 0;
}
int main() {
// 将所有位初始化为0
memset(a, 0, sizeof(a));
// 设置位
set(100);
set(100000);
set(1000000);
// 测试
printf("Test bit 100: %d\n", test(100));
printf("Test bit 100000: %d\n", test(100000));
printf("Test bit 1000000:%d\n", test(1000000));
// 清除其中一个位
clr(100000);
// 测试被清除的位
printf("Test cleared bit 100000: %d\n", test(100000));
// 测试一个未被设置的位
printf("Test unset bit 10000: %d\n", test(10000));
return 0;
}
注
本文添加的注释,是为了能让小白(像我一样的小白哈哈)也能很很清楚的明白代码中的逻辑,但本人毕竟能力有限,若有谬误的地方,还请指正,以防止我的注释误导别人。