C++新经典04--位运算

背景

许多网络游戏为了刺激玩家每天上线,都在游戏中设有“每日任务”——每天让玩家做一些任务,如杀怪、采集来赚取积分、金钱、经验等。每日任务根据游戏不同,数量也不同,每日任务比较少的网络游戏中,可能每日任务只有几个,每日任务多的网络游戏,可能每日任务有几十个。
现在假设要做一款网络游戏,其中每日任务有10个,例如给好友送一次礼物、花金币购买一次物品、和其他玩家进行一次PK、杀死一个游戏中的怪等,都属于每日任务之一。现在的需求是:记录这10个任务是否完成,没完成的,用0表示,完成的,用1表示。

可能读者很快就能想到一个解决方法:定义10个变量,或者定义10个元素的整型数组,每个数组元素存一个任务标记0或者1:
在这里插入图片描述
试想,如果往数据库中记录该玩家的每日任务是否完成,是要记录10条数据,这显然很浪费数据库的存储空间。针对这个问题,有两个前提条件先约定一下:
· 每日任务只有10个。
· 只需记录该任务是否完成:0(未完成)或者1(已完成)这两个状态之一。

由此,就想到了位运算。一个unsigned int型数据有4字节,也就是32个二进制位,每个二进制位又都可以是0或者1,这样看来,只需要用一个unsigned int型变量,就可以记录多达32种状态,每个状态要么是0,要么是1。也就是说,其实只需要用一个unsigned int型变量,就能记录下每日这10个任务是否完成,甚至一个unsignedint型变量能记录多达32个每日任务是否完成(因为有32个二进制位),远远超过10个每日任务这个数量,这样,往数据库中保存玩家每日任务数据时,就只需要记录一条数据。

一个unsignedint型数据,一共32个位,最右边代表第1位,逐渐往左边来,最左边代表第32位,最左边的称为最高位,最右边的称为最低位,如图11.1所示。
在这里插入图片描述
现在每日任务只有10个,那么只需要使用其中的10位表示这10个任务是否做完就可以了,用0表示任务没做完,用1表示任务已经做完,用最右边的最低位表示第1个任务,然后依次往左,表示第2个任务,第3个任务……,一直到第10个任务,如图11.2所示。
在这里插入图片描述
这里需要写一些代码来做一些基本操作,要实现两个功能:
· 判断某个任务是否做完。
· 标记某个任务已经做完了。

下面要讲解一些前提代码,请注意看下面的代码:

#define BIT(x) (1 <<(x))
//这段代码日后的工作中都有实用价值,
//#define曾经讲过,是带参数的宏定义

分析一下这个带参数的宏定义,会得到如下结果:
· BIT(0)等价于(1≪(0)),代表1左移0位;
· BIT(1)等价于(1≪(1)),代表1左移1位;
· BIT(2)等价于(1≪(2)),代表1左移2位。

每左移一位相当于乘以2,所以,上面这个#define的功能就能够推测出来:
在这里插入图片描述
所以执行下面这段代码:

   int i;
    for(i=0;i<10;i++)
    {
	    printf("BIT(%d)=%d\n",i,BIT(i));	//1,2,4,8,16,32,…,512
    }

结果如图11.3所示。

BIT(0)=1
BIT(1)=2
BIT(2)=4
BIT(3)=8
BIT(4)=16
BIT(5)=32
BIT(6)=64
BIT(7)=128
BIT(8)=256
BIT(9)=512

图11.3

看一下结果数字,这些数字是1、2、4、8、16、32、64、128、256、512,从表面看,可以观察到,每个数字都是前面的数字*2得到的。现在把这些数字变成二进制数再观察一下:
在这里插入图片描述
有了上面这些知识,就能判断某个任务是否做完了。定义一个无符号整型变量如下:

unsigned int task;

