PHP 位运算使用
位运算符
如果你正准备看下去,你应该先搞懂各个位运算符的作用。 以下是官网的一个介绍。
例子 | 名称 | 结果 |
---|---|---|
$a & $b | And(按位与) | 将把 $a 和 $b 中都为 1 的位设为 1。 |
$a | $b | Or(按位或) | 将把 $a 和 $b 中任何一个为 1 的位设为 1。 |
$a ^ $b | Xor(按位异或) | 将把 $a 和 $b 中一个为 1 另一个为 0 的位设为 1。 |
~ $a | Not(按位取反) | 将 $a 中为 0 的位设为 1,反之亦然。 |
$a << $b | Shift left(左移) | 将 $a 中的位向左移动 $b 次(每一次移动都表示“乘以 2”)。 |
$a >> $b | Shift right(右移) | 将 $a 中的位向右移动 $b 次(每一次移动都表示“除以 2”)。 |
详情请点击【这里】了解。
平常开发需要用位运算吗?
注:以下所有说到第几位都是从0位开始数,所有2进制都是抹去了高位为0的位只保留了用于对比的那几位。
之前我一直以为对我平常开发来说我并不需要用到位运算符,我觉得这东西需要做很复杂的操作才会用到。
但是在我用了很多次json_encode($array, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
这个函数以后,我开始好奇为什么只用一个参数就可以控制json字符串输出的两个设置,既能格式化输出还能不把内容编码成\uXXXX
可以更直观的看到中文是什么内容。
于是我分别打印了JSON_UNESCAPED_UNICODE
和JSON_PRETTY_PRINT
的值,他们分别是256
和128
,开始十进制开不出个所以然。于是我对比了他们的二级制值。
常量 | 二进制数值 | 10进制数值 |
---|---|---|
JSON_UNESCAPED_UNICODE | 0b100000000 | 256 |
JSON_PRETTY_PRINT | 0b010000000 | 128 |
JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | 0b110000000 | 384 |
可以看到从右往左数256
第8位是1,而128
第七位是1,通过按位或运算以后7位和8位都成了1。那函数内部就可以只需要判断json_encode
的第二个参数的二级制数第8位如果是1就是JSON_UNESCAPED_UNICODE
为真,第7位如果是1就是JSON_PRETTY_PRINT
为真了。
于是我又回想了还有哪个地方用了位操作符,一下又想起了这个函数error_reporting(E_ALL ^ E_WARNING ^ E_NOTICE)
,对比下二进制值。
注意一下
E_ALL
在 5.4.x 版本以后为32767,早期版本为30719。下面的举例假设 PHP >= 5.4.x
常量 | 二进制数值 | 10进制数值 |
---|---|---|
E_ALL | 0b111111111111111 | 32767 |
E_WARNING | 0b000000000000010 | 2 |
E_NOTICE | 0b000000000001000 | 8 |
E_ALL ^ E_WARNING ^ E_NOTICE | 0b111111111110101 | 32757 |
竖着直观的看下,运算以后第1位和第3位变为0了,也就是说E_WARNING
、E_NOTICE
被排除掉了。
很酷,一定要用!
到此忽然觉得这玩意儿太酷了,试想一下。比方说我现在要实现一个函数来初始化我家里的灯的开关状态。
错误示范
控制灯还不简单嘛,声明一个函数,一个参数控制一个开关。
<?php
function showLight($masterRoom = 0, $livingRoom = 0, $diningRoom = 0, $secondLie = 0, $kitchen = 0) {
echo '主卧', "\t";
echo '客厅', "\t";
echo '餐厅', "\t";
echo '次卧', "\t";
echo '厨房', "\t", PHP_EOL;
echo $masterRoom, "\t";
echo $livingRoom, "\t";
echo $diningRoom, "\t";
echo $secondLie, "\t";
echo $kitchen, "\t";
echo PHP_EOL;
}
//只开主卧灯
showLight(1);
//只开厨房灯
showLight(0, 0, 0, 0, 1);
//开所有灯
showLight(1, 1, 1, 1, 1);
可以看到我当我需要控制厨房灯的时候却要传其它四个参数,太不科学了,要是能要开哪个就传那个参数就好了。
用哪个传那个参数?用数组参数不就行了?
<?php
function showLight(array $control)
{
$control = array_merge([
'masterRoom' => 0,
'livingRoom' => 0,
'diningRoom' => 0,
'secondLie' => 0,
'kitchen' => 0,
], $control);
echo '主卧', "\t";
echo '客厅', "\t";
echo '餐厅', "\t";
echo '次卧', "\t";
echo '厨房', "\t", PHP_EOL;
echo $control['masterRoom'], "\t";
echo $control['livingRoom'], "\t";
echo $control['diningRoom'], "\t";
echo $control['secondLie'], "\t";
echo $control['kitchen'], "\t";
echo PHP_EOL;
}
//只开主卧和厨房
showLight(['masterRoom' => 1, 'kitchen' => 1]);
嗯,选择开启了。那我要排除厨房呢?showLight(['masterRoom' => 1, 'kitchen' => 0, 'livingRoom' => 1, 'diningRoom' => 1, 'secondLie
=> 1])`,又得输入全部参数了。
我现在还只有5盏灯,要是我是要控制一栋楼的所有灯只排除某一盏灯呢?我去......
再来看看用了位操作以后
先声明一个灯光控制的类,用5个位来表示5盏灯的开关为1则表示开灯。
<?php
class LightControl
{
const TURN_ON_ALL = 0b11111;
const KITCHEN = 0b10000;
const SECOND_LIE = 0b01000;
const DINING_ROOM = 0b00100;
const LIVING_ROOM = 0b00010;
const MASTER_ROOM = 0b00001;
private $options;
public function __construct($options = 0)
{
$this->options = $options;
echo '主卧', "\t";
echo '客厅', "\t";
echo '餐厅', "\t";
echo '次卧', "\t";
echo '厨房', "\t", PHP_EOL;
}
public function getOptions()
{
return $this->options;
}
public function setOptions($options)
{
$this->options = $options;
}
public function showOptions()
{
echo self::getOption($this->options, self::MASTER_ROOM), "\t";
echo self::getOption($this->options, self::LIVING_ROOM), "\t";
echo self::getOption($this->options, self::DINING_ROOM), "\t";
echo self::getOption($this->options, self::SECOND_LIE), "\t";
echo self::getOption($this->options, self::KITCHEN);
}
//获取指定灯的开关状态
private static function getOption($options, $option)
{
return intval(($options & $option) > 0);
}
}
//LightControl.php
我们来看看getOption
这个方法。因为我们用了五个位来表示每盏灯的开关状态。
可以看到从右边左数0-4
位分别是1的是MASTER_ROOM
主卧灯、LIVING_ROOM
客厅、DINING_ROOM
餐厅、SECOND_LIE
次卧、KITCHEN
厨房。
所以我们只需要一个方法来获取$options
指定位上是否为1就可以确定开关的状态了。
因为$option
一定只有个位上是1其它的都是0,所以$options
和$option
按位与以后如果他们的值大于0的话,它们肯定有一个相同的位都为1,也就是$option
的那个位上。
举个例子:
0b11111 $options 5个位上都是1
0b10000 KITCHEN
0b10000 $options & KITCHEN
可以看到0b10000
是一定大于0的。
- 全部关闭
<?php
$lightControl = new LightControl();
$lightControl->showOptions();
输出结果:
主卧 客厅 餐厅 次卧 厨房
0 0 0 0 0
- 全部打开
<?php
$lightControl = new LightControl(LightControl::TURN_ON_ALL);
$lightControl->showOptions();
输出结果:
主卧 客厅 餐厅 次卧 厨房
1 1 1 1 1
- 排除厨房
<?php
$lightControl = new LightControl(LightControl::TURN_ON_ALL ^ LightControl::KITCHEN);
$lightControl->showOptions();
输出结果:
主卧 客厅 餐厅 次卧 厨房
1 1 1 1 0
常量 | 二进制数值 |
---|---|
LightControl::TURN_ON_ALL | 0b11111 |
LightControl::KITCHEN | 0b10000 |
LightControl::TURN_ON_ALL ^ LightControl::KITCHEN | 0b01111 |
LightControl::TURN_ON_ALL ^ LightControl::KITCHEN
的值为0b01111
除了第4位(也就是厨房灯)其它都是1,成功排除厨房。
- 厨房和餐厅
<?php
$lightControl = new LightControl(LightControl::KITCHEN | LightControl::DINING_ROOM);
$lightControl->showOptions();
输出结果:
主卧 客厅 餐厅 次卧 厨房
0 0 1 0 1
常量 | 二进制数值 |
---|---|
LightControl::KITCHEN | 0b10000 |
LightControl::DINING_ROOM | 0b00100 |
LightControl::KITCHEN | LightControl::DINING_ROOM | 0b10100 |
LightControl::KITCHEN | LightControl::DINING_ROOM
的值为0b10100
,第2、4位(也就是厨房灯、客厅灯)都是1,选择打开了厨房灯、客厅灯。
可以看到用位操作以后可以灵活的控制灯了,如果要开的灯太多可以用排除法,要开的少可以用选择法。
总结
当需要用一个参数来控制众多只有true、false选项的时候可以考虑用到位运算来实现,可以用来简化参数的传递并且更为灵活。
本作品采用《CC 协议》,转载必须注明作者和本文链接
老哥稳
以前做权限用过位运算
位运算在很多场景下合理使用,能够大量节省 MySQL 存储压力。
了解过 Windows 开发的人,应该还记得很多 WinAPI 的设计,都使用了位运算的方式来指定 Flags,而不是一堆参数 TRUE / FALSE。
@gangpula 感谢分享,权限控制还真是个合适的使用场景:laughing:
@Wi1dcard 我没了解过windows开发,不清楚耶。
前几天才和同事讨论过用位运算来表示状态,可以任意组合,,
@largezhou :laughing: 嗯嗯,个人感觉状态如果较多这种场景,用位来表示还是挺好使的
位运算,论坛的很多 置顶/精华/热门 都是这样用的?
@839891627 这个不晓得是不是,我去搜索过倒是有用来控制权限。
最近刚学习,正好看到应用场景。:stuck_out_tongue_closed_eyes:
赞。
这个可以有,赞
我司也有很多功能上面使用了二进制的设计,来代替一对多的关联表。
我把几个函数打印了 E_ALL 常亮是32767 十进制的
@158abcd1510
感谢这位朋友,我刚去查了官网的文档,5.4.x以后的确是32767,之前的版本是30719。

位运算其实是很常用的,尤其是在C语言系统里面,速度会更快,其实在php的源码实现里面大量的使用位运算来实现,比如大家经常用到的数组,他的哈希算法就是用利用了位运算
醍醐灌顶, 相见恨晚 :+1:
这种函数的设计模式非常不错,值得学习。
我以为除以2,可以左移一位,比直接除法快,对不对?
@Shuyi 感觉单纯的乘除法应该差不多吧。并且除以2结果是小数的时候,如果用位运算就都是0了,结果也不对啊。另外除以2是右移1位哦。
@luokuncool 大神,谢谢
豁然开朗,一致没搞明白error_reporting(E_ALL ^ E_WARNING ^ E_NOTICE)这些到底是怎么控制报错信息的