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

news/2024/11/20 4:57:14/

背景

许多网络游戏为了刺激玩家每天上线,都在游戏中设有“每日任务”——每天让玩家做一些任务,如杀怪、采集来赚取积分、金钱、经验等。每日任务根据游戏不同,数量也不同,每日任务比较少的网络游戏中,可能每日任务只有几个,每日任务多的网络游戏,可能每日任务有几十个。
现在假设要做一款网络游戏,其中每日任务有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=1ETask2 = BIT(1),	//2=10ETask3 = BIT(2),	//4=100ETask4 = BIT(3),	//8=1000ETask5 = BIT(4),	//16=10000ETask6 = BIT(5), 	//32=100000ETask7 = BIT(6),	//64=1000000ETask8 = BIT(7),	//128=10000000ETask9 = BIT(8),	//256=100000000ETask10 = 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个变量,这就是位运算在实际工作中的主要用途之一。


http://www.ppmy.cn/news/1039154.html

相关文章

内网穿透-外远程连接中的RabbitMQ服务

文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…

在测试环境进行sqlserver锁表测试

将某表设置X锁1分钟&#xff1a; begin tran select top 1 * from tableName with (tablockx) waitfor delay 00:01:00 commit tran 查询当前被锁的表&#xff1a; --查询锁表的事务ID&#xff0c;被锁表名&#xff0c;锁模式&#xff0c;客户端主机名&#xff0c;客户端程序…

[Leetcode] [Tutorial] 二分查找

文章目录 35. 搜索插入位置Solution 33. 搜索旋转排序数组Solution 153. 寻找旋转排序数组中的最小值Solution 34. 在排序数组中查找元素的第一个和最后一个位置Solution 74. 搜索二维矩阵Solution 35. 搜索插入位置 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标…

Nginx常见的三个漏洞

目录 $uri导致的CRLF注入漏洞 两种常见场景 表示uri的三个变量 案例 目录穿越漏洞 案例 Http Header被覆盖的问题 案例 $uri导致的CRLF注入漏洞 两种常见场景 用户访问http://example.com/aabbcc&#xff0c;自动跳转到https://example.com/aabbcc 用户访问http://exa…

在SpringBoot中添加拦截器忽略请求URL当中的指定字符串

1 自定义拦截器 Component public class GlobalInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String path request.getRequestURI();if (pa…

基于ssm的CRM客户管理系统(spring + springMVC + mybatis)营销业务信息java jsp源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 基于ssm的CRM客户管理系统&#xff08;spring spring…

【制作npm包1】申请npm账号、认识个人包和组织包

概述 在开发当中经常有一种现象&#xff0c;重复代码写了N多遍&#xff0c;再次写同样的逻辑就再次翻查以前的代码逻辑。效率低下且容易出错&#xff0c;封装一个npm包的价值也不仅仅是给别人用&#xff0c;封装一套属于自己或者本部门的npm包也是相当有必要。 也许经常看到一…

前沿探索|关于 AIGC 的「幻觉/梦游」问题

AI语言模型的梦游是指模型产生内容与真实世界不符或者是毫无意义的情况。这种情况主要是由于语言模型缺乏真实世界的知识和语言的含义&#xff0c;导致模型难以理解和表达现实世界的概念和信息。这种情况在现代自然语言处理中普遍存在&#xff0c;尤其是在开放式生成领域的问题…