第四章 子程序
Perl可以让你创建子程序(subroutine) ,也就是用户自定义的函数。它让我们可以重复利用已有的代码。子程序的名称也属于Perl标识符的范畴(即由字母、数字和下划线组成,但不能以数字开头),有时候视情况会以“&” 开头。若无其他声明,我们都将使用该符号,这通常都是比较保险的做法。当然,也有一定不能用&的情形。
定义子程序
要定义你自己的子程序,可使用关键字sub、子程序名(不包含与号)以及用花括号封闭起来的代码块,这部分代码就是子程序的主体。例如:
sub marine {
$n t= 1;#全局变量$n
print "Hel1o, sailor number $n|\n";
]
子程序可以被定义在程序中的任意位置,你不需要对子程序进行事先声明,子程序
的定义是全局的。假如你定义了两个重名的子程序,那么后面的那个子程序会覆盖掉前面的那个。
调用子程序
你可以在任意表达式中使用子程序名(前面加上与号)来调用它
&marine; # 打印Hello, sailor number 1!
&marine; #打印He11o, sailor number 2!
&marine;#打印Helo, sailor number 3!
&marine; # 打印Hello, sailor number 4!
通常,我们把对子程序的调用称为呼叫(calling) 子程序。
返回值
子程序被调用时一定是作为表达式的某个部分,即使该表达式的求值结果不会被用到。.之前我们在调用&marine时,先对包含调用动作的表达式求值,但随即就把结果丟弃了。
很多时候,我们需要调用某个子程序并对它的返回值作进一步的处理。所以我们需要注意子程序的返回值。在Perl中,所有的子程序都有一个返回值,子程序并没有“有返回值”或“没有返回值”之分。但并不是所有的Perl子程序都包含有用的返回值。既然任何Perl子程序都有返回值,那么规定每次必须写“return" 某值就显得非常费事。在子程序的执行过程中,它会不断进行运算,而最后一次运算的结果(不管是什么)都会被自动当成子程序的返回值。
比如我们定义下面这个子程序,最后一个是加法表达式:
sub sum_of_fred_and_barney
print "Hey, you called the sum_of_fred_and_barney subroutine! \n";
$fred + $barney; # 这就是返回值
}
这个子程序里最后执行的表达式就是计算 f r e d 与 fred与 fred与barney的总和。因此, f r e d 与 fred与 fred与barney的总和就是返回值。以下是实际运行的情况:
$fred=3;
$barney=4;
$wilma = &sum_of_fred_and_barney;# $wilma 为7
print "\$wilma is $wilma. \n";
$betty = 3 * &sum_of_fred_and_barney; # $betty为21
print "\$betty is $Sbetty. \n";
这段代码会输出以下内容:
Hey, you called the sum_of _fred_and_barney subroutine!
$wilma is 7.
Hey, you called the sum_of fred_ and_ barney subroutine!
$betty is 21.
此处的print语句只用于协助调试,让我们得以确定该子程序被调用到了,程序完工后便可将它删除。不过,假设你在这段程序代码的结尾新增一条print语句, 像这样:
sub sum_of_fred_and_barney {
print "Hey, you called the sum_of_fred_and_barney subroutine!\n" ;
$fred + $barney; # 这不是返回值!
print "Hey, I'm returning a value now! \n" ;#糟糕!
}
最后执行的表达式并非加法运算,而是print语句。它的返回值通常是1,表示“成功输出信息”,但它不是我们真正想要返回的值。所以在子程序里增加额外的程序代码时,请小心检查最后执行的表达式是哪一个,确定它是你要的返回值。
那么,那个第二个(不完善的)子程序中 f r e d 与 fred与 fred与barney相加的结果我们并没有将总和存储起来,所以Perl会丢弃它。
“最后执行的表达式”的准确含义真的就是最后执行的表达式,而非程序代码的最后一行。比如下面这个子程序,它会返回 f r e d 和 fred和 fred和barney两者中值较大者:
sub larger_of_fred_or_barney {
if ($fred > $barney) {
$fred;
} else {
$barney;
}
```i
最后执行的表达式是自成一行的$fred或$barney, 所以它们将充当子程序的返回值。必须等到执行阶段得知这些变量的内容后,我们才会知道返回值到底是$fred还是$barney。
## 参数
Perl子程序可以有参数(argument) 。要传递参数列表到子程序里,只要在子程序调用的后面加上被括号圈引的列表表达式就行了。例如:
```perl
$n = &max(10, 15); # 包含两个参数的子程序调用
参数列表将会被传入子程序,让子程序随意使用。当然,得先将这个列表存在某处,Perl会自动将参数列表化名为特殊的数组变量@,该变量在子程序执行期间有效。子程序可以访问这个数组,以判断参数的个数以及参数的值。
这表示子程序的第一个参数存储于 [ 0 ] , 第 二 个 参 数 存 储 于 _[0], 第二个参数存储于 [0],第二个参数存储于[1], 依此类推。但是,请特别注意,这些变量和 变 量 毫 无 关 联 , 就 像 _变量毫无关联,就像 变量毫无关联,就像dino[3] (数组@dino中的元素之一)与KaTeX parse error: Expected 'EOF', got '&' at position 81: …已。 现在,你可以写一个类似于&̲larger_of_fred_…_ [0])而不用 f r e d , 也 可 以 使 用 子 程 序 的 第 二 个 参 数 ( fred,也可以使用子程序的第二个参数( fred,也可以使用子程序的第二个参数(_ [1])而不用$barney。因此,最后你可以写成这样:
sub max {
#请比较它和子程序&larger_of_fred_or_barney的差异
if ($_[0] > $_[1]) {
$_[0];
} else {
$_[1];
}
这里的一堆下标让程序变得不雅观,而且难以阅读、编写、检查和调试。不用担心,我们马上就会看到更好的办法。这个子程序还存在另一个问题。&max这个名字虽然既好听又简洁,但却没有说明这个子程序只接受两个参数:
$n = &max(10, 15, 27); #糟糕!
多余的参数会被忽略,反正子程序也不会用到$_[2],所以Perl并不在乎里面是否有值。参数如果不足也会被忽略,如果用到超出@_数组边界的参数,只会得到undef。
实际上,@变量是子程序的私有变量
假如已经有了全局变量@,则该变量在子程序调用前会先被存起来,并在子程序返回时恢复原本的值。这也表示子程序可以将参数传给其他程序,而不用担心遗失自己的@_变量。就算是嵌套的子程序(nested subroutine)调用它自己的@_变量时也一样。即使子程序递归调用自己,每次调用的仍然是一个新的@。所以在当前的子程序调用中,@ _总是包含了它的参数列表。
子程序中的私有变量
既然每次调用子程序时Per1都会给我们新的@_,难道不能利用它构造私有变量吗?答案当然是可以。
默认情况下,Perl里面所有的变量都是全局变量,也就是说,在程序里的任何地方都可以访问它们。但你随时可以借助my操作符来创建私有变量,我们称之为词法变量(lexical variable) :
sub max {
my($m,$n);#该语句块中的新私有变量
($m, $n)=@_;#将参数赋值给变量
if($m>$n){
$m}else{$n}
}
这些变量属于封闭语句块的私有变量,语句块之外任意地方的 m 或 m或 m或n都完全不受这两个私有变量的影响。反过来也是,外部变量同样无法影响内部的私有变量。所以,我们
可以把这个子程序放进世界上任何一个Perl程序里,不用担心它和哪个程序中(可能存
在)的 m 和 m和 m和n变量冲突。另外值得一提的是,在前一个例子中的if语句块中,作为返回值的表达式后面没有分号。虽然Perl允许你省略语句块中最后一个分号。但实际上通常只有像前面的例子那样,代码简单到整个语句块内只有一行时,才可以省略分号。
前一个例子中的子程序还可以进一步简化。你是否注意到列表($m, $n)出现了两次?
其实my操作符也可以应用到括号内的变量列表,所以习惯上会将这个子程序中的前两行语句合并起来:
my($m, $n) = @_; #对子程序的参数命名
这一行语句会创建私有变量并为它们赋值,让第一个参数的名称变成较好记的 m , 而 第 二 个 参 数 的 则 为 m,而第二个参数的则为 m,而第二个参数的则为n。几乎所有的子程序都会以类似的程序代码作为开头。当你看到这一行时就会知道,这个子程序具有两个标量参数,而在子程序内部,它们分