为什么print($ a = a..c)产生:1E0

时间:2011-11-03 23:53:16

标签: perl

print (a..c) # this prints: abc  
print ($a = "abc") # this prints: abc

print ($a = a..c); # this prints: 1E0

我原以为会打印:abc

use strict;
print ($a = "a".."c"); # this prints 1E0

为什么呢?这只是我的电脑吗? 编辑:我有一个部分答案(范围运算符..在标量上下文中返回一个布尔值 - 谢谢)但我不明白的是: 为什么:print($ a =“a”......“c”)产生1而不是0 为什么:print($ a =“a”..“c”)产生1E0而不是1或0

3 个答案:

答案 0 :(得分:17)

这里有许多微妙的事情。首先,..实际上是两个完全不同的运算符,具体取决于调用它的上下文。在列表上下文中,它会在给定的起点和终点之间创建一个值列表(递增1)。

@numbers =  1  ..  3;  # 1, 2, 3
@letters = 'a' .. 'c'; # a, b, c (Yes, Perl can increment strings)

因为print在列表上下文中解释了它的参数

print 'a' .. 'c';    # <-- this
print 'a', 'b', 'c'; # <-- is equivalent to this

在标量上下文中,..是触发器操作符。来自perlop中的Range Operators

  

只要左操作数为false,它就是false。一旦离开   操作数为true,范围运算符在右操作数之前保持为true   如果范围操作符再次变为假,则为真。

$a = ...中分配标量值会创建标量上下文。这意味着..中的print ($a = 'a' .. 'c')是触发器运算符的实例,而不是列表创建运算符。

触发器操作符设计用于过滤文件中的行。 e.g。

while (<$fh>) {
    print if /first/ .. /last/;
}

会打印文件中的所有行,从包含first的文件开始,到以包含last的文件结尾。

触发器操作器具有一些额外的魔法设计,可以根据行号轻松过滤。

while (<$fh>) {
    print if 10 .. 20;
}

将打印文件的第10行到第20行。它通过采用特殊情况行为来实现这一点:

  

如果标量..的任一操作数是常量表达式,那么   如果操作数与当前输入相等(==),则认为该操作数为真   行号($.变量)。

字符串ac都是常量表达式,因此触发了这种特殊情况。它们不是数字,但它们用作数字(==是数字比较)。 Perl将根据需要在字符串和数字之间转换标量值。在这种情况下,两个值nummify为0.因此

print ($a = 'a' .. 'c');             # <-- this
print ($a = 0 .. 0);                 # <-- is effectively this
print ($a = ($. == 0) .. ($. == 0)); # <-- which is really this

我们已接近神秘的底部。到下一位。更多来自perlop:

  

返回的值是false的空字符串或序列   数字(以1开头)表示真实。序列号重置为   遇到的每个范围。范围中的最终序列号具有   字符串“E0”附加到它

如果您尚未从文件中读取任何行,$.将是undef,在数字上下文中为00 == 0为真,因此..返回一个真值。这是第一个真正的价值,所以它是1。因为两者左侧和右侧都是真的,第一个真值也是最后一个真值,而E0“这是最后一个值”后缀附加到返回值值。 print ($a = 'a' .. 'c')打印1E0的原因。如果您要将$.设置为非零值,则..将为false并返回空字符串。

print ($a = 'a' .. 'c'); # prints "1E0"
$. = 1;
print ($a = 'a' .. 'c'); # prints nothing

最后一块拼图(我现在可能走得太远)是赋值运算符返回一个值。在这种情况下,这是分配给$a 1 - 1E0的值。该值最终由print吐出。

1:从技术上讲,分配会为分配给的项目生成左值。即它返回变量$a的左值,然后计算为1E0

答案 1 :(得分:12)

这是列表上下文与标量上下文的问题,如perldoc perlop中所述:

  

