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

Sek*_*eki 6 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"给出了预期的结果

好

虽然看起来类似的代码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"
Run Code Online (Sandbox Code Playgroud)

那个输出

called T(#1#)
called T(##)
called T(#1,43#)
results:42 ,42
Run Code Online (Sandbox Code Playgroud)

但我无法理解为什么传递给join()args 的列表T+1, 1+T被解析为T(1, 43)...

TLP*_*TLP 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
Run Code Online (Sandbox Code Playgroud)

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

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

sub MB_ICONQUESTION                     { 0x00000020 }
Run Code Online (Sandbox Code Playgroud)

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

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

在您的实验案例中,声明

print $/,'results:', join ' ,', T(1), T+1, 1+T
Run Code Online (Sandbox Code Playgroud)

是解释

print $/,'results:', join ' ,', T(1), T(+1, (1+T))
Run Code Online (Sandbox Code Playgroud)

因为从右到左执行

1+T = 43
T +1, 43 = 42
T(1) = 42
Run Code Online (Sandbox Code Playgroud)

因为加上+具有较高的优先级高于逗号,,和一元+甚至更高.

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

print $/,'results:', join ' ,', T(1), T()+1, 1+T
#                                      ^^-- parentheses
Run Code Online (Sandbox Code Playgroud)

作为一般规则,应始终使用带有子程序调用的括号.在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.
Run Code Online (Sandbox Code Playgroud)

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

  • 最终是模块中提供"常量"的错误,它忘了提供存在的'()'原型来解决这个问题. (5认同)
  • @TLP我很清楚这一点.()原型是*在现代代码中使用*有意义的少数原型之一.具体来说,它告诉/ parser /"这个函数从不接受参数",这样`CONSTANT + 1`将被解析为`CONSTANT()+ 1`而不是在它不存在时,`CONSTANT(+1)`.这个问题是原型的确切情况. (3认同)
  • 或者只是`使用常数`,我认为也适当地处理它. (2认同)

Sob*_*que 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()));
Run Code Online (Sandbox Code Playgroud)

这显然不是你想到的,但确实解释了为什么你得到你的结果.

我会在你原来的例子中建议- 而+你可能希望考虑使用,|因为它看起来非常像是MB_ICONQUESTION一个标志.

所以:

use strict;
use warnings;

use Win32 qw( MB_ICONQUESTION );

print MB_ICONQUESTION;

Win32::MsgBox( "world", 4 | MB_ICONQUESTION , "hello" );
Run Code Online (Sandbox Code Playgroud)

要么

use strict;
use warnings;

use Win32 qw( MB_ICONQUESTION );

print MB_ICONQUESTION;

Win32::MsgBox( "world", MB_ICONQUESTION | 4 , "hello" );
Run Code Online (Sandbox Code Playgroud)

产生相同的结果.

这是因为在没有括号的情况下调用子例程时的优先级 - 您可以这样做:

print "one", "two";
Run Code Online (Sandbox Code Playgroud)

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

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

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

test 1;
test +1; 
Run Code Online (Sandbox Code Playgroud)

如果我们解析这个,我们看到perl将其视为:

test 1;
test 1;
Run Code Online (Sandbox Code Playgroud)

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

sub MB_ICONQUESTION() {0x00000020}

Win32::MsgBox "world", 4 + MB_ICONQUESTION, "hello";
Win32::MsgBox "world", MB_ICONQUESTION + 4, "hello";
Run Code Online (Sandbox Code Playgroud)

也许:

use constant MB_ICONQUESTION => 0x00000020;
Run Code Online (Sandbox Code Playgroud)

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