1

我正在尝试编写一个 perl 脚本,该脚本调用在其他地方(由其他人)编写的函数,该函数操纵我脚本范围内的一些变量。假设脚本是main.pl并且函数在funcs.pm. 我的main.pl样子是这样的:

use warnings;
use strict;

package plshelp;
use funcs;

my $var = 3;
print "$var\n";   # <--- prints 3

{                 # New scope somehow prevents visibility of $pointer outside
    local our $pointer = \$var;
    change();
}

print "$var\n";   # <--- Ideally should print whatever funcs.pm wanted

出于某种原因,使用local our $pointer;会阻止$pointer范围之外的可见性。但是,如果我只是使用our $pointer;,则可以在main.pl使用范围之外看到变量$plshelp::pointer(但在中看不到funcs.pm,所以无论如何它都没用)。作为旁注,有人可以解释一下吗?

funcs.pm看起来像这样:

use warnings;
use strict;

package plshelp;

sub change
{
    ${$pointer} = 4;
}

我希望这会在主脚本运行时更改$var并打印的值。4但是我得到一个编译错误,说$pointer没有声明。可以通过在inour $pointer;的顶部添加来删除此错误,但这会创建一个在任何地方都可见的不必要的全局变量。我们也可以通过删除 来删除这个错误,但这似乎是个坏主意。我们也可以通过使用in让它工作,但写作的人不想这样做。changefuncs.pmuse strict;$plshelp::pointerfuncs.pmfuncs.pm

有没有一种好方法可以funcs.pm在不声明全局变量的情况下实现在我的范围内操作变量的功能?如果我们无论如何都要使用全局变量,我想我根本不需要使用动态范围。

假设由于某种原因无法将参数传递给函数。

更新

local our就防止可见性而言,似乎没有做任何“特殊”。从perldoc

这意味着 whenuse strict 'vars'生效,our允许您使用包变量而不用包名限定它,但只能在 our 声明的词法范围内。这立即适用——即使在同一个语句中。

即使之前没有使用过包变量,这也可以工作,因为包变量在第一次使用时就会出现。

所以这意味着$pointer即使在我们离开花括号之后“存在”。只是我们必须使用$plshelp::pointer而不是 just来引用它$pointer。但是由于我们local在初始化之前使用过$pointer,它在范围之外仍然是未定义的(尽管它仍然是“声明的”,不管这意味着什么)。写这个的更清晰的方法是(local (our $pointer)) = \$var;. 在这里,our $pointer“声明”$pointer并返回$pointer。我们现在应用local这个返回值,并且这个操作$pointer再次返回我们分配给\$var.

但这仍然留下了一个主要问题,即是否有一个很好的方法来实现所需的功能。

4

2 回答 2

5

让我们弄清楚全局变量如何our工作以及为什么必须声明它们:全局变量的存储与其非限定名称的可见性之间存在差异。在 下use strict,未定义的变量名称不会隐式引用全局变量。

  • 我们始终可以使用其完全限定名称访问全局变量,例如$Foo::bar.

  • 如果当前包中的全局变量在编译时已经存在并被标记为导入变量,我们可以使用非限定名称访问它,例如$bar. 如果一个Foo包写得恰当,我们可以说现在我们的包中的全局变量在use Foo qw($bar); say $bar哪里。$bar

  • 使用our $foo,如果该变量不存在,我们会在当前包中创建一个全局变量。变量的名称也可以在当前的词法范围内使用,就像my声明的变量一样。

local操作员不创建变量。相反,它保存全局变量的当前值并清除该变量。在当前范围结束时,将恢复旧值。您可以将每个全局变量名称解释为一组值。您可以在local堆栈上添加(和删除)值。因此,虽然local可以动态限定一个值,但它不会创建一个动态限定的变量名。

通过仔细考虑何时编译哪些代码,您的示例当前不起作用的原因就很清楚了:

  • 在您的主脚本中,您加载模块funcs。该use语句在 BEGIN 阶段执行,即在解析期间。

    use warnings;
    use strict;
    
    package plshelp;
    use funcs;
    
  • funcs模块编译:

    use warnings;
    use strict;
    
    package plshelp;
    
    sub change
    {
        ${$pointer} = 4;
    }
    

    此时,$pointer词法范围内没有变量,也不$pointer存在导入的全局变量。因此你得到一个错误。$pointer这种编译时观察与运行时变量的存在无关。

修复此错误的规范方法是our $pointer在以下范围内声明一个变量名sub change

sub change {
    our $pointer;
    ${$pointer} = 4;
}

请注意,全局变量无论如何都会存在,这只是将名称带入范围以用作非限定变量名称。


仅仅因为您可以使用全局变量并不意味着您应该这样做。他们有两个问题:

  • 在设计层面,全局变量没有声明一个清晰的接口。通过使用完全限定名称,您可以简单地访问变量而无需任何检查。它们不提供任何封装。这导致了脆弱的软件和奇怪的远距离动作。

  • 在实现层面上,全局变量的效率不如词法变量。我从未真正见过这件事,但想想周期!

此外,全局变量是全局变量:它们一次只能有一个值!在某些情况下,将值local限定为有助于避免这种情况,但在复杂系统中仍然可能存在冲突,其中两个模块想要将相同的全局变量设置为不同的值,并且这些模块相互调用。

我见过的全局变量唯一好的用途是为不能接受额外参数的回调提供额外的上下文,与您的方法大致相似。但在可能的情况下,最好将上下文作为参数传递。子程序参数已经有效地动态作用域:

sub change {
  my ($pointer) = @_;
  ${$pointer} = 4;
}

...
my $var = 3;
change(\$var);

如果有很多上下文,传递所有这些引用可能会很麻烦:change(\$foo, \$bar, \$baz, \@something_else, \%even_more, ...). 然后,将上下文捆绑到一个对象中可能是有意义的,然后可以以更可控的方式对其进行操作。操作局部或全局变量并不总是最好的设计。

于 2018-07-07T11:09:32.400 回答
-1

您的代码有太多错误,无法修复它

您已经package plshelp在主脚本和模块中使用过,即使主入口点在main.pl并且您的模块在funcs.pm. 那只是不负责任。您是否认为该package声明仅用于寻求帮助的广告,而您在其中放了什么并不重要?

您的帖子没有说明您所写的内容有什么问题,但令人惊讶的是它没有引发错误。

这是一些接近的东西,可以满足您的期望。我无法真正解释事情,因为您自己的代码离工作还很远

函数.pm

package Functions;

use strict;
use warnings;

use Exporter 'import';

our @EXPORT_OK = 'change';

sub change {

    my ($ref) = @_;

    $$ref = 4;
}

主文件

use strict;
use warnings 'all';

use Functions 'change';

my $var = 44;

print "$var\n";
change(\$var);
print "$var\n";

输出

44
4
于 2018-07-06T15:14:02.400 回答