【问题标题】:= and , operators in Perl= 和 , Perl 中的运算符
【发布时间】:2011-02-21 01:26:55
【问题描述】:

请解释这种明显不一致的行为:

$a = b, c;
print $a; # this prints: b

$a = (b, c);
print $a; # this prints: c

【问题讨论】:

  • 真正的 WTF 是使用 Perl w/o "use strict"
  • “WTF”——如果你知道得更好,就不要在 Perl 中使用 strict ,但他可能不知道得更好,这可能是他在 Perl 学校的第一堂课。毕竟,语言确实支持它。不要认为他应该因为提出这个问题而被否决。
  • 为什么不同的代码产生不同的输出算作“不一致”?
  • '明显不一致'不等于'不一致'
  • use strict 并不是真正的问题 - 好吧,我使用了 strict 不喜欢的裸词,但我可以很容易地提出这样的问题:use strict; $a="b", "c";打印 $a; # 这打印出: b # 然后说: $a=("b","c");打印 $a; # 这打印:c

标签: perl


【解决方案1】:

= 运算符的优先级高于 ,。 并且逗号运算符丢弃其左侧参数并返回右侧参数。

请注意,逗号运算符的行为因上下文而异。来自perldoc perlop

二进制“,”是逗号运算符。在 它评估其左侧的标量上下文 参数,扔掉那个值,然后 评估它的正确论点并且 返回该值。这就像 C 的逗号运算符。

在列表上下文中,它只是列表 参数分隔符,并插入两者 它的参数到列表中。这些 参数也从左侧评估 向右。

【讨论】:

  • "逗号运算符丢弃它的左参数并返回右参数"不,它没有:@a = (2,3);打印@a; # 这打印:23
  • 我可以在哪个 perldoc 上查找此构造行为的解释:“( $a ) = ( 'b', 'c' );”?
  • 哦,天哪,不要在 $a 周围加上括号 - 我正在努力将事情保持原样,一些专家会轻蔑地说“这一切都有一个完全合理的解释:如果 a那么 b 除非 c 当且仅当 e 否则 f 或 x" :p
  • 左参数:$a = "b",右参数:"c"。 left 被评估,所以 $a 包含“b”,right 返回。由于您不占用返回的值,因此不会发生任何事情。
【解决方案2】:

由于 eugene 的回答似乎给 OP 留下了一些问题,我尝试基于此进行解释:

$a = "b", "c";
print $a;

这里左边的参数是$a = "b",因为= 的优先级高于,,它将首先被评估。之后$a 包含"b"

正确的参数是 "c" 并且将很快返回。

当您打印$a 时,显然是在您的屏幕上打印b

$a = ("b", "c");
print $a;

这里将首先评估术语("b","c"),因为括号的优先级更高。它返回"c",并将分配给$a

所以你在这里打印"c"

$var = ($a = "b","c");
print $var;
print $a;

这里$a 包含“b”,$var 包含“c”。

一旦你得到了优先规则,这是完全一致的

【讨论】:

  • 很好的解释。我试图打破你的逻辑,但据我有限的经验,据我所知,它非常强大。也许如果我继续深入思考并重新安排事情,回来说“不,那也行不通,因为怎么样:(@var = scalar $a, @c) = 或其他东西。但它看起来不错。我绝对通过提出这个问题加深了我的理解,所以感谢大家尝试回答。我还有其他关于 Perl 的事情我不了解,但我现在真的觉得这是深入了解它们的地方。
  • 这实际上只是eugene y第一句话的加长版。
  • 是的,如果你先发布了你的,那么我也会以同样的方式反对你的。两种解释都说“逗号运算符丢弃其左侧参数并返回右侧参数。”这并非在所有情况下都是正确的。在某些情况下是错误的。例如。 @a = ("b", "c")
  • @DanD man:逗号运算符求值(例如,可能有副作用)并丢弃其左侧参数,然后求值并返回右侧参数——仅在标量上下文中。在列表上下文中,就像分配给数组一样,逗号用作列表连接运算符。
【解决方案3】:

因为 eugene 和 mugen 已经用很好的例子很好地回答了这个问题,所以我将设置一些概念,然后询问 OP 的一些概念性问题,看看它是否有助于阐明一些 Perl 概念。

第一个概念是符号$@ 的含义(我们不会在这里讨论%)。 @ 表示多个项目(说“这些东西”)。 $ 表示一项(表示“这件事”)。要获取数组@a 的第一个元素,您可以执行$first = $a[0],获取最后一个元素:$last = $a[-1]。注:不是@a[0]@a[-1]。您可以通过 @shorter = @longer[1,2] 进行切片。

第二个概念是void、标量和列表上下文的区别。 Perl 具有使用容器(标量、数组等)的上下文的概念。看到这一点的一个简单方法是,如果您将列表(我们将得到这个)存储为数组@array = ("cow", "sheep", "llama"),那么我们将数组存储为标量$size = @array,我们得到数组的长度。我们还可以通过使用scalar 运算符(例如print scalar @array)来强制执行此行为。为了清楚起见,我再说一遍:标量上下文中的数组(不是列表)将返回,而不是元素(如列表那样),而是数组的长度。

