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

Gre*_*bet 13 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; 
}
Run Code Online (Sandbox Code Playgroud)

像您期望的那样打印1到10,但以下不是:

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

my $x;

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

for $x (1 .. 10) {
    print_func; 
}
Run Code Online (Sandbox Code Playgroud)

发出以下警告10次:

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

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

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.
Run Code Online (Sandbox Code Playgroud)

即它应该拒绝子程序,因为自由变量没有在任何地方声明和for循环因为变量尚未声明.

为什么Perl表现得这样?

Sch*_*ern 17

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

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

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

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

our $x;

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

for $x (1 .. 10) {
    print_func; 
}
Run Code Online (Sandbox Code Playgroud)

local $x这个循环有一个隐含的东西.local真的应该被命名temp.它在其范围的持续时间内暂时覆盖全局变量的值,但它仍然是全局变量.这就是为什么print_func可以看到它.

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

use v5.10;

our $x = 42;

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

say $x;  # 42
Run Code Online (Sandbox Code Playgroud)

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

my $x;

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

for $x (1 .. 10) {
    print_func; 
}
Run Code Online (Sandbox Code Playgroud)

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

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


这有一些风格的外卖.

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

实际上,print_func应该采取论点.那么您不必担心复杂的范围规则.

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

for $x (1..10) {
    print_func($x);
}
Run Code Online (Sandbox Code Playgroud)

一直用for my $x.

不要依赖复杂的隐式for循环范围规则.总是声明循环迭代器my.

for my $x (1..10) {
    print_func($x);
}
Run Code Online (Sandbox Code Playgroud)

避免全局变形.

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

my $Thing = 42;
sub get_thing { return $Thing }
sub set_thing { $Thing = shift; return }
Run Code Online (Sandbox Code Playgroud)

将变量声明为接近它们使用的位置.

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

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

  • @GregoryNisbet这不会起作用,因为`local`不适用于词法,我认为你已经在范围内使用了'my $ x`.否则你的类比基本上是正确的*如果范围内没有'$ x`,或者它是全局的*.如果在范围内已经存在词汇"$ x",那么`for $ x(...){...}`的行为会发生变化.在这种情况下,`for $ x`在循环中声明了一个新的词法,所以它更像是`do {my $ x; for $ x(...){...}};`.这就是#1398为什么你应该总是明确并为我的$ x编写`的原因. (2认同)