在perl中,使用默认参数调用多个子例程是一种不好的做法吗?

在perl中,使用默认参数调用多个子例程是一种不好的做法吗?,perl,subroutine,shift,Perl,Subroutine,Shift,我正在学习perl,并了解使用shift解包子程序参数是一种常见且公认的做法。我还理解,省略函数参数以使用默认的@数组是一种常见且可接受的做法 考虑到这两件事,如果您在没有参数的情况下调用一个子例程,@可以(并且如果使用shift)更改。这是否意味着使用默认参数调用另一个子例程,或者实际上在此之后使用@数组,被认为是不好的做法?考虑这个例子: sub total { # calculate sum of all arguments my $running_sum; # take

我正在学习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解压参数实际上是一种不好的做法,除非您的子例程实际上应该更改传递的参数,但我在多个地方读到过,包括堆栈溢出,这不是一种不好的做法


所以问题是:如果使用shift是常见的做法,我是否应该始终假定传递的参数列表可能会发生更改,这是子例程的副作用(如引用的示例中的
&total
子例程)?是否有一种方法可以按值传递参数,这样我就可以确保参数列表不会被更改,这样我就可以再次使用它(如引用文本中的
&avg
子例程)?

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;
}
使用函数原型强制执行参数(但这是一个错误)

遗憾的是,您仍然必须执行自己复杂的输入验证/污染检查,即:

return unless defined $user;
return unless defined $passphrase;
或者更好的是,提供更多的信息

unless (defined($user) && defined($passphrase)) {
    carp "Input error: user or passphrase not defined";
    return -1;
}
应该是你的第一个停靠港

希望这有帮助

参考文献:

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

一般来说,
shift
使用
&
符号调用函数是可以的,而不是。(除非在某些非常特殊的情况下,您可能永远不会遇到。)

您的代码可以被重新编写,这样
总计
就不会
@
移动
。使用for循环可能更有效

sub total {
  my $total = 0;
   $total += $_ for @_;
  $total;
}
或者您可以使用
List::Util
中的
sum
函数:

use List::Util qw(sum);

sub avg { @_ ? sum(@_) / @_ : 0 }
除了在面向对象的Perl中提取
$self
之外,使用
shift
并不常见。但是,由于您总是调用函数,如
foo(…)
,因此
foo
shift
s或不
shift
参数数组并不重要。
(关于函数,唯一值得注意的是它是否分配给
@
中的元素,因为这些是作为参数提供的变量的别名。分配给
@
中的元素通常是不好的。)

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

(a)
&total
-使用相同的
@
调用
total
,并覆盖原型。
(b)
total(@)
-调用
total
,并附上
@

(c)
&total(@)
-使用
@
的副本调用
total
,并覆盖原型

表格(b)为标准表格。表格(c)不应该被看到,除非在同一个包中的sub有一个原型(并且不使用原型),并且由于一些模糊的原因,它们必须被覆盖。拙劣设计的证明。
形式(a)只适用于尾部调用(
@=(…);goto&foo
)或其他形式的优化(过早优化是万恶之源)。

我尝试了一个简单的例子:

#!/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) = @_;

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

我在中发现了以下宝石:

“是的,还有一些与@的可见性有关的未解决问题。我暂时忽略了这个问题。(但请注意,如果我们将@的范围设为词汇范围,那么这些匿名子例程可以像闭包一样工作…(哎呀,这听起来有点含糊不清吗?(没关系。))”

您可能遇到以下问题之一:-(


奥托·阿蒙可能是对的->+1

这里有一些例子说明谨慎使用
@
很重要

1.散列y参数

有时,您希望编写一个函数,该函数可以获取键值对列表,但其中一个是最常用的,您希望该函数在不需要键的情况下可用

sub get_temp {
  my $location = @_ % 2 ? shift : undef;
  my %options = @_;
  $location ||= $options{location};
  ...
}
因此,如果您使用奇数个参数调用函数,第一个参数是location。这允许
get_temp('Chicago')
get_temp('New York',unit=>'C')
或偶数
get_temp(unit=>'K',location=>'Nome,Ak'))
。对于用户来说,这可能是一个更方便的API。通过移动奇数参数,现在
@
是一个偶数列表,可以分配给散列

2.调度

假设我们有一个类,我们希望能够按名称分派方法(可能自动加载可能很有用,我们将手动滚动)。也许这是一个命令行脚本,其中参数是方法。在本例中,我们定义了两个分派方法,一个是“clean”,另一个是“dirty”。如果我们使用
-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
来调用death方法

$ ./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\u dirty
$ ./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
$ ./test.pl -c death Adios
Robot: Adios at ./test.pl line 33
    Unusual::death('Unusual=HASH(0x9427188)', 'Adios') called at ./test.pl line 44