用户自定义的子程序

子程序用[sub](http://perldoc.perl.org/functions/sub.html)关键字来声明。相比内置函数,自定义子程序总是接受一种输入:一个scalar的列表。当然这个列表可以只包含一个元素,甚至为空。一个scalar会被转换成包含一个scalar的列表来处理,而一个有N个元素的hash会被转换成包含2N个元素的列表来处理。

尽管括号可以省略,我们还是应该总是在调用子程序的时候加上括号,即使不提供任何参数,读者就能更容易发现子程序的调用。

在子程序中,参数被保存在内置array变量@_中。例如:

  1. sub hyphenate {
  2. # 从array中取出第一个参数,忽略其他
  3. my $word = shift @_;
  4. # 聪明过头的list comprehension
  5. $word = join "-", map { substr $word, $_, 1 } (0 .. (length $word) - 1);
  6. return $word;
  7. }
  8. print hyphenate("exterminate"); # "e-x-t-e-r-m-i-n-a-t-e"

Perl以引用方式调用

不像其他主流编程语言,Perl以引用方式调用子程序(译者注:以引用方式传递参数)。这意味着子程序中用到的变量或值不是实参的副本,它们本身就是实参。

  1. my $x = 7;
  2. sub reassign {
  3. $_[0] = 42;
  4. }
  5. reassign($x);
  6. print $x; # "42"

如果你尝试这样做:

  1. reassign(8);

程序就会因为错误而终止运行,因为reassign()的第一行就相当于

  1. 8 = 42;

这显然是非常荒谬的。

这边可以学到的经验教训是,在子程序中你总是应该在使用参数之前将它们提取出来。

提取参数

我们有不止一种方法来提取@_中的参数,但总有一些方法比其他方法更好。

下面的示例子程序left_pad在字符串左边填充某个字符直到达到需要的长度。(x函数将同一个字符串的多个副本连接起来。)(注意:为了简化问题,这些子程序都缺乏必要的错误检查,比如确保填充字符串长度为1,检查要求的宽度是否大于等于字符串的长度,需要的参数是否都提供了。)

left_pad通常就像下面这样调用:

  1. print left_pad("hello", 10, "+"); # "+++++hello"
  1. 逐个抽取@_中的参数很有效,但也并不是那么地美观:

    1. sub left_pad {
    2. my $oldString = $_[0];
    3. my $width = $_[1];
    4. my $padChar = $_[2];
    5. my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    6. return $newString;
    7. }
  2. 对于不超过4个参数的情况推荐用shift通过移出元素的方法来提取@_中的参数:

    1. sub left_pad {
    2. my $oldString = shift @_;
    3. my $width = shift @_;
    4. my $padChar = shift @_;
    5. my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    6. return $newString;
    7. }

    如果没有给shift函数提供array参数,它就会默认对@_进行操作。这种用法很常见:

    1. sub left_pad {
    2. my $oldString = shift;
    3. my $width = shift;
    4. my $padChar = shift;
    5. my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    6. return $newString;
    7. }

    超过4个参数以后就很难搞清楚参数的哪部分被赋值给谁了。

  3. 你也可以一次性把所有@_中的参数提取出来。仍然是适用于少于4个参数的情形:

    1. sub left_pad {
    2. my ($oldString, $width, $padChar) = @_;
    3. my $newString = ($padChar x ($width - length $oldString)) . $oldString;
    4. return $newString;
    5. }
  4. 对于有大量参数的子程序,或者有些参数可选或无法和其他参数组合使用的子程序,最佳实践是要求用户构造参数的hash来调用这个子程序,然后将整个@_放回到一个hash中。用这种方法,我们子程序的调用会看起来会有点不一样:

    1. print left_pad("oldString" => "pod", "width" => 10, "padChar" => "+");

    而子程序自身就变成这样:

    1. sub left_pad {
    2. my %args = @_;
    3. my $newString = ($args{"padChar"} x ($args{"width"} - length $args{"oldString"})) . $args{"oldString"};
    4. return $newString;
    5. }

返回值

就像其他Perl表达式一样,子程序调用也会根据上下文表现出不同的行为。你可以用[wantarray](http://perldoc.perl.org/functions/wantarray.html)函数(也许我们应该叫它wantlist(译者注:上下文可以是scalar或者列表,不是一个array或者hash),不过不要在意这些细节)来检测子程序是在什么上下文中被调用的,这样就可以返回恰当类型的结果:

  1. sub contextualSubroutine {
  2. # 调用这里需要一个列表,那么就返回一个列表
  3. return ("Everest", "K2", "Etna") if wantarray;
  4. # 调用者需要一个scalar,那么就返回一个scalar
  5. return 3;
  6. }
  7. my @array = contextualSubroutine();
  8. print @array; # "EverestK2Etna"
  9. my $scalar = contextualSubroutine();
  10. print $scalar; # "3"