请记住,当您只期望一项时,使用 $ 印记之前,即 $first = $a[0]。通过这种方式,您知道您处于标量环境中。现在,当您调用$length = @array 时,您可以清楚地看到您在标量上下文中调用数组,因此您在列表上下文中触发了数组的特殊属性,您会得到它的长度。

这还有一个很好的功能可以测试数组中是否有元素。 print '@array contains items' if @array; print '@array is empty' unless @array。 if/unless 测试在数组上强制标量上下文,因此 if 看到数组的长度而不是它的元素。由于除零之外的所有数值都是“真实的”,因此如果数组的长度非零,则 if @array 语句的计算结果为 true,您将得到 print 语句。

空上下文意味着某些操作的返回值被忽略。在 void 上下文中一个有用的操作可能类似于递增。 $n = 1; $n++; print $n; 在此示例中,$n++(返回后递增)处于 void 上下文中,因为未使用(存储、打印等)其返回值“1”。

第三个概念是列表和数组的区别。列表是一组有序的值,数组是一个包含一组有序值的容器。例如,您可以在体操中看到差异,在使用 sort 后必须先获取特定元素,而无需先存储结果(例如,尝试 pop sort { $a cmp $b } @array,这不起作用,因为 pop 不作用于列表,仅一个数组)。

现在我们可以问,当您尝试您的示例时,您希望 Perl 在这些情况下做什么?正如其他人所说,这取决于优先级。

在您的第一个示例中,由于 = 运算符的优先级高于 ,,因此您实际上并未为变量分配列表,您所做的更像是 ($a = "b"), ("c"),它实际上与字符串“c”。事实上,它是在 void 上下文中调用的。启用警告后,由于此操作不会完成任何操作,Perl 会尝试通过以下消息警告您您可能不是故意这样做的:Useless use of a constant in void context

现在,当您尝试将列表存储到标量(或在标量上下文中使用列表)时,您希望 Perl 做什么?它不会存储列表的长度,这只是数组的一种行为。因此,它必须存储列表中的值之一。虽然我知道这在规范上是不正确的,但这个例子与实际情况非常接近。

my @animals = ("cow", "sheep", "llama");
my $return;
foreach my $animal (@animals) {
  $return = $animal;
}
print $return;

因此你得到了列表的最后一个元素(典型的区别是前面的值从来没有被存储然后被覆盖,但是逻辑是相似的)。

有一些方法可以将看起来像列表的东西存储在标量中,但这涉及到引用。在perldoc perlreftut 中了解更多信息。

希望这能让事情变得更清楚一些。最后我要说,在你掌握 Perl 的优先规则之前,为列表和函数的参数加上显式括号永远不会有什么坏处。

【讨论】:

  • 谢谢,我不知道 void context 是什么意思,但你解释得很清楚。 $a=("a", "b"); print $a # this prints b but: @a = ("a", "b"); $a = @a; print $a; # this prints: 2. 两个 print 2 或两个 print b 不是更一致吗?
  • 再次重读我的文本,我没有明确说明在标量上下文中调用数组时会发生什么。将编辑。
  • 感谢您花时间进一步解释。 @a[0] 确实有效。我喜欢@longer[1,2]。所以我现在知道:标量上下文中的列表返回列表的最后一部分(除非优先级妨碍了)。标量上下文中的数组返回一个数字,除非您说: $a = "@b" 在这种情况下,标量上下文中的数组返回连接在一起的整个数组。这让我尝试了 $a = "(a, b)";它的行为符合预期。
  • @a[0] 有效,但作为只有一个元素的数组切片。启用警告(您总是使用strictwarnings 对吗?)这会给您一个警告。 Perl6 将改变印记的使用方式,以允许更合乎逻辑(在某些方面)的常量印记(@a = ....; $b = @a[0]; 等)
【解决方案4】:

有一种简单的方法可以查看 Perl 如何处理这两个示例,只需运行它们:

perl -M@987654321@=@987654322@,@987654323@ -e'...'

如您所见,不同之处在于操作顺序与您可能怀疑的略有不同。

perl -MO=Deparse,-p -e'$a = a, b;print $a'
(($a = 'a'), '???');
print($a);
perl -MO=Deparse,-p -e'$a = (a, b);print $a'
($a = ('???', 'b'));
print($a);

注意:您看到的是'???',因为原始值已被优化掉。

【讨论】:

  • Ty,我喜欢这个,我会努力记住它。我想你不能将 -p -e 聚集在一起来制作 -pe -it 会形成一个集群 -f :p
  • 在另一个问题中,我将-p 误认为是 perl 的参数,但实际上它是 Deparse 的参数,正如您在链接文档中看到的那样。所以-p-e不能聚类。
  • 查看 perldoc perlrun 以了解如何解析 -MO=Deparse,-p
最近更新 更多