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

时间:2015-05-13 09:42:01

标签: perl winapi operator-precedence

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

在单行中测试Win32::MsgBox时,我对以下内容感到困惑:给出MsgBox可能的参数是消息,一个标志的总和来选择那种按钮(值0..5)和消息框图标“常量”(MB_ICONSTOP,...)和标题

调用perl -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQUESTION, hello"会得到预期的结果

OK

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

NOK

我首先认为它来自我缺少括号,但添加一些perl -MWin32 -e"Win32::MsgBox (world, MB_ICONQUESTION+4, hello)"会产生完全相同的错误结果。

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

>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) ...

2 个答案:

答案 0 :(得分:9)

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 }

这是一个以二进制形式返回3200100000的函数,表示正在设置位)。同样,Sobrique指出,这是一个标志变量,所以你不应该使用加法,而是使用按位逻辑和/或运算符。

在你的情况下,它只接受任何参数并忽略它们。如果你期望一个常数,这有点令人困惑。

在您的实验案例中,声明

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.

其中在我看来,只有第一个是透明的,而其他的有点模糊。

答案 1 :(得分:5)

这与你如何调用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;

或者如上所述 - 代码中的变通方法 - 不要使用+而是使用|,它会对位标记操作产生相同的结果,但是由于运算符优先级永远不会被传递到子程序。 (或者当然,总是指定常量的括号)