17

我正在学习 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引用文本中的子例程一样)?

4

7 回答 7

20

一般来说,shift从参数中 ing 是可以的——使用&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( ... ),因此参数数组是否为s 并不重要。 (关于一个函数,唯一值得注意的是它是否分配给 in 的元素,因为这些是您作为参数提供的变量的别名。分配给 in 的元素通常是不好的。)foo shiftshift
@_@_

即使您不能更改 的实现total,使用显式参数列表调用 sub 也是安全的,因为参数列表是数组的副本:

(a) &total— 调用total相同的@_, 并覆盖原型。
(b) —total(@_)调用total. (c) — 调用, 的副本并覆盖原型。@_
&total(@_)total@_

表格 (b) 是标准的。表格 (c) 不应该被看到,除非在极少数情况下,同一包内的 subs 具有原型(并且不使用原型),并且由于某些模糊的原因必须覆盖它们。糟糕设计的证明。形式 (a)适用于尾调用 ( @_ = (...); goto &foo) 或其他形式的优化(过早优化是万恶之源)。

于 2012-12-31T13:01:03.627 回答
3

除非您有充分的理由,否则您应该避免使用&func;打电话的方式,并相信其他人也会这样做。

为了防止您@_被被调用者修改,只需执行&func()or func

于 2012-12-31T18:26:04.727 回答
2

Perl 有时有点过于松懈,并且有多种访问输入参数的方法可能会使代码变得很臭且不一致。如果想要更好的答案,请尝试强加自己的标准。

这是我使用和看到的一些方法

狡猾的

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;
}

使用函数原型强制执行参数(但这并不流行

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确实应该是您的第一个停靠点。

希望这可以帮助!

于 2012-12-31T11:13:45.810 回答
2

以下是一些谨慎使用@_事项的示例。

1. Hash-y 参数

有时您想编写一个可以获取键值对列表的函数,但其​​中一个是最常见的用途,您希望它无需键即可使用。例如

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。通过移动奇数参数,now@_是一个偶数列表,并且可以分配给一个散列。

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让调用者为新方法做好准备。

于 2012-12-31T15:59:45.210 回答
0

参考:

sub refWay{
    my ($refToArray,$secondParam,$thirdParam) = @_;
    #work here
}

refWay(\@array, 'a','b');

哈希方式:

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});
于 2012-12-31T12:20:01.390 回答
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) = @_;

为了避免以后出现任何问题。

于 2012-12-31T13:02:56.230 回答
0

我在http://perldoc.perl.org/perlsub.html找到了以下 gem :

“是的,仍然存在与 @_ 的可见性有关的未解决问题。我暂时忽略了这个问题。(但请注意,如果我们使 @_ 词法作用域,那些匿名子例程可以像闭包一样......(哎呀,这听起来有点口语吗?(没关系。))”

您可能遇到了这些问题之一:-(

OTOH amon 可能是对的 -> +1

于 2012-12-31T13:17:57.630 回答