在标量上下文中,“..”返回一个布尔值。运营商是   双稳态,如触发器,并模拟行范围(逗号)   sed,awk和各种编辑的运营商。每个“..”运算符   即使在调用子例程时,它也会保持自己的布尔状态   包含它。只要其左操作数为假,它就是假的。   一旦左操作数为真,范围运算符将保持为真,直到   右操作数为true,范围运算符变为false   再次。直到下一次范围操作员才会变为假   被评估。它可以测试正确的操作数并在其上变为false   相同的评价它成为现实(如在awk中),但它仍然返回true   一旦。如果你不希望它在下一次之前测试正确的操作数   评估,如在sed中,只使用三个点(“...”)而不是两个点。在   所有其他方面,“......”的行为就像“......”一样。

[剪断]

  

范围中的最终序列号附加了字符串“E0”   它,它不会影响它的数值,但会给你一些东西   搜索是否要排除端点。

编辑以回应DanD男士的评论:

我发现它有点难以消化;坦率地说,我很少使用..运算符,甚至更少使用标量上下文。但是,例如,输入循环中的表达式5..10隐式地与$.的当前值进行比较(这是我没有引用的描述的一部分;请参阅手册)。在第5行到第9行,它产生一个真值(实验表明它是一个数字,但文档没有这么说)。在第10行,它产生一个附加"E0"的数字 - 即,它是指数表示法,但是没有"E0"时的值相同。

"E0"调整的目的是让你检测你是否在指定的范围中标记特殊处理范围内的最后一行。如果没有"E0",您将无法专门处理最终比赛。

一个例子:

#!/usr/bin/perl

use strict;
use warnings;

while (<>) {
    my $dotdot = 2..4;
    print "On line $., 2..4 yields \"$dotdot\"\n";
}

给出5行输入,打印:

On line 1, 2..4 yields ""
On line 2, 2..4 yields "1"
On line 3, 2..4 yields "2"
On line 4, 2..4 yields "3E0"
On line 5, 2..4 yields ""

这可以让你检测到一条线在范围之内或之外,当它是范围中的最后一行时。

但是标量..可能更常用于其布尔结果,通常用于单行;例如,perl -ne 'print if 2..4'将打印您提供的任何输入的第2,3和4行。它故意类似于sed -n '2,4p'

答案 2 :(得分:10)

可以通过咨询perldoc的perlop页面找到答案:

  

二进制“..”是范围运算符,它实际上是两个不同的运算符,具体取决于上下文。在列表上下文中,它返回一个值列表(从一个值开始计算)从左值到右值...

这是熟悉的用法,由print "a" .. "c";调用,因为函数的参数在列表上下文中计算。 (如果在标量上下文中对它们进行评估,那么print @list将打印@list的大小,这几乎绝对不是人们通常想要的。)

  

在标量上下文中,“..”返回一个布尔值。运算符是双稳态的,就像一个触发器,并模拟sed,awk和的行范围(逗号)运算符各种编辑。每个“..”运算符都保持自己的布尔状态,甚至在调用包含它的子例程时也是如此。 只要左操作数为false,就为false。一旦左操作数为真,范围运算符将保持为真,直到右操作数为真,此后范围运算符再次变为假。在下次计算范围运算符之前,它不会变为假。它可以测试正确的操作数,并在相同的评估中变为false(如在awk中),但它仍然返回true一次。如果您不希望它在下一次评估之前测试正确的操作数,就像在sed中一样,只需使用三个点(“...”)而不是两个点。在所有其他方面,“......”的行为就像“......”一样。

它进一步详细说明,但粗体部分是理解操作员如何工作的重要部分。标量上下文由$a =强制,即分配给标量左值。如果您执行@a =,则会打印出您期望的内容。

请注意,"a" .. "b"不会生成字符串"abc",而是生成列表("a", "b", "c")。如果使用列表,您将得到类似的结果(尽管列表强制进入标量上下文时打印的值会有所不同)。