Perl中逗号运算符的执行顺序

Posted

技术标签:

【中文标题】Perl中逗号运算符的执行顺序【英文标题】:Order of execution with comma operator in Perl 【发布时间】:2011-08-01 21:34:30 【问题描述】:

考虑以下脚本:

print'+'x$z,($z=1,$w)?'':$_ for 1..3;

正如我所料,这会打印出1+2+3。变量$z 最初是未赋值的,所以'+'x$z 评估为空;之后,$z 设置为 1,因此 '+'x$z 现在计算为 +

但是,如果我将其更改为 $z 包含 + 本身:

print$z,($z='+',$w)?'':$_ for 1..3;

脚本现在打印+1+2+3。这似乎在向我暗示执行顺序不同,但我不明白为什么。

关于导致这两个示例行为不同的执行顺序的确切规则是什么?执行顺序是否明确?

【问题讨论】:

有什么理由不使用print join '+', 1..3?我的意思是高尔夫,对吧? @Zaid:因为这里的代码只是一个简化的例子。 Here is my golf entry that prompted this question。相关声明为(现在)print$t>0?"$z":'-',($z='+',$w/=$s)-1?"\\frac$u$w":$u,$p>1?"x^$p":x x$p 【参考方案1】:

这是我试图理解你的两个例子的尝试。考虑这个脚本:

use strict;
use warnings;
use Data::Dumper;

sub dd  print Dumper(\@_) 

my $z = 0;

dd($z + 2, ($z = 1));  # Similar to your Version 1.
dd($z,     ($z = 1));  # Similar to your Version 2.

输出,带有一些 cmets:

$VAR1 = [
          2,              # The constant 2.
          1               # $z by reference, which prints as 1.
        ];
$VAR1 = [
          1,              # $z by reference.
          $\$VAR1->[0]  # Ditto.
        ];

在版本 1 中,Perl 不能将 $z + 2 直接传递给 dd()。它必须评估表达式。该评估的结果(常量 2)作为第一个参数传递。第二个参数也被求值:$z 设置为 1,赋值的返回值为$z,然后$z 通过引用传递给dd()

在版本 2 中,Perl 可以简单地通过引用直接传递第一个参数:无需计算更大的表达式。第二个参数与版本 1 中的相同。结果是dd() 两次接收到相同的变量,如Data::Dumper 输出所示。

【讨论】:

谢谢! (虽然您似乎只是在重复ikegami 所说的话,然后证明这是真的……) @Timwi 也许是这样,但混淆较少,恕我直言。 Nit:你说引用被传递,但那是不准确的。不涉及参考。 “$z 是函数的passed by reference”是正确的术语。这是通过对@_ 的元素进行别名来实现的。【参考方案2】:

原始答案

您需要通过perl -MO=Deparse,-p 运行它。第一段代码显示了这一点:

print(('+' x $z), ((($z = 1), $w) ? '' : $_)) foreach (1 .. 3);

但第二段代码显示了这一点:

print($z, ((($z = '+'), $w) ? '' : $_)) foreach (1 .. 3);

困惑和困扰

显然证明不足以向某些人充分解释问题。它不应该是,因为我认为它非常清楚。

公认的解决方案错误地指出,这在某种程度上与 Perl 通过隐式引用传递标量变量的事实有关。它与那一点关系都没有。这是一个简单的优先级和评估顺序问题。我原本打算让 Deparse 输出清楚地说明这一点。

显然有些人仍然感到困惑。


第一版

很好,这是你的解释,全都放在银盘上给你。

这个:

print'+'x$z,($z=1,$w)?'':$_ for 1..3;

由 Deparse 和一些额外的格式提供,相当于:


    ($w, $z) = (undef, undef);
    for (1..3) 
        print(("+" x $z), ((($z = 1), $w) ? "" : $_))
    
 continue 
    print "\n";

现在,展开循环并分离出产生这个时会发生什么:


     ($w, $z) = (undef, undef);
    
        local $_ = 1;
        $temp = "+" x $z; # $z is undef
        $z = 1;
        print $temp, $_;
    
    
        local $_ = 2;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    
    
        local $_ = 3;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    
 continue 
    print "\n";

这三个都产生相同的输出:1+2+3

第二版

现在我们重新开始:

print$z,($z='+',$w)?'':$_ for 1..3;

并生成一个解析版本:


    ($w, $z) = (undef, undef);
    for (1..3) 
        print($z, ((($z = "+"), $w) ? "" : $_));
    
 continue 
    print "\n";

后跟一个循环展开版本:


    ($w, $z) = (undef, undef);
    
        local $_ = 1;
        $z = "+";
        print $z, $_;
    
    
        local $_ = 2;
        $z = "+";
        print $z, $_;
    
    
        local $_ = 3;
        $z = "+";
        print $z, $_;
    
 continue 
    print "\n";

所有三个版本,出于我真正希望现在非常清晰的原因,打印相同的结果:+1+2+3


进一步启蒙

追踪正在发生的事情的最佳方法是对其进行跟踪:

