用户自定义的子程序
子程序用[sub](http://perldoc.perl.org/functions/sub.html)
关键字来声明。相比内置函数,自定义子程序总是接受一种输入:一个scalar的列表。当然这个列表可以只包含一个元素,甚至为空。一个scalar会被转换成包含一个scalar的列表来处理,而一个有N个元素的hash会被转换成包含2N个元素的列表来处理。
尽管括号可以省略,我们还是应该总是在调用子程序的时候加上括号,即使不提供任何参数,读者就能更容易发现子程序的调用。
在子程序中,参数被保存在内置array变量@_
中。例如:
sub hyphenate {
# 从array中取出第一个参数,忽略其他
my $word = shift @_;
# 聪明过头的list comprehension
$word = join "-", map { substr $word, $_, 1 } (0 .. (length $word) - 1);
return $word;
}
print hyphenate("exterminate"); # "e-x-t-e-r-m-i-n-a-t-e"
Perl以引用方式调用
不像其他主流编程语言,Perl以引用方式调用子程序(译者注:以引用方式传递参数)。这意味着子程序中用到的变量或值不是实参的副本,它们本身就是实参。
my $x = 7;
sub reassign {
$_[0] = 42;
}
reassign($x);
print $x; # "42"
如果你尝试这样做:
reassign(8);
程序就会因为错误而终止运行,因为reassign()
的第一行就相当于
8 = 42;
这显然是非常荒谬的。
这边可以学到的经验教训是,在子程序中你总是应该在使用参数之前将它们提取出来。
提取参数
我们有不止一种方法来提取@_
中的参数,但总有一些方法比其他方法更好。
下面的示例子程序left_pad
在字符串左边填充某个字符直到达到需要的长度。(x
函数将同一个字符串的多个副本连接起来。)(注意:为了简化问题,这些子程序都缺乏必要的错误检查,比如确保填充字符串长度为1,检查要求的宽度是否大于等于字符串的长度,需要的参数是否都提供了。)
left_pad
通常就像下面这样调用:
print left_pad("hello", 10, "+"); # "+++++hello"
逐个抽取
@_
中的参数很有效,但也并不是那么地美观:sub left_pad {
my $oldString = $_[0];
my $width = $_[1];
my $padChar = $_[2];
my $newString = ($padChar x ($width - length $oldString)) . $oldString;
return $newString;
}
对于不超过4个参数的情况推荐用
shift
通过移出元素的方法来提取@_
中的参数:sub left_pad {
my $oldString = shift @_;
my $width = shift @_;
my $padChar = shift @_;
my $newString = ($padChar x ($width - length $oldString)) . $oldString;
return $newString;
}
如果没有给
shift
函数提供array参数,它就会默认对@_
进行操作。这种用法很常见:sub left_pad {
my $oldString = shift;
my $width = shift;
my $padChar = shift;
my $newString = ($padChar x ($width - length $oldString)) . $oldString;
return $newString;
}
超过4个参数以后就很难搞清楚参数的哪部分被赋值给谁了。
你也可以一次性把所有
@_
中的参数提取出来。仍然是适用于少于4个参数的情形:sub left_pad {
my ($oldString, $width, $padChar) = @_;
my $newString = ($padChar x ($width - length $oldString)) . $oldString;
return $newString;
}
对于有大量参数的子程序,或者有些参数可选或无法和其他参数组合使用的子程序,最佳实践是要求用户构造参数的hash来调用这个子程序,然后将整个
@_
放回到一个hash中。用这种方法,我们子程序的调用会看起来会有点不一样:print left_pad("oldString" => "pod", "width" => 10, "padChar" => "+");
而子程序自身就变成这样:
sub left_pad {
my %args = @_;
my $newString = ($args{"padChar"} x ($args{"width"} - length $args{"oldString"})) . $args{"oldString"};
return $newString;
}
返回值
就像其他Perl表达式一样,子程序调用也会根据上下文表现出不同的行为。你可以用[wantarray](http://perldoc.perl.org/functions/wantarray.html)
函数(也许我们应该叫它wantlist
(译者注:上下文可以是scalar或者列表,不是一个array或者hash),不过不要在意这些细节)来检测子程序是在什么上下文中被调用的,这样就可以返回恰当类型的结果:
sub contextualSubroutine {
# 调用这里需要一个列表,那么就返回一个列表
return ("Everest", "K2", "Etna") if wantarray;
# 调用者需要一个scalar,那么就返回一个scalar
return 3;
}
my @array = contextualSubroutine();
print @array; # "EverestK2Etna"
my $scalar = contextualSubroutine();
print $scalar; # "3"