修改$ _

时间:2015-12-03 04:50:18

标签: perl coding-style language-features built-in

我试图在我的代码中扩展隐式$_(全局"主题"变量)的使用。 Perlmonks对函数有this (outdated?) article,在没有显式变量的情况下接受$_

我遇到的问题是我不知道哪些功能设置$_。我知道至少mapgrepfor / foreach会改变$_的值,但我认为必须有更多。我也不清楚与$_有关的任何范围问题,如:

for (@array_of_array_refs)
{
  for (@$_)
  {
    print;
  }
  print;  # what does this print?
}

是否有一系列功能或一系列指导方针,因此我会直观地了解如何避免崩溃$_

3 个答案:

答案 0 :(得分:8)

Steffen Ullrich's answer具有误导性,所以我必须在这里作出回应。我可能错过了一些事情,但现在已经很晚了。并且,Learning Perl已经解释了这一切。 ;)

local运算符在词法范围内不起作用。尽管如此,它并不仅限于它所处的障碍。人们通常会在理解中遇到这个问题,因为他们不会尝试。像"外面"和"内部"对local有误导性和危险性。

考虑这种用法,其中有一个打印$_的全局值的函数:

$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();

sub show_it { print "\$_ is $_\n"; }

sub inside {
    local $_;

    $_ = 'Inside';
    show_it();
    }

当你运行它时,你会看到块内的$_值设置在块外:

$_ is Outside
$_ is Inside
$_ is Outside

local适用于包变量。它暂时使用一个新值,直到块结束。但是,作为一个包变量,它会在程序中的任何地方发生变化,直到local的范围结束。运营商具有词汇范围,但其效果无处不在。您正在为全局变量提供临时值,并且该全局变量仍然是全局变量。 local变量具有全局效应但是词汇生命周期。它们会更改程序中无处不在的值,直到该范围结束。

正如我之前所写,谈论"内部"是错误的。和"外面"与local。它"之前"和"之后"。我将展示更多即将到来的东西,甚至时间也会瓦解。

my完全不同。它根本不适用于包变量。也被称为"词汇变量",这些变量并不存在于其范围之外(即使像PadWalker这样的反魔术模块看它们)。程序的任何其他部分都无法看到它们。它们仅在其范围内创建的范围和子范围中可见。

Perl v5.10允许我们创建$_的词汇版本(和fixed and made experimental in v5.16 - 不要使用它。另请参阅The good, the bad, and the ugly of lexical $_ in Perl 5.10+)。我可以让我之前的例子使用:

use v5.10;

$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();

sub show_it { print "\$_ is $_\n"; }


sub inside {
    my $_;
    $_ = 'Inside';
    show_it();
    }

现在输出不同了。词法$_与任何其他词汇变量具有相同的效果。它不会影响其范围之外的任何内容,因为这些变量只存在于它们的词法范围内:

$_ is Outside
$_ is Outside
$_ is Outside

但是,要回答原来的问题。 Perlmonks帖子Builtin functions defaulting to $_仍然很好,但我不认为它与此相关。这些函数使用$_,而不是设置它。

关于Perl的重要事项是没有简短的答案。 Perl做了有意义的事情,而不是使它保持一致的事情。毕竟,它是post-modern language

不用担心更改$_的方法不是更改$_。避免使用它。我们在Effective Perl Programming中有很多类似的建议。

的foreach

循环结构foreach及其for同义词使用$_的本地化版本来引用当前主题。在循环内部,包括循环调用的任何内容,使用当前主题:

use v5.10;

$_ = 'Outside';
show_it();
sub show_it { say "\$_ is $_"; }

my @array = 'a' .. 'c';
foreach ( @array ) {
    show_it();
    $_++
    }

say "array: @array";

注意foreach循环后的数组。即使foreach本地化$_,Perl也会对值进行别名而不是复制它。即使该值位于外部词法范围内,更改控制变量也会更改原始值:

$_ is Outside
$_ is a
$_ is b
$_ is c
array: b c d

不要使用$_作为控制变量。我只在真正的短程序中使用默认值,主要是因为我希望控件变量在大程序中具有有意义的名称。

map和grep

foreach一样,mapgrep使用$_作为控制变量。您不能为这些使用不同的变量。您仍然可以通过我在上一节中展示的性能增强别名来影响范围之外的变量。

同样,这意味着有一些范围泄漏。如果您更改了区块内的$_,并且$_是输入列表中的其中一项,那么外部$_会发生变化:

use v5.10;
$_ = 'Outside';
my @transformed = map { $_ = 'From map' } ( $_ );
say $_;

对于中等复杂的内联块,我将$_指定给词法变量:

my @output = map { my $s = $_; ... } @input;

如果你对$_感到紧张,不要在map内做map的邪恶伎俩:

my @words = map {
    map { split } $_
    } <>;

这是一个愚蠢的例子,但我过去做过这样的事情,我需要将主题变成一个列表。

while(&lt;&gt;)

Perl有一个方便的小习惯用法,它将文件句柄中的下一行分配给$_。这意味着而不是:

while( defined( $_ = <> ) )

您可以通过以下方式获得完全相同的内容:

while( <> )

但是,$_中的任何值都会留在$_中。

$_ = "Outside\n";
show_it();
sub show_it { print "\$_ is $_" }

while( <DATA> ) {
    show_it();
    }

show_it();

__DATA__
first line
second line
third line

输出看起来有点奇怪,因为最后一行没有值,但它是分配给$_的最后一个值:行输入操作符在defined测试之前分配的undef停止循环:

$_ is Outside
$_ is first line
$_ is second line
$_ is third line
$_ is

last放在那里,输出会改变

$_ = "Outside\n";
show_it();
sub show_it { print "\$_ is $_" }