tie $z, "Tie::Trace", "z";
tie $w, "Tie::Trace", "w";

($w, $z) = (undef, undef);
print'+'x$z,($z=1,$w)?'':$_ for 1..3;
print "\n";


    ($w, $z) = (undef, undef);
    for (1..3) 
        print(("+" x $z), ((($z = 1), $w) ? "" : $_))
    
 continue 
    print "\n";



     ($w, $z) = (undef, undef);
    
        local $_ = 1;
        $temp = "+" x $z; # $z is undef
        $z = 1;
        print $temp, $_;
    
    
        local $_ = 2;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    
    
        local $_ = 3;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    
 continue 
    print "\n";


($w, $z) = (undef, undef);
print$z,($z='+',$w)?'':$_ for 1..3;
print "\n";


    ($w, $z) = (undef, undef);
    for (1..3) 
        print($z, ((($z = "+"), $w) ? "" : $_));
    
 continue 
    print "\n";



    ($w, $z) = (undef, undef);
    
        local $_ = 1;
        $z = "+";
        print $z, $_;
    
    
        local $_ = 2;
        $z = "+";
        print $z, $_;
    
    
        local $_ = 3;
        $z = "+";
        print $z, $_;
    
 continue 
    print "\n";


package Tie::Trace;

sub TIESCALAR 
    my($class, $name, $value) = @_;
    return bless 
        NAME  => $name,
        VALUE => undef,
     => $class;


sub FETCH 
    my($self) = @_;
    my $name = '$' . $self->NAME;
    my $value = $self->VALUE;
    print STDERR "[reading value ", defined($value) ? $value : "undef",
            " from $name]\n";
    return $value;


sub STORE 
    my($self, $value) = @_;
    my $name = '$' . $self->NAME;
    print STDERR "[writing value ", defined($value) ? $value : "undef",
            " into $name]\n";
    $self->VALUE = $value;
    return $value;

当你运行它时,它会产生这个相当令人满意的输出:

[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
+1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
+1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value + from $z]
[writing value + into $z]
[reading value + from $z]
[writing value + into $z]
[reading value + from $z]
+1+2+3

总结

我现在已经费力地证明了这里实际发生的事情与传递引用无关。它仅与评估顺序有关,与其他无关。

【讨论】:

非常感谢您的详细讨论。但是,冒着使您失去动力的风险,恐怕您还没有对一般情况有所启​​发。从您的回答中,我将无法推断出任何其他代码的执行顺序。特别是,你说在第一个例子中,'+' x $z存储在一个临时变量中,然后$z被修改;但在第二个示例中,不知何故没有临时变量,$z 在传递给打印之前被修改。为什么'+' x $z 存储在临时文件中而$z 不是?似乎是随机的。我想知道这背后的规则。 此外,您声称我接受的答案是错误的,并且没有传递引用。我使用一个非常简单的脚本对此进行了测试:sub xyz $_[0]++; $c = 1; xyz($c); print $c; 如果你是对的,这应该打印1。但是,它会打印2,表明存在 传递引用。 @Timwi:我从来没有说过 Perl 不做标量值的传递引用;相信我,我完全清楚它确实如此,以及它是如何做到的。我说过引用传递与正在发生的事情完全无关,这完全可以通过简单的优先级和执行顺序问题来解释。除了splitmapgrep 之外,任何出现在槽中的作为函数调用参数的表达式在函数调用本身被计算之前都会被完全计算。显然,$z=1 必须首先评估,因为逗号运算符的同步点。 @Timwi, ɪɴ sʜᴏʀᴛ:因为$z="+"的赋值必须发生在对print的调用之前,所以当print最终发生时,传入的值是"+"。这就是为什么print "lost$xfound\n" if $x = "+" 总是会打印出"lost+found":分配发生在print 甚至被调用之前。无需借助引用传递来解释这一点。 这就是我怀疑 tchrist 出错的地方。谢谢,@ikegami。【参考方案3】:

参数在 Perl 中通过引用传递。

print $z, ($z='+',$w) ? '' : $_;

基本上是


   local @_;
   alias $_[0] = $z;
   alias $_[1] = ($z='+',$w) ? '' : $_;
   &print;

因为$_[0]$z 的别名,所以对$z 的更改会反映在$_[0] 中,即使这些更改发生在评估参数之后。

你可以在下面看到同样的效果:

my $x = 3;
sub f  
   ++$x;
   print("$_[0]\n");

f($x);  # 4

【讨论】:

谢谢,这就解释了。如果我将$z 更改为"$z",它将被评估并按预期再次工作。谢谢! 确实,@Timwi。 "$z"$z 创建一个新字符串,对$z 的任何更改都不会影响该副本。

以上是关于Perl中逗号运算符的执行顺序的主要内容,如果未能解决你的问题,请参考以下文章

逗号运算符的三角归约如何知道制作所有列表的列表?

c语言 逗号表达式

逗号运算符和逗号表达式

js中的逗号运算符

逗号表达式

c语言逗号表达式