位運算子
如果你正準備看下去,你應該先搞懂各個位運算子的作用。 以下是官網的一個介紹。
例子 | 名稱 | 結果 |
---|---|---|
$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選項的時候可以考慮用到位運算來實現,可以用來簡化引數的傳遞並且更為靈活。