(4 + sub) 不等于 (sub + 4)?

(4 + sub) not equals to (sub + 4)?

(编辑)TL;DR:我的问题是我虽然 Win32 API 定义的是真正的整数 常量 (如在平台 SDK headers 中),而 Win32 Perl 包装器将它们定义为 subs。从而导致one-liner解析被误解。


在 one-liner 中测试对 Win32::MsgBox 的调用时,我对以下内容感到困惑:假设 MsgBox 的可能参数是消息,标志的总和选择按钮类型(值 0..5)和消息框图标 "constants"(MB_ICONSTOP,...)和标题

调用 perl -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQUESTION, hello" 给出了预期的结果

虽然看起来相似的代码 perl -MWin32 -e"Win32::MsgBox world, MB_ICONQUESTION+4, hello" 是错误的

我首先认为这是因为我缺少括号,但是添加一些 perl -MWin32 -e"Win32::MsgBox (world, MB_ICONQUESTION+4, hello)" 给出了完全相同的错误结果。

我尝试与一位同事一起深入挖掘并使用以下代码显示传递给函数调用的参数(因为 MB_xxx 常量实际上是 subs)

>perl -Mstrict -w -e"sub T{print $/,'called T(#'.join(',',@_).'#)'; 42 }; print $/,'results:', join ' ,', T(1), T+1, 1+T"

输出

called T(#1#)
called T(##)
called T(#1,43#)
results:42 ,42

但我不明白为什么在传递给 join() 的列表中,args T+1, 1+T 被解析为 T(1, 43)...

B::Deparse 救援:

C:>perl -MO=Deparse -MWin32 -e"Win32::MsgBox world, MB_ICONQUETION+4, hello"
use Win32;
Win32::MsgBox('world', MB_ICONQUESTION(4, 'hello'));
-e syntax OK

C:>perl -MO=Deparse -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQESTION, hello"
use Win32;
Win32::MsgBox('world', 4 + MB_ICONQUESTION(), 'hello');
-e syntax OK

第一种情况下的 MB_ICONQUESTION 调用被认为是带有参数 +4, 'hello' 的函数调用。在第二种情况下,它被认为是一个没有参数的函数调用,并添加了 4。它似乎不是一个常数,而是一个函数。

在源代码中我们得到了验证:

sub MB_ICONQUESTION                     { 0x00000020 }

是returns32的函数(二进制的00100000表示设置了一个位)。同样正如 Sobrique 指出的那样,这是一个标志变量,因此您不应使用加法,而应使用按位逻辑 and/or 运算符。

在你的例子中,它只接受任何参数并忽略它们。如果您期望常数,这会有点混乱。

在你的实验案例中,语句

print $/,'results:', join ' ,', T(1), T+1, 1+T

被解读

print $/,'results:', join ' ,', T(1), T(+1, (1+T))

因为从右到左执行

1+T = 43
T +1, 43 = 42
T(1) = 42

因为加号+比逗号,有更高的precedence,而一元+甚至更高。

要消除歧义,您需要使用括号来阐明优先级:

print $/,'results:', join ' ,', T(1), T()+1, 1+T
#                                      ^^-- parentheses

作为一般规则,应该始终在子例程调用中使用括号。在 perldoc perlsub 中有 4 个调用符号:

NAME(LIST);    # & is optional with parentheses.
NAME LIST;     # Parentheses optional if predeclared/imported.
&NAME(LIST);   # Circumvent prototypes.
&NAME;         # Makes current @_ visible to called subroutine.

其中我认为只有第一个是透明的,其他的有点晦涩

这与您如何调用 T 以及 perl 如何解释结果有关。

如果我们解析您的示例,我们会得到:

BEGIN { $^W = 1; }
sub T {
    use strict;
    print $/, 'called T(#' . join(',', @_) . '#)';
    42;
}
use strict;
print $/, 'results:', join(' ,', T(1), T(1, 1 + T()));

这显然不是您想要的,但确实解释了为什么您会得到这样的结果。

我建议在您的 原始 示例中 - 而不是 + 您可能希望考虑使用 | 因为它看起来非常像 MB_ICONQUESTION 旨在成为一个标志。

所以:

use strict;
use warnings;

use Win32 qw( MB_ICONQUESTION );

print MB_ICONQUESTION;

Win32::MsgBox( "world", 4 | MB_ICONQUESTION , "hello" );

use strict;
use warnings;

use Win32 qw( MB_ICONQUESTION );

print MB_ICONQUESTION;

Win32::MsgBox( "world", MB_ICONQUESTION | 4 , "hello" );

产生相同的结果。

这是因为调用不带括号的子程序时的优先级 - 您可以这样做:

print "one", "two";

并且两者都被视为 print 的参数。 Perl 假定 sub 之后的参数将被传递给它。

+4 被枚举为参数,并传递给 T.

sub test { print @_,"\n";};

test 1;
test +1; 

如果我们对此进行解析,我们会看到 perl 将其视为:

test 1;
test 1;

所以最终 - 您发现了 Win32 中的一个错误,可以通过以下方式修复:

sub MB_ICONQUESTION() {0x00000020}

Win32::MsgBox "world", 4 + MB_ICONQUESTION, "hello";
Win32::MsgBox "world", MB_ICONQUESTION + 4, "hello";

或者也许:

use constant MB_ICONQUESTION => 0x00000020;

或者如前所述 - 代码中的解决方法 - 不要使用 + 而是使用 | 这将对位标志操作产生相同的结果,但由于运算符优先永远不会被传递到子例程中。 (或者当然,始终为常量指定括号)