• PHP之位运算符


    使用场景:

      1)断奇数偶数

    for ($i=0; $i < 10 ; $i++) { 
        if($i & 1){
            echo $i.PHP_EOL;
        }
    }
    //输出所有奇数

      2)快速修改状态

    $status1 = 1;
    $status2 = 0;
    
    echo $status1 ^ 1;   // 0
    echo $status2 ^ 1;  //1

      3)日常算法

    <?php
    
    //普通函数
    function ip2int($ip)
    {
        list($ip1,$ip2,$ip3,$ip4) = explode('.', $ip);
        return $ip1*pow(256, 3) + $ip2*pow(256, 2) + $ip3*256 + $ip4;
    }
    
    //位运算符
    function ip2int2($ip)
    {
        list($ip1,$ip2,$ip3,$ip4) = explode('.', $ip);
        return ( $ip1 * (1<<24) ) + ( $ip2 * (1<<16) ) + ( $ip3 * (1<<8) ) + ($ip4);
    }
    
    $ip = '192.168.0.192';
    
    echo ip2int($ip).PHP_EOL;
    echo ip2int2($ip).PHP_EOL;

    注意点:有人问,为什么位运算不能直接这样写呢?

    function ip2int2($ip)
    {
        list($ip1,$ip2,$ip3,$ip4) = explode('.', $ip);
        return ($ip1<<24) + ($ip2<<16) + ($ip3<<8) + ($ip4);
    }
    

      这样不是更简洁吗?  但是你可以运行一下,会惊讶的发现并不一样

    为什么?  需要注意的数值型的上限  http://php.net/manual/zh/language.types.integer.php    可以通过常量 PHP_INT_MAX 来查看这个值的具体大小:   2147483647

    应用
    需求
    这是一个设置消息提醒的功能。我们假设这是一个blog系统,用户希望有人收藏自己的文章后,可以得到一个消息提醒。
    这里有三种提醒方式:邮箱提醒、短信提醒、微信公众号提醒,用户可以通过开关按钮控制某种提醒方式是开启还是关闭。可以参考以下表格:
    
    提醒操作	邮箱	短信	微信公众号
    文章被收藏	开关按钮	开关按钮	开关按钮
    被关注	开关按钮	开关按钮	开关按钮
    有人评论	开关按钮	开关按钮	开关按钮
    实现 1、数据库设计,表名(remind): 字段 数据类型 默认值 collection(文章被收藏) int default(0) followed(被关注 ) int default(0) commented(有人评论) int default(0) 2、在Remind Model中设置执行操作的提醒方式类型: cosnt REMIND_NO = 0;//关闭所有的提醒 cosnt REMIND_EMAIL = 1;//邮箱提醒 2的0次方 转化为二进制是:1 cosnt REMIND_SMS = 2;//短信提醒 2的1次方 转化为二进制是:10 cosnt REMIND_WECHAT = 4;//微信公众号提醒 2的2次方 转化为二进制是:100 如果有新的提醒方式值应为4、8、16,依次类推,2的n次方。 仔细观察二进制转化后 email第一位是1,sms第二位是1,wechat第三位是1. 即email占领了第一位、sms占领了第二位、wechat占领了第三位 1,代表开启提醒;0,代表关闭提醒 如果第一位是1,表示开启邮箱提醒;如果第一位是0,表示关闭邮箱提醒 假设用户希望有人评论他的文章时,收到来自系统的提醒: 只开启邮箱提醒,那数据库中commented字段对应的值应是:1;转化为二进制:1 只开启短信提醒,那数据库中commented字段对应的值应是:2;转化为二进制:10 只开启微信公众号提醒,那数据库中commented字段对应的值应是:4;转化为二进制:100 如果开启邮箱提醒和短信提醒,那数据库中commented字段对应的值应是:1+2=3;转化为二进制:11 如果开启邮箱提醒和微信公众号提醒,那数据库中commented字段对应的值应是:1+4=5;转化为二进制:101 如果开启短信提醒和微信公众号提醒,那数据库中commented字段对应的值应是:2+4=6;转化为二进制:110 如果开启所有提醒,那数据库中commented字段对应的值应是:1+2+4=7;转化为二进制:111 重点来了,前方高能,请注意啦~ 3、控制器中写具体业务逻辑: 用户希望有人关注他后,可以通过邮箱提醒他,所以用户开启了邮箱提醒: 前端传值:['option'=>'followed','type'=>'email']; 后端接收到参数,从数据库中找到当前用户的这一条数据,得知followed字段值为0; email对应的值为1,进行&运算,判断email提醒是否开启 1&0=0,所以未开启,那我们要开启email提醒,此时followed=0+1=1; 后来用户觉得短信提醒比较直接,所以又开启了短信提醒: 前端传值:['option'=>'followed','type'=>'sms']; 后端接收到参数,从数据库中找到当前用户的这一条数据,得知followed字段值为1; sms对应的值为2,进行&运算,判断sms提醒是否开启 2&1=0,所以未开启,那我们要开启sms提醒,此时followed=2+1=3; 后来该用户越来越厉害,每天关注他的人特别多,每天都收到很多消息,所以他又想关闭短信提醒: 前端传值:['option'=>'followed','type'=>'sms']; 后端接收到参数,从数据库中找到当前用户的这一条数据,得知followed字段值为3; sms对应的值为2,进行&运算,判断sms提醒是否开启 2&3=2,所以已开启,那我们要关闭sms提醒,此时followed=3-2=1; 部分程序代码: $option = $GET['option'];//提醒操作,由前端传来的值 $type = $GET['type'];//提醒方式,由前端传来的值 $remind = Remind::model()->find(1);//根据条件,在数据库中找到的一条记录// 如果是提醒方式一 if ($type == "sms") { if ($remind->$option & Remind::REMIND_SMS) { // true 代表已开启sms提醒,此时应关闭sms提醒 $remind->$option -= Remind::REMIND_SMS; } else if (!($remind->$option & Remind::REMIND_SMS)) { // false 代表已关闭sms提醒,此时应开启sms提醒 $remind->$option += Remind::REMIND_SMS; } }

      

    有一个广告表,我们要对广告做显示控制:
    
    手动上下线。
    
    只允许 VIP 查看。
    
    可能的表结构如下:
    
    CREATE TABLE `finger_ad` (
      `ad_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `ad_name` varchar(50) NOT NULL COMMENT '广告名称',
      `ad_image_url` varchar(255) NOT NULL COMMENT '广告图片',
      `ad_url` varchar(255) NOT NULL COMMENT '广告图片URL跳转地址',
      `is_vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否仅限 VIP 显示',
      `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT '显示状态:1显示、0隐藏',
      PRIMARY KEY (`ad_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='广告表';
    假如后期,我们需求更改了。需要再增加几种限制:
    
    已登录用户
    
    未登录用户
    
    30 天内未登录用户
    
    注册 30 天的用户
    
    遇到这种限制条件的需求,开发同学是不是很伤脑筋?
    
    可能很多开发第一反应就是在表结构增加这种新增的限制条件字段。一切看来似乎很美好。
    
    的确,这样添加字段是最快最容易的方式。也能完成我们的需求。
    
    但是,这样会引来如下毛病:
    
    每次增加限制条件。我们都要增加字段。这种对数据库的更动能少改就少改。毕竟,无限制的增加字段不可取。
    
    假如广告表数据量很大。大到增加一个字段需要几分钟的时候,这会给数据库服务器造成读写压力。
    
    条件越多,SQL 条件语句就会越来越长。
    
    那么,还有没有更好的方式解决这些问题呢?
    
    答案:有!
    
    这就是我们今天要讲的按位与运算符的高级技巧。
    
    我们把上面的表结构改一下:
    
    DROP TABLE IF EXISTS `finger_ad`;
    CREATE TABLE `finger_ad` (
      `ad_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `ad_name` varchar(50) NOT NULL COMMENT '广告名称',
      `ad_image_url` varchar(255) NOT NULL COMMENT '广告图片',
      `ad_url` varchar(255) NOT NULL COMMENT '广告图片URL跳转地址',
      `bit_condition` INT(11) UNSIGNED NOT NULL COMMENT '位运算条件:1-登录可访问、2-未登录可访问、4-30天注册可访问、8-30天未登录可访问、16-未消费可访问、32-VIP可访问',
      `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT '显示状态:1显示、0隐藏',
      PRIMARY KEY (`ad_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET UTF8 COMMENT='广告表';
    我们把所有的条件都去掉了。增加了一个字段:    bit_condition 。把所有的条件都组合到一个字段。
    
    那我们此时该如何写代码呢?
    
    比如,现在要添加如下限制条件的广告:
    
    只允许登录用户访问或已注册 30 天用户或是 VIP 用户才允许访问该广告。
    
    那么,这个广告的    bit_condition 该如何设置值呢?很简单,把这几个条件的位值直接相加。此时值为:37。
    
    很多可能会很奇怪。设置为 37 ,我怎么知道是这几个值的和呢?如果对 Linux 系统权限熟悉的同学就很容易理解这种做法。实际上,这里运用了按位与运算的特性:任意组合相加的值不会重复。
    
    这个理解起来有一定难度。我三两句也很难给你梳理明白。大家可以在网上深入挖掘一下这方面儿的知识。你只需要知道这一点特点即可。
    
    那么,现在我们该如何写 SQL 呢?
    
    示例如下:
    
    SELECT * FROM finger_ad WHERE display = 1 AND bit_condition & 3 = bit_condition
    这条 SQL 语句当中的 3 对应的是当前用户针对这么多条件得到的数值。如果    bit_condition位值是与 3 按位与与    bit_condition 结果相同,说明条件符合。
    
    我们通过一个字段解决了所有条件的问题。着实得感谢按位与运算符的特性。同时也对    MySQL能支持位运算符感到开心。
    
    那么,它有什么缺点呢?
    
    想必有经验的同学已经看出来了。这种写法只能满足包含关系。假如要实现同时满足 3 个条件才能访问就不行了。或者,一个满足另外一个取反。
    
    优点明显,同样缺点也很明显。大家要根据实际情况来选用。

  • 相关阅读:
    Markdown高级使用之流程图
    Sentinel滑动窗口算法
    Markdown基础使用
    多线程学习(二)--整体简介
    MYSQL学习(三) --索引详解
    MYSQL学习(二) --MYSQL框架
    MYSQL 学习(一)--启蒙篇《MYSQL必知必会》
    数据结构学习(六) --排序
    数据结构学习(五)--查找
    数据结构学习(四)--图
  • 原文地址:https://www.cnblogs.com/xingxia/p/php_bit.html
Copyright © 2020-2023  润新知