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

Perl (4+;sub)不等于(sub+;4)?,perl,winapi,operator-precedence,Perl,Winapi,Operator Precedence,(编辑)TL;DR:我的问题是,我认为Win32 API定义的是真正的整数常量(如在平台SDK头中),而Win32 Perl包装器将它们定义为子类。从而导致了一行解析的误解 在一行程序中测试对Win32::MsgBox的调用时,我对以下内容感到困惑:假定MsgBox的可能参数是消息、选择按钮类型(值0..5)和消息框图标“常量”(MB_ICONSTOP,…)的标志和标题 调用perl-MWin32-e“Win32::MsgBox world,4+MB_ICONQUESTION,hello”会得

(编辑)TL;DR:我的问题是,我认为Win32 API定义的是真正的整数常量(如在平台SDK头中),而Win32 Perl包装器将它们定义为子类。从而导致了一行解析的误解


在一行程序中测试对
Win32::MsgBox
的调用时,我对以下内容感到困惑:假定
MsgBox
的可能参数是消息、选择按钮类型(值0..5)和消息框图标“常量”(
MB_ICONSTOP
,…)的标志和标题

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

虽然看起来类似的代码
perl-MWin32-e“Win32::msgboxworld,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_iconquest
调用被视为带有参数
+4,“hello”
的函数调用。在第二种情况下,它被认为是一个没有参数的函数调用,并且添加了4个参数。它似乎不是一个常数,而是一个函数

在源代码中,我们验证了这一点:

sub MB_ICONQUESTION                     { 0x00000020 }
它是一个返回
32
(二进制
00100000
,表示正在设置的位)的函数。正如所指出的,这是一个标志变量,因此不应使用加法,而应使用位逻辑和/或运算符

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

在你的实验案例中,陈述

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
因为加号
+
比逗号
和一元
+
更高

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

print $/,'results:', join ' ,', T(1), T()+1, 1+T
#                                      ^^-- parentheses
作为一般规则,应始终在子例程调用中使用括号。中有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()));
这显然不是你想要的,但确实解释了为什么你会得到你想要的结果

我建议在原来的例子中——而不是<代码> +>代码>,你可能想考虑使用<代码> <代码>,因为它看起来非常像<代码>。 因此:

产生同样的结果

这是因为在调用不带括号的子例程时会出现进位-您可以执行以下操作:

print "one", "two";
这两个参数都被视为打印的参数。Perl假定将
后面的参数传递给它

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

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

test 1;
test +1; 
如果我们对此进行深入分析,我们会看到perl将其视为:

test 1;
test 1;
因此,最终-您发现Win32中有一个bug,可以通过以下方式修复:

sub MB_ICONQUESTION() {0x00000020}

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

use constant MB_ICONQUESTION => 0x00000020;

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

对这个问题的否决票显然是错误的。这是一个有点混淆子程序和运算符优先级的有效问题。有趣的是,您调用
MB_ICONQUESTION
一个常量,然后使用子程序进行实验。您问题的标题应该表示子程序,而不是常量。@TLP:我的monk同事告诉我,
MB_iconquest
实际上是一个sub(在perl中常量通常是sub),所以我们尝试了后面的测试。我还将更改标题,因为我熟悉win32 API中的true C
MB_xx
定义,感谢您为我指出
Deparse
包,帮助我理解perl是如何读取我的代码的!最终,提供“常量”的模块中出现了一个错误,它忘记了给出正是为了解决这个问题而存在的“()”原型。或者只是
使用常量
,我认为它也能正确处理。@LeoNerd原型往往是问题而不是解决方案。它们的存在是为了使子例程像内置程序一样工作,而不是用于参数检查。@TLP我很清楚这一点。()原型是少数几个在现代代码中使用起来有意义的原型之一。具体地说,它告诉/parser/“此函数从不接受参数”,因此
CONSTANT+1
将被解析为
CONSTANT()+1
,而不是在没有它的情况下,
CONSTANT(+1)
。这个问题正是prototype所适用的情况。感谢使用binary
代替添加的技巧,顺便说一句,我没有考虑使用显式括号,因为起初我不知道API常量实际上不是一个常量,而是一个sub。。。我有点像Perl新手:o)每个人都从一开始就开始学习一门语言。你在那里提出了一个很好的问题,这是非常值得赞赏的——有趣,有足够的深度来重现和研究。
sub MB_ICONQUESTION() {0x00000020}

Win32::MsgBox "world", 4 + MB_ICONQUESTION, "hello";
Win32::MsgBox "world", MB_ICONQUESTION + 4, "hello";
use constant MB_ICONQUESTION => 0x00000020;