注意,这里给这个任务变量取名叫task,task变量一共32位长;如果想看第7个任务是否做完,怎么看呢?如果第7个位置是1,就说明第7个任务做完了;如果第7个位置是0,就说明第7个任务没做完,如图11.4所示。
在这里插入图片描述
现在,问题的关键就是要把这第7个位置的数据提取出来。如何提取,就需要用到位运算。回忆一下上一节的按位与运算符“&”,如果两个相应的位都为1,则该位的结果为1,否则为0。回忆一下按位与的公式:
在这里插入图片描述
可以想象,如果把task与1000000(这是二进制数,第7位为1,其他位为0)做按位与运算,会出现什么结果?如果第7位为1,则结果肯定会如图11.5所示。
在这里插入图片描述
那么,得到的结果描述如下:如果task中(一共有32位)的第7位是0,那么task&1000000=0;如果第7位是1,那么task&1000000=1000000(二进制)=64(十进制)=BIT(6)。
所以,要判断某个任务是否做完,完整的判断代码应该这样写,这些代码具备商用价值,供读者参考和借鉴:

//10个任务
enum EnumTask
{
	ETask1 = BIT(0),	//1=1
	ETask2 = BIT(1),	//2=10
	ETask3 = BIT(2),	//4=100
	ETask4 = BIT(3),	//8=1000
	ETask5 = BIT(4),	//16=10000
	ETask6 = BIT(5), 	//32=100000
	ETask7 = BIT(6),	//64=1000000
	ETask8 = BIT(7),	//128=10000000
	ETask9 = BIT(8),	//256=100000000
	ETask10 = BIT(9),	//512=1000000000
}
unsigned int task = 0;
//刚开始所有任务都没执行过,所以任务变量先初始化为0
//判断第7个任务是否执行过了。按位与,不为0则表示任务7做过
if(task & ETask7)
{
	//任务7已经做过
	printf("任务7已经做过了\n");
}
else
{
	//任务7还没做过
	printf("任务7还没做过,现在做任务7\n");
}

以上就判断出任务7做没做,核心代码就是这一句:if(task&ETask7)。接着思考,如果任务7没做,如何把任务7做了,也就是让任务7这个位置标记上1?这就用到了按位或运算符“|”,参加运算的两个运算量,如果两个相应的位有一个为1,则该位的结果为1,否则为0。回忆一下按位或的公式:
在这里插入图片描述
所以,如果把task与1000000(这是二进制数,第7位为1,其他位为0)做按位或运算,会出现什么结果呢?结果就是其他位都不变,但是第7位肯定变为1(不管原来是什么);把第7位标记为1,就起到了标记任务7做完了的目的。所以代码继续完善如下:

    unsigned int task = 0;
    //刚开始所有任务都没执行过,所以任务变量先初始化为0
    //判断第7个任务是否执行过了
    if (task & ETask7)	//按位与,不为0则表示任务7做过
    {
        //任务7已经做过
        printf("任务7已经做过了\n");
    }
    else
    {
        //任务7还没做过
        printf("任务7还没做过,现在做任务7\n");
        //把任务7做了(标记任务7做完)
        task = task | ETask7;	//位操作运算符优先级高于赋值运算符
    }
    //再次判断任务7是否做过了
    if (task & ETask7)
    {
        printf("任务7已经做过了,可以把这个task变量值保存到数据库中去了\n");
    }

这里,总结一下:
通过按位与操作来判断某个二进制位是否被标记为1,通过按位或操作将某个二进制位标记为1,然后,就可以把上面的task变量中的内容保存到数据库里,下次该玩家再上线,再把这个内容从数据库中取出,就能判断该玩家的某个任务是否做过,做过的话就可以有一些其他的处理,如不让他再重复做了。

上面这个范例,就是通过位操作的方法,把原本需要10个变量(数字)记录10个任务是否完成缩减成了用一个unsignedint类型变量来记录,一下子就节省了9个变量,这就是位运算在实际工作中的主要用途之一。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值