我正在学习perl并且理解使用shift解包子例程参数是一种常见且公认的做法。我也理解,省略函数参数以使用默认的@_
数组是常见且可接受的做法。
考虑到这两件事,如果你调用一个没有参数的子程序,@_
可以(并且如果使用shift)可以改变。这是否意味着使用默认参数调用另一个子例程,或者事实上,在此之后使用@_
数组被认为是不好的做法?考虑这个例子:
sub total { # calculate sum of all arguments
my $running_sum;
# take arguments one by one and sum them together
while (@_) {
$running_sum += shift;
}
$running_sum;
}
sub avg { calculate the mean of given arguments
if (@_ == 0) { return }
my $sum = &total; # gets the correct answer, but changes @_
$sum / @_ # causes division by zero, since @_ is now empty
}
我的直觉告诉我,使用shift来解压缩参数实际上是不好的做法,除非你的子程序实际上应该改变传递的参数,但我已经在多个地方读过,包括Stack Overflow,这不是一个坏事实践。
所以问题是:如果使用shift是常见的做法,我是否总是假设传递的参数列表可能会被更改,作为子例程的副作用(如引用示例中的&total
子例程)?有没有办法按值传递参数,所以我可以确定参数列表不会被更改,所以我可以再次使用它(比如在引用文本中的&avg
子例程中)?
答案 0 :(得分:20)
通常,shift
来自参数是正确的 - 使用&
sigil来调用函数不是。 (除非在一些非常具体的情况下,你可能永远不会遇到。)
您的代码可以重写,因此total
不会shift
来自@_
。使用for循环甚至可能更有效。
sub total {
my $total = 0;
$total += $_ for @_;
$total;
}
或者您可以使用sum
中的List::Util
功能:
use List::Util qw(sum);
sub avg { @_ ? sum(@_) / @_ : 0 }
除了在面向对象的Perl中提取shift
之外,使用$self
并不常见。但是,当您始终调用foo( ... )
之类的函数时,如果foo
shift
或shift
无效 {1}}参数数组。
(关于函数唯一值得注意的是它是否分配给@_
中的元素,因为它们是您作为参数赋予的变量的别名。分配给@_
中的元素通常是不好的。)
即使您无法更改total
的实现,使用显式参数列表调用sub也是安全的,因为参数列表是数组的副本:
(a)&total
- 使用相同的total
调用@_
,并覆盖原型。
(b)total(@_)
- 使用total
的副本调用@_
(c)&total(@_)
- 使用total
的副本调用@_
,并覆盖原型。
表格(b)是标准的。不应该看到形式(c),除非在极少数情况下对于sub具有原型(并且不使用原型)的同一个包内的subs,并且由于某些不明原因它们必须被覆盖。证明设计不佳。
对于尾部调用(@_ = (...); goto &foo
)或其他形式的优化(以及过早优化是所有邪恶的根源),表单(a)仅 。
答案 1 :(得分:3)
除非你有充分的理由,否则你应该避免使用&func;
调用方式,并相信其他人会这样做。
要保护您的@_
不被被叫方修改,只需&func()
或func
。
答案 2 :(得分:2)
有时候Perl有点过于宽松,并且有多种方式访问输入参数可能会使代码变得异常和不一致。如果想要更好的答案,请尝试强加自己的标准。
以下是我使用过的几种方式
位移y
sub login
{
my $user = shift;
my $passphrase = shift;
# Validate authentication
return 0;
}
扩展@_
sub login
{
my ($user, $passphrase) = @_;
# Validate authentication
return 0;
}
显式索引
sub login
{
my user = $_[0];
my user = $_[1];
# Validate authentication
return 0;
}
使用函数原型强制执行参数(但这是not popular)
sub login($$)
{
my ($user, $passphrase) = @_;
# Validate authentication
return 0;
}
可悲的是,你仍然需要执行自己的复杂输入验证/污点检查,即:
return unless defined $user;
return unless defined $passphrase;
或者更好的是,提供更多信息
unless (defined($user) && defined($passphrase)) {
carp "Input error: user or passphrase not defined";
return -1;
}
Perldoc perlsub应该是您的第一个停靠点。
希望这有帮助!
答案 3 :(得分:2)
以下是谨慎使用@_
的一些示例。
<强> 1。哈希参数
有时你想编写一个可以获取键值对列表的函数,但有一个是最常用的,你希望它在不需要键的情况下可用。例如
sub get_temp {
my $location = @_ % 2 ? shift : undef;
my %options = @_;
$location ||= $options{location};
...
}
所以现在如果你用奇数个参数调用函数,第一个就是位置。这允许get_temp('Chicago')
或get_temp('New York', unit => 'C')
甚至get_temp( unit => 'K', location => 'Nome, Ak')
。这可能是您的用户更方便的API。通过移动奇数参数,现在@_
是偶数列表,可以分配给散列。
<强> 2。调度强>
假设我们有一个类,我们希望能够按名称调度方法(可能AUTOLOAD可能很有用,我们将手动滚动)。也许这是一个命令行脚本,其中参数是方法。在这种情况下,我们定义两个调度方法一个“干净”和一个“脏”。如果我们使用-c
标志调用,我们就会得到干净的标志。这些方法按名称查找方法并调用它。不同的是如何。脏的一个离开自己在堆栈跟踪中,干净的一个必须是更多的切割器,但调度没有在堆栈跟踪中。我们制作了death
方法,为我们提供了跟踪。
#!/usr/bin/env perl
use strict;
use warnings;
package Unusual;
use Carp;
sub new {
my $class = shift;
return bless { @_ }, $class;
}
sub dispatch_dirty {
my $self = shift;
my $name = shift;
my $method = $self->can($name) or confess "No method named $name";
$self->$method(@_);
}
sub dispatch_clean {
my $self = shift;
my $name = shift;
my $method = $self->can($name) or confess "No method named $name";
unshift @_, $self;
goto $method;
}
sub death {
my ($self, $message) = @_;
$message ||= 'died';
confess "$self->{name}: $message";
}
package main;
use Getopt::Long;
GetOptions
'clean' => \my $clean,
'name=s' => \(my $name = 'Robot');
my $obj = Unusual->new(name => $name);
if ($clean) {
$obj->dispatch_clean(@ARGV);
} else {
$obj->dispatch_dirty(@ARGV);
}
现在如果我们调用./test.pl
来调用死亡方法
$ ./test.pl death Goodbye
Robot: Goodbye at ./test.pl line 32
Unusual::death('Unusual=HASH(0xa0f7188)', 'Goodbye') called at ./test.pl line 19
Unusual::dispatch_dirty('Unusual=HASH(0xa0f7188)', 'death', 'Goodbye') called at ./test.pl line 46
但是我们在跟踪中看到了dispatch_dirty
。如果我们调用./test.pl -c
,我们现在使用干净的调度程序并获取
$ ./test.pl -c death Adios
Robot: Adios at ./test.pl line 33
Unusual::death('Unusual=HASH(0x9427188)', 'Adios') called at ./test.pl line 44
这里的关键是goto
(不是邪恶的goto),它使用子程序引用并立即使用当前@_
将执行切换到该引用。这就是为什么我必须unshift @_, $self
以便调用者为新方法做好准备。
答案 4 :(得分:0)
参考文献:
sub refWay{
my ($refToArray,$secondParam,$thirdParam) = @_;
#work here
}
refWay(\@array, 'a','b');
HashWay:
sub hashWay{
my $refToHash = shift; #(if pass ref to hash)
#and i know, that:
return undef unless exists $refToHash->{'user'};
return undef unless exists $refToHash->{'password'};
#or the same in loop:
for (qw(user password etc)){
return undef unless exists $refToHash->{$_};
}
}
hashWay({'user'=>YourName, 'password'=>YourPassword});
答案 5 :(得分:0)
我尝试了一个简单的例子:
#!/usr/bin/perl
use strict;
sub total {
my $sum = 0;
while(@_) {
$sum = $sum + shift;
}
return $sum;
}
sub total1 {
my ($a, $aa, $aaa) = @_;
return ($a + $aa + $aaa);
}
my $s;
$s = total(10, 20, 30);
print $s;
$s = total1(10, 20, 30);
print "\n$s";
这两个印刷语句的答案都是60。
但我个人认为,这些论点应该以这种方式接受:
my (arguments, @garb) = @_;
为了避免后者出现任何问题。
答案 6 :(得分:0)
我在http://perldoc.perl.org/perlsub.html中找到了以下宝石:
“是的,仍有未解决的问题与@_的可见性有关。我暂时忽略了这个问题。(但请注意,如果我们使@_词法作用域,那些匿名子程序就像闭包一样。 ..(哎呀,听起来有点Lispish?(没关系。)))“
您可能遇到过其中一个问题: - (
OTOH amon可能是对的 - &gt; 1