如何在PHP中实现位掩码?

94

我不确定位掩码是否是正确的术语,请让我解释一下:

在PHP中,可以通过多种方式调用error_reporting函数:

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Reporting E_NOTICE can be good too (to report uninitialized
// variables or catch variable name misspellings ...)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// Report all errors except E_NOTICE
// This is the default value set in php.ini
error_reporting(E_ALL ^ E_NOTICE);

我从php.net页面 这里 找到了术语“位掩码(bitmask)”。

总之,我实现了一个简单的方法,称为ls,它返回目录的内容。

该函数需要3个参数...($include_hidden = false,$return_absolute = false,$ext = false)

因此,当我调用该函数时,我会设置希望如何返回结果。无论我想要返回隐藏目录还是只想要基本名称等。

因此,当我调用该函数时,我编写的代码是:

ls(true, false, true)
ls(false, false, true)
ls(true, true, true)
etc...

我认为如果能够标记返回数据的方式,代码将更易读?

比如:

ls( INCLUDE_HIDDEN | HIDE_EXTS );
ls( SHOW_ABSOLUTE_PATHS | HIDE_EXTS );

在测试中,我该如何实现检测调用了哪些标志?

4个回答

174

其实很简单。首先,有一小段代码可以演示如何实现它。如果您对这段代码正在做什么或如何工作还不理解,请随时在评论中提出额外的问题:

const FLAG_1 = 0b0001; // 1
const FLAG_2 = 0b0010; // 2
const FLAG_3 = 0b0100; // 4
const FLAG_4 = 0b1000; // 8
// Can you see the pattern? ;-)

function show_flags ($flags) {
  if ($flags & FLAG_1) {
    echo "You passed flag 1!<br>\n";
  }
  if ($flags & FLAG_2) {
    echo "You passed flag 2!<br>\n";
  }
  if ($flags & FLAG_3) {
    echo "You passed flag 3!<br>\n";
  }
  if ($flags & FLAG_4) {
    echo "You passed flag 4!<br>\n";
  }
}

show_flags(FLAG_1 | FLAG_3);

演示


由于标志是整数,因此在32位平台上可以定义多达32个标志。在64位平台上,它可以定义64个标志。还可以将标志定义为字符串,在这种情况下,可用的标志数量就几乎是无限的(当然要考虑系统资源的限制)。以下是其二进制表示形式的示例(为简化起见,使用了8位整数)。

FLAG_1
Dec:    1
Binary: 00000001

FLAG_2
Dec:    2
Binary: 00000010

FLAG_3
Dec:    4
Binary: 00000100

// And so on...

当你将标志组合起来传递给函数时,你需要使用按位或(OR)运算符。现在让我们看一下当我们传递 FLAG_1 | FLAG_3 时会发生什么。

  00000001
| 00000100
= 00000101

当您想查看设置了哪些标志时,可以使用按位掩码与标志进行按位与运算。因此,让我们使用上面的结果来查看是否设置了FLAG_3

  00000101
& 00000100
= 00000100

...我们获得了标志的值,一个非零整数 - 但如果我们检查是否设置了FLAG_2

  00000101
& 00000010
= 00000000

当我们得到零时,意味着在检查值是否被传递时可以将 AND 操作的结果简单地评估为布尔值。


12
基本位运算包括:$flags & FLAG_1 - 检查是否设置了FLAG_1,$flags | FLAG_1 - 设置FLAG_1,$flags & ~FLAG_1 - 取消设置FLAG_1,~$flags - 反转标志。 - Konstantin Pereiaslov
8
我不懂 PHP,也不太可能学会,但我很高兴偶然发现了这个问题——你的解释可以帮助任何人在任何语言中实现位图 :) - Chris Cirefice
7
我建议将2的幂定义为位移操作1。define('FLAG_1', 1<<0); define('FLAG_2', 1<<2); define('FLAG_3', 1<<3); define('FLAG_4', 1<<4); 这通常是在C中完成的方式。 - AmitP
7
@AmitP 这种 PHP 的方法有一些(小)问题。1)目前 const 关键字不支持表达式,即使结果是常量(例如const FLAG_1 = 1 << 0; 是一个解析错误)。显然这与 define() 无关。2)在 C 中,像 const int FLAG_1 = 1 << 0; 这样的常量表达式将在编译时解析,实际编译值将是表达式的结果,而在 PHP 中,这将每次都被评估,会导致微小的性能损失。虽然这些问题都不是避免使用您建议的更可读版本的好理由。 - DaveRandom
1
@Benjamin,这和问题无关,但是你可以使用const来处理标量表达式:const IS_WINDOWS = \PHP_OS & "\xDF\xDF\xDF" === 'WIN'; 这等同于非常常见的 stripos(\PHP_OS, 'WIN') === 0,不过是在编译时进行操作。目前看来这样做没有意义(所有的Windows版本上PHP_OS都为'WINNT'),但我猜这有将来的用途... - DaveRandom
显示剩余2条评论