while( <DATA> ) {
    show_it();
    last;
    }

show_it();

__DATA__
first line
second line
third line

现在分配的最后一个值是第一行:

$_ is Outside
$_ is first line
$_ is first line

如果您不喜欢这样,请不要使用这个成语:

while( defined( my $line = <> ) )

模式匹配

替换运算符s///默认绑定到$_并且可以更改它(这就是重点)。但是,使用v5.14,您可以使用the /r flag,它会保留原始内容并返回修改后的版本。

匹配运算符m//也可以更改$_。它不会更改值,但可以设置位置标志。这就是Perl如何在标量上下文中进行全局匹配:

use v5.10;

$_ = 'Outside';
show_it();
sub show_it { say "\$_ is $_ with pos ", pos(); }

foreach my $time ( 1 .. 5 ) {
    my $scalar = m/./g;
    show_it();
    }

show_it();

即使值相同,$_中的某些标量设置也会发生变化:

$_ is Outside with pos
$_ is Outside with pos 1
$_ is Outside with pos 2
$_ is Outside with pos 3
$_ is Outside with pos 4
$_ is Outside with pos 5
$_ is Outside with pos 5

你可能不会遇到这个问题。您可以通过与$_匹配失败来重置位置。也就是说,除非您使用/c标志。即使标量值没有变化,其部分记账也发生了变化。这是problems with lexical $_之一。

匹配时会发生另一件奇怪的事情。每个匹配变量是动态范围的。他们不会改变他们在外部范围内的价值观:

use v5.10;

my $string = 'The quick brown fox';

OUTER: {
    $string =~ /\A(\w+)/;
    say  "\$1 is $1";

    INNER: {
        $string =~ /(\w{5})/;
        say  "\$1 is $1";
        }

    say  "\$1 is $1";
    }

$1范围内OUTER的值未被$1中的INNER替换:

$1 is The
$1 is quick
$1 is The

如果这会伤害你的头脑,请不要使用每个匹配变量。马上分配(只有当你成功匹配时):

my $string = 'The quick brown fox';

OUTER: {
    my( @captures ) = $string =~ /\A(\w)/;

    INNER: {
        my $second_word;
        if( $string =~ /(\w{5})/ ) {
            $second_word = $1
            }
        }
    }

答案 1 :(得分:5)

有关更详细的说明,请参阅Brian's answer。但是我留下这个答案是因为问题上下文中的一些问题可能很难理解,除了Brian的答案之外,这个答案中的不同描述和评论可能会有所帮助,以便更好地理解问题。 / p>

阅读Wikipedia page for "scope"以了解各种范围,尤其是词汇和动态范围,也可能会有用。

map,grep,for / foreach等&#34; localize&#34; $_。这意味着它们将新变量绑定到$_,并且只有在离开词法范围时,原始变量才会绑定到$_。请参阅答案的最后,以获得更详细的描述&#34;本地化&#34;。例如:

for(qw(1 2)) {
    for(qw(a b)) {
        print map { uc($_) } ($_,'x');
        print $_
    }
    print $_
}

会给你AXaBXb1AXaBXb2,它显示每次使用for / map会将$_绑定到另一个变量,并在离开块后将其绑定回上一个变量。

对于以$_作为默认参数的函数:这​​些函数除了预期之外没有任何副作用(即替换s///)并且在perldoc中记录函数或操作将使用$_作为默认参数。

但是,您必须注意自己是否使用$_并希望确保它不会影响之前的含义。在这种情况下,自己本地化$_有助于防止意外更改之前的$_

sub myfunction {
    local $_;
    # from now on until the functions gets left changes to $_ 
    # will not affect the previous $_
    ...
}

使用块

也可以
{
    local $_;
    # from now on until the block gets left changes to $_
    # will not affect the previous $_
    ...
}

但请注意,经常使用的while (<>)不会本地化$_

$_ = 'foo';
say $_;
while (<>) {
    say $_;
}
say $_;

在这种情况下,循环之后的say $_将不会显示循环之前的值('foo'),而是循环中的最后一个隐式赋值(undef)。

究竟什么是本地化?大多数用于词法范围,可以用&#34; my&#34;在Perl。但是&#34;本地化&#34;变量是不同的,无论是使用显式local还是隐式内部,map ...

主要思想是通过本地化像$_这样的全局符号绑定到另一个变量,原始变量仅在词法范围结束后恢复。因此,与词汇范围相反,这种新的约束甚至影响从这个词法范围内调用的函数,即

sub foo { say $_}

$_ = 1;
foo(); # 1

{
    local $_;  # bind symbol $_ to new variable
    $_ = 2;
    foo();     # 2 - because $_ inside foo() is the same as in this block
}

foo(); # 1     # leaving the block restored original binding of $_

答案 2 :(得分:0)

除了学习所有内置函数(至少应该对你使用的函数做什么),我认为这是使用$_的最佳指南:

  
      
  • 只有使用$_时才使用$_比使用显式变量更清晰,更明显。
  •   

感谢$_ as&#34;它&#34;。 &#34;它&#34;是一个代名词。你不会说&#34;我去了它并买了它&#34;当你的意思是&#34;我去商店买了冰淇淋&#34;。代词只有在显而易见的时候才会被使用。

稍微改变原始代码会给我们一个有用的例子。注意在外部作用域中使用命名(显式)变量,并在尽可能小的范围内使用# Process student records one year at a time for my $student_records_aref (@student_records_by_year_array_refs) { # Print a report on each student for the current year print_report_on($_) for @{$student_records_aref}; } (其中它将整个内部循环缩减为单行):

private class MyClass{
    /**
    * Some code here
    **/

    private int numberOfPeople();
    private Human[] people;
    private void printPeople(){
        // some code here
    }

    /**
    * Some code here
    **/
}