for循环不会修改`my`变量,但会修改`my`变量

时间:2016-05-03 00:39:22

标签: perl

在Perl 5.20中,for循环似乎能够修改模块范围的变量,但不能修改父范围中的词法变量。

#!/usr/bin/env perl
use strict;
use warnings;

our $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

按照您的预期打印1到10,但以下不会:

#!/usr/bin/env perl
use strict;
use warnings;

my $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

发出以下警告10次:

Use of uninitialized value $x in concatenation (.) or string at perl-scoping.pl line 8.

这里发生了什么?我知道perl子例程不能嵌套(并且总是具有模块范围),因此它们无法关闭my变量似乎是合乎逻辑的。在这种情况下,strict模式下的perl应该使用如下消息拒绝第二个程序:

Global symbol "$x" requires explicit package name at perl-scoping.pl line 6.
Global symbol "$x" requires explicit package name at perl-scoping.pl line 9.

即。它应该拒绝子程序,因为自由变量不是在任何地方声明的,而for循环因为变量没有被声明。

为什么Perl会这样表现?

1 个答案:

答案 0 :(得分:17)

令人困惑,但记录在案的行为可能源于将循环迭代器变量设为隐式本地化全局而不是词汇的错误决策。来自Foreach Loops in perlsyn

  

如果变量前面带有关键字my,那么它是词法范围的,因此只能在循环中可见。否则,该变量隐含在循环的本地,并在退出循环时重新获得其前一个值。如果先前使用my声明变量,则它使用该变量而不是全局变量,但它仍然本地化为循环。

换句话说, 循环迭代器始终本地化为循环 。如果它是全局的,那么它就像在循环块中声明local一样。如果它是一个词法,那么它就像在循环块中用my声明它一样。

将此应用于您的两个示例将有助于了解正在发生的事情。

our $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

该循环上有一个隐含的local $xlocal确实应该被命名为temp。它会在其范围的持续时间内暂时覆盖全局变量的值,但它仍然是全局。这就是print_func可以看到它的原因。

当范围结束时,旧值将恢复。如果您在for循环后添加print $x,则可以看到此内容。

use v5.10;

our $x = 42;

for $x (1 .. 10) {
    say $x;
}

say $x;  # 42

让我们看一下涉及词汇(my变量)的代码。

my $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

这里真正发生的是你有两个名为$x的词法变量。一个是文件作用域,一个作用于循环。 for循环中的内部$x优先于外部$x。这被称为“阴影”。

在物理范围之外不能看到词汇。 print_func()只能看到外部未初始化的$x

这有一些风格上的内容。

始终将参数传递到您的函数中。

实际上,print_func应该进行论证。然后,您不必担心复杂的范围规则。

sub print_func {
    my $arg = shift;
    print "$arg\n";
}

for $x (1..10) {
    print_func($x);
}

始终使用for my $x

不要依赖复杂的隐式for循环范围规则。始终使用my声明循环迭代器。

for my $x (1..10) {
    print_func($x);
}

避免全局变种。

由于很难说出访问全局的内容,因此不要使用它们。如果您认为自己需要全局,请编写一个函数来控制对文件范围词汇的访问。

my $Thing = 42;
sub get_thing { return $Thing }
sub set_thing { $Thing = shift; return }

将变量声明为接近使用位置。

Yeold编码样式将执行诸如在文件或函数顶部声明所有变量之类的事情。这是非常非常非常古老的语言,它要求变量只在某些地方声明。 Perl和大多数现代语言没有这样的限制。

如果你一次声明你的变量,很难知道它们的用途是什么,而且很难知道它正在使用或影响它。如果你声明它接近它的第一次使用,限制了可能影响它的东西,并使它更明显的用途。