我正在尝试编写一个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
。可以通过在our $pointer;
的{{1}}的顶部添加change
来消除此错误,但这将创建一个不必要的全局变量,该变量在任何地方都可见。我们也可以通过删除funcs.pm
来消除此错误,但这似乎是一个坏主意。我们也可以通过在use strict;
中使用$plshelp::pointer
使它工作,但是写funcs.pm
的人不想这样做。
是否有一种好的方法来实现让funcs.pm
在我的范围内操作变量而不声明全局变量的功能?如果我们还是要使用全局变量,我想我根本不需要使用动态作用域。
我们只是说由于某种原因不可能将参数传递给函数。
更新
就防止可见性而言,funcs.pm
似乎没有做任何“特殊”操作。来自perldoc:
这意味着
local our
生效时,use strict 'vars'
允许您使用包变量而不用包名来限定它,而只能在我们声明的词法范围内使用。这立即适用-即使在同一条语句内。
和
即使以前没有使用过package变量,它也可以工作,因为package变量在首次使用时就已经存在。
因此,这意味着our
“存在”,即使我们离开了花括号也是如此。只是我们必须使用$pointer
而不是$plshelp::pointer
来引用它。但是,由于我们在初始化$pointer
之前使用了local
,因此在范围之外仍未定义它(尽管无论如何它都被“声明了”)。一种更清晰的写方法是$pointer
。在这里,(local (our $pointer)) = \$var;
“声明” our $pointer
并返回$pointer
。现在,我们将$pointer
应用于此返回值,并且此操作再次返回local
,我们将其分配给$pointer
。
但这仍然是一个主要问题,即是否存在一种很好的方法来实现所需的功能而未得到答复。
答案 0 :(得分: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;
}
请注意,全局变量无论如何都会存在,这只会使名称成为范围,用作不合格的变量名称。
仅因为可以使用全局变量并不意味着您应该这样做。他们有两个问题:
在设计级别,全局变量未声明明确的接口。通过使用完全限定的名称,您可以简单地访问变量而无需进行任何检查。它们不提供任何封装。这造成了软件的脆弱和远距离的奇怪动作。
在实现级别,全局变量的效率仅比词法变量低。我从来没有真正看过这个问题,但是想想周期!
此外,全局变量是 global 变量:它们一次只能有一个值!在某些情况下,用local
限制值的范围可以帮助避免这种情况,但是在复杂的系统中仍然存在冲突,在复杂的系统中,两个模块希望将同一全局变量设置为不同的值,并且这些模块相互调用。 >
我看到的全局变量的唯一好用法是为回调提供额外的上下文,该回调不能接受额外的参数,大致类似于您的方法。但是在可能的情况下,最好始终将上下文作为参数传递。子例程参数已经有效地动态范围化:
sub change {
my ($pointer) = @_;
${$pointer} = 4;
}
...
my $var = 3;
change(\$var);
如果存在大量上下文,则传递所有这些引用可能很麻烦:change(\$foo, \$bar, \$baz, \@something_else, \%even_more, ...)
。然后,将上下文绑定到对象中就可以了,然后可以以更可控的方式对其进行操作。操纵局部或全局变量并不总是最好的设计。
答案 1 :(得分:-1)
您的代码有太多错误,无法对其进行修复
即使主入口点位于package plshelp
中,而模块位于main.pl
中,您也已在主脚本和模块中使用了funcs.pm
。那是不负责任的。您是否认为package
语句仅用于广告寻求帮助,而不管您在此处输入什么内容?
您的帖子没有说您写的内容有什么问题,但是令人惊讶的是它没有引发错误。
这很接近您期望的结果。我真的无法解释问题,因为您自己的代码尚无法正常工作
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