Perl出人意料的结果

时间:2017-07-05 18:01:34

标签: perl parameter-passing

想象一下,我有这个Perl脚本

my $name = "   foo    ";
my $sn   = "     foosu";

trim($name, \$sn);

print "name: [$name]\n";
print "sn: [$sn]\n";
exit 0;


sub trim{

   my $fref_trim = sub{
                          my ($ref_input) = @_;
                          ${$ref_input} =~ s/^\s+// ;
                          ${$ref_input} =~ s/\s+$// ;
    };

   foreach my $input (@_){
       if (ref($input) eq "SCALAR"){
            $fref_trim->($input);
       } else {
            $fref_trim->(\$input);
       }
   }
}

结果:

name: [foo]
sn: [foosu]

我希望$ name是" [ foo ]"在调用trim后打印值时,sub会按照我的意愿设置$name。为什么这个工作,当它真的不应该?

我没有通过引用传递$ name,而trim sub没有返回任何内容。我希望trim子要创建$name值的副本,处理副本,但原始$name在打印时仍会有前导和尾随空格在主要代码中。

我认为这是因为@_的别名,但是foreach my $input (@_)不应该强制sub复制值,只处理值而不是别名?

我知道我可以简化这个子,我只用它作为例子。

2 个答案:

答案 0 :(得分:8)

@_的元素是原始变量的别名。您所观察到的是:

之间的区别
sub ltrim {
    $_[0] =~ s/^\s+//;
    return $_[0];
}

sub ltrim {
    my ($s) = @_;
    $s =~ s/^\s+//;
    return $s;
}

将您的代码与以下内容进行比较:

#!/usr/bin/env perl

my $name = "   foo    ";
my $sn   = "     foosu";

trim($name, \$sn);

print "name: [$name]\n";
print "sn: [$sn]\n";

sub trim {
    my @args = @_;

    my $fref_trim = sub{
        my ($ref_input) = @_;
        ${$ref_input} =~ s/^\s+//;
        ${$ref_input} =~ s/\s+\z//;
    };

   for my $input (@args) {
       if (ref($input) eq "SCALAR") {
           $fref_trim->($input);
       }
       else {
           $fref_trim->(\$input);
       }
   }
}

输出:

$ ./zz.pl
name: [   foo    ]
sn: [foosu]

另请注意,for my $input ( @array )中的循环变量不会为数组的每个元素创建新副本。见perldoc perlsyn

  

foreach循环遍历正常列表值,并将标量变量VAR依次设置为列表的每个元素。 ...

     

...

     

foreach循环索引变量是您要循环的列表中每个项目的隐式别名。

在您的情况下,这意味着,在每次迭代时$input都是@_的对应元素的别名,@_本身是作为参数传递给变量的变量的别名。子程序。

复制sub trim { my $fref_trim = sub{ my ($ref_input) = @_; ${$ref_input} =~ s/^\s+//; ${$ref_input} =~ s/\s+\z//; }; for my $input (@_) { my $input_copy = $input; if (ref($input_copy) eq "SCALAR") { $fref_trim->($input_copy); } else { $fref_trim->(\$input_copy); } } } 可以防止修改调用上下文中的变量。当然,你可以这样做:

@_

但我觉得如果你不想做出选择性的话,我会发现isArray的批发副本一次更清晰,更有效率。

答案 1 :(得分:3)

  

我认为这是因为@_的别名,但是foreach my $input (@_)不应该强制sub复制值,只处理值而不是别名?

你是对的,@_包含别名。缺少的部分是foreach也将循环变量别名化为当前列表元素。引用perldoc perlsyn

  

如果LIST的任何元素是左值,您可以通过修改循环内的VAR来修改它。相反,如果LIST的任何元素不是左值,则任何修改该元素的尝试都将失败。换句话说,foreach循环索引变量是列表中您要循环的每个项的隐式别名。

所以最终$input$_[0]的别名,这是$name的别名,这就是为什么您会看到$name中出现的更改。