19
define( "INCLUDE_HIDDEN", 0x1 );
define( "HIDE_EXTS", 0x2 );
define( "SHOW_ABSOLUTE_PATHS", 0x4 );
//And so on, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, 0x400, 0x800 etc..

你可以在ls函数中检查单个标志:

if( $flags & INCLUDE_HIDDEN ) { //<-- note just a single &, bitwise and
    //$flags have INCLUDE_HIDDEN
}

7
您知道使用十六进制数与整数相比的优势吗?只是出于好奇 :) - AlexMorley-Finch
@AlexMorley-Finch 看起来这完全是个人喜好,使用十进制或十六进制似乎并不会改变执行时间。 - Brian Leishman

1
其他人提供了很好的建议,但现在更常见的是传递关联数组而不是位掩码。这样更易读,并允许您传递除了true/false值之外的其他变量。像这样:
myFunction(['includeHidden' => true, 'fileExts' => false, 'string' => 'Xyz']);

function myFunction($options) {
    // Set the default options
    $options += [
        'includeHidden' => false,
        'fileExts' => true,
        'string' => 'Abc',
    ];

    if ($options['includeHidden']) {
        ...
    }
    ...
}

3
我过去也是这样做的,但是我的同伴抱怨他们的IDE没有“类型提示”,没有适当的 doc注释,而且函数在未传递必需参数时不会抛出错误。如果PHP中存在“命名参数”,那么理想情况下将解决所有这些问题。 - Timo Huovinen
嗨@TimoHuovinen - 感谢您的评论。添加文档注释很容易,可以概述选项和默认值,如果需要,抛出异常,并使参数默认为允许类型提示的数组。这对您有用吗? - Simon East
哦,我明白了。Symfony OptionsResolver类更进一步提供了更多功能。很酷。 - Simon East
"添加文档注释以概述选项和默认值很容易",是的,你可以像twig一样添加它们,但这仍然是一种变通方法。"使参数默认为允许类型提示的数组",你会如何做到这一点?我指的是 IDE 的类型提示和代码补全,其中编辑器告诉您函数可以接受哪些参数。(请记住,我也赞成使用选项数组,只是我需要弄清楚如何让伙伴们满意) - Timo Huovinen
这并没有回答问题,OP的问题是关于位掩码的。建议不要使用它们而不是回答问题并不是很有帮助。 - lots0logs
显示剩余2条评论

0
我曾经使用过一个API,但文档非常简略。它给了我们一个整数并说:
“这些值中编码有信息作为位域。位域通过将多个真/假值存储在同一整数中工作,而不是对每个值都有多个整数。要在位域中查找值,您需要利用位掩码。”
Bit     Decimal Value    Setting
0       1                Display
1       2                Sell
2       4                Kiosk Display
3       8                No Passes
4       16               Dolby Digital
5       32               THX
6       64               DLP
[etc]

我根据其他答案解决了这个问题,并且想分享一下我编写的函数。

最终我编写了这个函数:

<?php
function bitmap_decode( $label_array, $value, $return_item ) {
    $label_array = array_flip( $label_array ); // swap the keys and values
    $i = $label_array[ $return_item ]; // get the decimal key value for the item on the list
    $i =  2**$i; // use 2 to the nth power to get the decimal bitmap value for the item
    return $value & $i ? 1 : 0; // use the & operator to determine if the value is true or false. return 1 or 0 accordingly
}

...这将解析出值

$info1 = array(
    'Display',
    'Sell',
    'Kiosk Display',
    'No Passes',
    'Dolby Digital',
    'THX',
    'DLP',
);


$api_value = 5;
echo bitmap_decode( $info1, $api_value, 'Sell' ); // 0

$api_value = 35;
echo bitmap_decode( $info1, $api_value, 'Sell' ); // 1

然后我们可以做相反的事情 - 对值进行编码,而无需考虑任何非二进制数字或类似的内容:

function bitmap_encode( $items, $label_array ) {
    $return = 0;
    $label_array = array_flip( $label_array ); // swap the keys and values
    foreach ( $items as $item ) {
        $i = $label_array[ $item ]; // get the decimal key value for the item on the list
        $i = 2**$i; // use 2 to the nth power to get the decimal bitmap value for the item
        $return += $i; // add $i to the return value
    }
    return $return;
}

// set these flags as true
$flags = array( 'Display', 'Sell', 'THX' );
echo bitmap_encode( $flags, $info1 ); // 35

我知道这并不完全回答了原帖的问题,但这是在PHP中制作位掩码系统的另一种方法。


1
另外,(作为一种选择)您可以尝试这个库:https://github.com/yaroslavche/BitMask =) - yaroslavche

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接