子程序的参数

Perl子程序不需要定义形参,在调用子程序时,传递参数的地方是一个列表上下文,传递的所有实参会依次放入名为@_的数组中。因此,通过$_[0]可获取第一个参数的值,通过$_[1]可获取第二个参数的值,依次类推。

sub area{
  my $length = $_[0];
  my $width = $_[1];
  return $length * $width;
}

# 传递两个参数,子程序内部:`@_ = (2, 5)`
say area(2, 5);

Perl的默认变量总结:

1.默认标量变量$_

2.在子程序内部,默认列表变量是@_

3.在子程序外部,默认列表变量是@ARGV

在子程序内部,列表操作的默认对象是@_,因此,在子程序内部,下面两个操作是等价的:

shift @_;
shift;

它们都表示获取第一个参数,且从参数列表@_中移除第一个参数。

在定义子程序时,经常会使用shift来获取所传递的参数并修改参数列表:

sub subname{
  my $arg1 = shift;
  my $arg2 = shift;
}

处理多个参数

调用子程序时,可直接传递多个参数:

subname('a', 'b', 'c', 'd');

上面传递的四个参数,均存储在@_中,可分别通过$_[0]$_[1]$_[2]$_[3]获取。

有时候可能会期望将前几个参数赋值给指定变量,剩余参数放进一个数组。这种需求可以这样实现:

my ($a, @arr) = @_;

当然,也可以将传递的参数组合成hash:

sub subname {
  my %hash = @_;
  say keys %hash;
  say values %hash;
}

subname(
  "name", "junmajinlong",
  "age", 23,
);
# 或者
subname(
  name=>"junmajinlong",
  age=>23,
);

上面第二种调用subname子程序时传递参数的方式也是传递命名参数的方式。但是,这种方式处理命名参数比较容易出错,这要求传递的参数必须能够转换成hash数据,如果传递的参数不合理,将会给出警告或报错。

例如,只传递单个参数,将无法合理地构建成hash,于是会给出警告信息:

subname("junmajinlong");

解决这种问题的方案是传递一个hash引用。参考下文。

注意,重新构建的数组变量、hash变量必须放在最后面。例如,下面处理参数的方式是不合理的:

my (@arr, $a, $b) = @_;

放在最前面的@arr会吞掉@_的所有元素,使得$a$b都是空变量。

传递数组和hash

实际上,调用子程序的参数部分是一个列表上下文,传递的所有参数都会转换为列表数据然后放入@_中。

因此,直接将数组变量或hash变量作为参数传递给子程序,将得不到期待的结果,数组变量或hash数据都会被压平(flatten)。

my @arr = qw(a b c);
my %h = (name=>"junma", age=>23,);
subname(@arr);  # 等价于subname('a', 'b', 'c');
subname(%h);  # 等价于subname('name', 'junma', 'age', 23);

某些情况下,可以将它们压平后的元素重新构建成数组或hash:

sub subname{
  my @arr = @_; # 将所有参数重新构建成数组
  my %h = @_;  # 将所有参数重新构建成Hash
  ...
}

但这种重新构建数组或hash的方式不能适用于所有场景,有些情况下也不方便,比如传递的参数数量是奇数时不能合理地构造hash,比如要传递非常多参数时,传递所有参数数据不如传递一个包含它们的引用更高效。

如果确实需要传递数组或hash,建议的方式是传递它们的引用。

sub subname{
  my $arr_ref = shift;
  say $$arr_ref[0];
}

my @arr = qw(a b c);
subname(\@arr);

当然,也可以传递匿名数组、匿名hash,它们本质上仍然是引用。

sub subname{
  my $arr_ref = shift;
  my $hash_ref = shift;
  my $scalar = shift;
  
  say $$arr_ref[0];
  say $$hash_ref{name};
  say $scalar;
}

subname([qw(a b c)], {name=>'junma', age=>23}, 3333);

参数是别名引用

调用子程序时传递的参数,放入@_中的都是原始数据的别名引用:修改@_各元素的值,也将影响原始数据。

例如:

sub subname{
  $_[0]++;
  say $_[0];
}

my $a=33;
subname($a);
say $a;

这将输出:

34
34

也就是说,@_中的数据和原始数据是完全相同的,它们是别名关系。

这种别名关系也存在于容器的元素中。

sub subname{
  $_[0]++;
  say $_[0];
}

my @arr = (11,22,33);
subname(@arr);
say $arr[0];

实际上,subname(@arr)会将@arr的各元素压平后传入子程序内的@_,但是压平后的是各元素的别名,而不是各元素的值。因此上面的示例输出:

12
12

如果想要让子程序内部的修改不影响原始数据,则应该将参数数据保存到变量中。

sub subname{
  my $a = shift;
  $a++;
  say $a;
}

my $x=33;
subname $x;  # 34
say $x;      # 33

数组也类似:

sub subname{
  my @arr = @_;
  $arr[0]++;
  say $arr[0];
}

my @arr=(11,22,33);
subname @arr;  # 12
say $arr[0];   # 11