第四章 子程序
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。几乎所有的子程序都会以类似的程序代码作为开头。当你看到这一行时就会知道,这个子程序具有两个标量参数,而在子程序内部,它们分别称为 m 和 m和 m和n。
变长参数列表
把更长的(任意长度的)列表作为参数传给子程序。
通过检查@数组的长度其实也很容易确定参数个数是否正确。比方说,我们可以将&max写成下面这样以检查其参数列表
sub max {
if(@_!=2){
print "WARNING! &max should get exactly two arguments!\n" ;
#其余代码和前面一样......
}
上面的if判断是在标量上下文中直接使用数组“名称”来取得数组元素的个数。但在实际编写的PerI程序中这种检查方式很少见,更好的做法是让子程序自动适应任意数目的参数。
改进的&max子程序
$smaxirum=amnax(3, 5, 10, 4; 6);
sub max {
my($max_so_far) = shift @_ ; # 数组中的第一个值, 暂时把它当成最大值
foreach (@_) { #遍历数组@_中的其他元素
if ($_> smax_so_far) { #当前元素比$max. so_ fa更大吗?
$max_so_far . $_ ;
}
$max_ so_ far;
}
上面的程序代码使用了一般称为“ 高水线(high-watermark) ”的算法:大水过后,在最后一波浪消退时,高水线会标示出所见过的最高水位。本例中,
m
a
x
s
o
f
a
r
记
录
了
高
水
线
,
所
以
最
后
max_so_far记录了高水线,所以最后
maxsofar记录了高水线,所以最后max_so_far变量中的值就是我们要找的最大值。
第一行程序代码会对参数数组@进行shift操作并将所得到的3 (范例程序里的第一个参数)存入
m
a
x
s
o
f
a
r
变
量
。
所
以
@
现
在
的
内
容
为
(
5
,
10
,
4
,
6
)
,
因
为
3
已
被
移
走
。
现
在
最
大
的
数
字
是
目
前
唯
一
见
过
的
值
:
3
,
也
就
是
第
一
个
参
数
。
然
后
f
o
r
e
a
c
h
循
环
会
遍
历
参
数
列
表
@
里
剩
余
的
元
素
。
循
环
的
控
制
变
量
默
认
为
max_so_far变量。所以@现在的内容为(5,10,4,6),因为3已被移走。现在最大的数字是目前唯一见过的值: 3, 也就是第一个参数。然后foreach循环会遍历参数列表@_里剩余的元素。循环的控制变量默认为
maxsofar变量。所以@现在的内容为(5,10,4,6),因为3已被移走。现在最大的数字是目前唯一见过的值:3,也就是第一个参数。然后foreach循环会遍历参数列表@里剩余的元素。循环的控制变量默认为。循环第一次执行时,
是
5
,
而
i
f
进
行
比
较
时
看
到
_是5,而if进行比较时看到
是5,而if进行比较时看到_比
m
a
x
s
o
f
a
r
还
大
,
所
以
max_so_far还大,所以
maxsofar还大,所以max_so_far会被设成5(新的高水线)。第三次循环时,
是
10
。
这
是
新
的
最
大
值
,
所
以
它
会
被
存
入
_ 是10。这是新的最大值,所以它会被存入
是10。这是新的最大值,所以它会被存入max_so_far。到第四次时,
是
4
。
这
时
i
f
比
较
的
结
果
为
假
,
因
为
_ 是4。这时if比较的结果为假,因为
是4。这时if比较的结果为假,因为_不比
m
a
x
s
o
f
a
r
(
即
10
)
大
,
所
以
会
跳
过
i
f
里
的
程
序
代
码
。
最
后
,
max_ so_ far (即10)大,所 以会跳过if里的程序代码。最后,
maxsofar(即10)大,所以会跳过if里的程序代码。最后,_是6,因此if里的程序代码又被跳过一次。这是最后一次执行循环, 所以整段循环就执行完了。
此时,$max_so_far就变成了我们的返回值。既然它是目前见过的最大值,并且我们已经
遍历过所有数字,那它一定是列表中最大的值:10
空参数列表
没有任何參数传入,又会发生什么事情呢?比如
$maximum = &max(@numbers);
数组@numbers有时可能只是一个空列表。也许数组内容是程序从文件读入的,但文件却是空的。
子程序的第一行会对参数数组@_ (现在是空的) 进行shift操作,以此作为
m
a
x
s
o
f
a
r
的
值
。
这
并
不
会
出
错
,
因
为
数
组
是
空
的
,
所
以
s
h
i
f
t
会
返
回
u
n
d
e
f
给
max_so_far的值。这并不会出错,因为数组是空的,所以shift会返回undef给
maxsofar的值。这并不会出错,因为数组是空的,所以shift会返回undef给max_so_far,现在foreach循环要遍历@_数组,但由于它是空的,所以循环本身不会执行。
接下来会将$max_so_far的值undef作为子程序的返回值。从某种角度来看,那是
正确的结果,因为在空列表中没有最大值。
关于语法(my)变量
词法变量可使用在任何语句块内,而不仅限于子程序的语句块。比如说,它可以在if、while或 foreach的语句块里使用:
foreach (1..10) {
my($square) = $_ * $_; #该循环中的私有变量
print "$_ squared is $square.\n";
}
上面的例子中,变量
s
q
u
a
r
e
对
其
所
属
语
句
块
(
也
就
是
f
o
r
e
a
c
h
循
环
的
语
句
块
)
来
说
是
私
有
的
。
如
果
变
量
的
定
义
并
未
出
现
在
任
何
语
句
块
里
,
则
该
变
量
对
于
整
个
程
序
源
文
件
来
说
就
是
私
有
的
。
词
法
变
量
的
作
用
域
(
s
c
o
p
e
)
受
限
于
定
义
它
的
最
内
层
语
句
块
(
或
文
件
)
。
只
有
语
句
块
上
下
文
作
用
域
内
的
程
序
代
码
才
能
以
square对其所属语句块(也就是foreach循环的语句块)来说是私有的。如果变量的定义并未出现在任何语句块里,则该变量对于整个程序源文件来说就是私有的。词法变量的作用域(scope)受限于定义它的最内层语句块(或文件)。只有语句块上下文作用域内的程序代码才能以
square对其所属语句块(也就是foreach循环的语句块)来说是私有的。如果变量的定义并未出现在任何语句块里,则该变量对于整个程序源文件来说就是私有的。词法变量的作用域(scope)受限于定义它的最内层语句块(或文件)。只有语句块上下文作用域内的程序代码才能以square这个名称使用该变量。这为程序维护提供了便利。如果$square的值出错了,那么就可以在有限的源代码范围内找到原因。
还需要注意的是,my操作符并不会更改变量赋值时的上下文:
my($num) = @_; #列表上下文,和($num) = @_;相同
my $num = @_; #标量上下文,和$num = @_;相同
在第一行里,按照列表上下文,$num会被设为第一个参数;在第二行里,按照标量上下文,它会被设为参数的个数。看到这样的程序代码时,你可以忽略my这个词,直接判断变量
赋值时的上下文。
请记住,在my操作符不加括号时,只能用来声明单个词法变量:
my $fred, $barney; #错!没声明$barney
my($fred, $barney); #两个都声明了
#当然,你也可以使用my来创建新的私有数组
my @phone_number;
所有新变量的值一开始都是空的:标量被设为undef,数组被设为空列表。
你最好对每个新变量都使用my声明,让它保持在自己所在的词法作用域内。在第三章中,你已经看到过如何在foreach循环中定义自己的控制变量,而这个控制变量也可以声明为词法变量:
foreach my $rock (qw/bedrock slate lava /) {
print“One rock is $rock.\n"; # 依次输出每块石头的名字
}
use strict编译指令
希望Perl能更严格,多一点约束力,可以use strict编译指令 。
所谓编译指令(pragma) ,其实不过是提供给编译器的某些指示,告诉它如何处理接下来的代码。这里的use strict编译指令是要告诉Perl内部的编译器接下来( 代码块或是程序源文件中)的代码应该稍加严谨点,遵循优良的编程风格。
比如:
$bamm_bamm = 3; #Perl会自动创建这个变量
#然后你继续写了一些代码。当上一行代码被新写的代码挤出屏幕顶端后,你又写了下面这行代码,想增加那个变量的值:
$bammbamm += 1; #糟了!
因为Perl看到了一个新的变量名(变量名中的下划线是有意义的),它会创建一个新的变量,然后增加它的值。如果你有先见之明,已经启用了警告功能,那么Perl就会警告你这两个(或其中之一)全局变量名在程序中只出现过一次。可是,如果你不够谨慎,那么这两个变量名都会在程序里并存,Perl也并不发出警告。
要告诉PerI你愿意接受更严格的限制,请将use strict这 个编译指令放在程序开头(或者任何需要强制使用约束规则的语句块或文件内):
use strict; #强制使用一些严格的、良好的编程风格
use 5.012; #自动加载strict编译指令
现在,所有的约束当中,Perl首先会坚持要求你声明所有新出现的变量。一般加
上my就可以了
my $bamm_bamm = 3; # 新的词法变量
$bammbamm += 1; #无此变量:属于编译时致命错误
现在,拼错变量名,报错说从未声明过
b
a
m
m
b
a
m
m
这
个
变
量
,
因
此
错
误
在
编
译
阶
段
就
会
被
找
出
来
:
当
然
,
此
限
制
只
适
用
于
新
创
建
的
变
量
,
P
e
r
I
的
内
置
变
量
(
比
如
bammbamm这个变量,因此错误在编译阶段就会被找出来: 当然,此限制只适用于新创建的变量,PerI的内置变量(比如
bammbamm这个变量,因此错误在编译阶段就会被找出来:当然,此限制只适用于新创建的变量,PerI的内置变量(比如和@)则完全不用事先声明。
tips:比整个屏幕长的程序都应该加上use strict,最好在开始写程序时就用它。
return操作符
如果想在子程序执行到一半时停止执行,使用return操作符,它会立即停止执行并从子程序内返回某个值:
my @names = qw/ fred barney betty dino wilma pebbles bamm-bamm /;
my $result = &which_element_is(" dino", @names);
sub which_element_is {
my($what, @array) = @_;
foreach (0. .$#array) { # 数组@array中 所有元素的索引
if ($what eq $array[$_ ]) {
return $_ ; #一发现就提前返回
}
}
-1;
} #没找到符合条件的元素(写不写return都没关系)
我们希望这个子程序能找出@names数组中值为dino的元素的索引。首先,用my声明参数名:一个是
w
h
a
t
表
示
要
搜
索
的
内
容
,
另
一
个
是
@
a
r
r
a
y
,
表
示
供
搜
索
的
目
标
数
组
。
这
个
数
组
其
实
是
@
n
a
m
e
s
的
拷
贝
。
f
o
r
e
a
c
h
循
环
依
次
取
出
@
a
r
r
a
y
的
索
引
值
(
第
一
个
索
引
值
为
0
,
最
后
一
个
索
引
值
为
what表示要搜索的内容,另一个是@array,表示供搜索的目标数组。这个数组其实是@names的拷贝。foreach循环依次取出@array的索引值(第一个索引值为0,最后一个索引值为
what表示要搜索的内容,另一个是@array,表示供搜索的目标数组。这个数组其实是@names的拷贝。foreach循环依次取出@array的索引值(第一个索引值为0,最后一个索引值为#array,这个我们已经在第三章见到过了)。
每一次执行foreach循环时,我们会检查$what中的字符串是否等于位于当前索引的数组@array的元素。如果两者相等,我们就立刻返回其索引值。这是关键字return最常见的用法:立即返回某个值,而不再执行子程序的其余部分。
但是,假如我们找不到符合条件的那个元素呢?本例中,子程序的作者选择返回-1作为“查无此值”的数字代号。虽然在这个例子里,返回undef可能会更符合Perl的风格,但他还是决定用-1。最后一行写成return -1也行, 但实际上return并不是必需的。
有些程序员在每次返回值时都会用return,以明确表明它是返回值。比如在子程序执行
到中间部分就已经得出结论的话,就可以使用return关键字立即返回,就像本章之前提
到&larger_of_fred_or_barney时说的那样。而到子程序末尾再返回结果的话,则完全可
以省略return。
省略与号
在调用子程序时何时可以省略与号。如果编译器在调用子程序前看到过子程序的定义,或者Perl通过语法规则判断它只能是子程序调用,那么对待该子程序就可以像内置函数那样,在调用时省略与号。
换句话说,假如Perl单从语法上便能看出它是省略了与号的子程序调用,也就是说,你只要将参数列表放进括号里,它就一定是函数调用:
my @cards = shuffle(@deck_of_cards); # &shuffle 上的&是多余的
或者,如果Per1的内部编译器已经见过子程序的定义,那么通常也可以省略与号。并
且,连参数列表两边的括号都可以省略:
sub division {
$_[0] / $_[1];#用第一个参数除以第个参数
}
my $quotient = division 355, 113; # 用的就是之前定义的&division
上面的程序之所以能够运行,是因为它符合“加不加括号都不会产生歧义”的原则。
但不要把子程序定义放在调用语句的后面,否则编译器就无法提前判断division的意义。编译器需要在子程序调用前先看到子程序定义,才有办法像对内置函数般调用该子程序;否则,编译器不知道该如何处理这个表达式。
例外是:假如这个子程序和PerI内置函数同名, 你就必须使用与号调用。原因很简单,也是为了避免歧义。加上与号,就说明调用的是你自己定义的子程序。所以只能在没有同名内置函数的情况下省略与号:
sub chomp {
print "Munch, munch!\n";
]
&chomp; #这里必须使用&,绝不能省略!
真正的省略规则如下:
除非你知道Perl所有的内置函数名,否则请务必在调用函数时使用与号。这意味着,你最初写的100个程序应该始终加上与号。但若看到别人的程序中省略与号的写法,那未必是错的,也许他很清楚Perl没有这个名字的内置函数
非标量返回值
子程序不仅可以返回标量值,如果你在列表上下文调用它,它还能返回列表值。
假设你想取出某段范围的数字(如同范围操作符…的递增序列),只是你不但想递增,
还想递减序列。虽然范围操作符只能产生递增序列,不过要反过来取也不是什么难事:
sub list_from_fred_to_barney {
if ($fred < $barney) {
#从$fred数到$barney
$fred. . $barney;
} else {
#从$fred倒数回$barney
reverse $barney..$fred;
}
}
$fred = 11;
$barney = 6;
@c = &list_from_fred_to_barney; # @c的值为(11, 10, 9, 8, 7, 6)
我们会先用范围操作符取得从6到11的列表,再用reverse操作符把它反转。最后的结果就是我们想要的:从
f
r
e
d
(
11
)
倒
数
到
fred(11)倒数到
fred(11)倒数到barney(6)的列表。
其实你还可以什么都不返回。单写一个return不给任何参数时,在标量上下文中的返回值就是undef,在列表上下文中则是空列表。这通常用于表示子程序执行有误,它告诉调用者无法取得有意义的返回值。
持久化私有变量
在子程序中可以使用my操作符来创建私有变量,但每次调用这个子程序的时候,这个私有变量都会被重新定义。而使用state操作符来声明变量,我们便可以在子程序的多次调用期间保留变量之前的值,并将变量的作用域局限于子程序内部。
回到本章第一个例子,我们有个名为marine的子程序,它会使全局变量的值每次递增:
sub marine {
$n +=1; # 全局变量$n
print "Hello, sailor number $n!\n";
我们可以用state来声明变量,它会告诉Perl该变量属于当前子程序的私有变量,并且在
多次调用这个子程序期间保留该变量的值。
use 5.010;
sub marine {
state $n=0;#持久性私有变量$n
$n += 1;
print "He1lo, sailor number $n!\n";
现在我们可以在用了strict编译指令并且不用全局变量的前提下得到和之前相同的输
出。第一次调用该子程序时,Perl声明并初始化变量
n
,
而
在
接
下
来
的
调
用
中
,
这
个
表
达
式
将
被
P
e
r
l
忽
略
。
每
次
子
程
序
返
回
后
,
P
e
r
I
都
会
将
变
量
n,而在接下来的调用中,这个表 达式将被Perl忽略。每次子程序返回后,PerI都会将变量
n,而在接下来的调用中,这个表达式将被Perl忽略。每次子程序返回后,PerI都会将变量n的当前值保留下来,以备下次
调用时再用。
类似标量变量,其他任意类型的变量都可以被声明为state变量。下面的子程序可以通过声明state数组来保留它的参数及计算总和:
use 5.010;
running_sum( 5, 6 );
running_sum( 1..3 );
running_sum( 4 );
sub running_sum {
state $sum = 0;
state @numbers;
foreach my $number ( @_ ) {
push @numbers,$number;()
$sum = $number;
}
say "The sum of (@numbers) is $sum" ;
}
每次我们调用这个子程序的时候,它都会将新的参数与之前的参数相加,因此每次都会
输出一个新的总和:
Thesumof(56)is11
Thesumof(56123)is17
The sum of (561234) is 21
但是在使用数组和哈希类型的state变量时,不能在列表上下文中初始化这两种类型的state变量:
state @array = qw(a b c); #错误!
习题
1.[12]写一个名为total的子程序,它可以返回给定列表中数字相加的总和。提示:该子程序不需要执行任何I/O,它只需要按要求处理它的参数并给调用者返回一个值就行了。用下面这个程序来检验一下你写完的子程序,第一次调用时返回的列表中数字的总和应该是25。
my @fred=qw{1 3 5 7 9};
my $fred_total = total(@fred);
print "The total of \@fred is $fred_total. \n";
print "Enter some numbers on separate lines: ";
my $user_total = total(<STDIN>);
print "The total of those numbers is $user_total. \n";
- [5]使用之前程序中的子程序,计算从1加到I000的总和。
参考答案:更简单的方式——直接在print里调用子程序(注意不可在“ ”中调用)
print "the numbers from 1 to 1000 add up tp",total(1..1000),"\n";
- [18]额外附加题: 写一个名为&above_average的子程序,当给定一个包含多个数字的列表时,返回其中大于这些数平均值的数。(提示: 另外写一个子程序,通过用这些数的总和除以列表中数字的个数来计算它们的平均值。)当你写完后,用下面的程序检验一下。
my @fred = above_average(1..10);
print "\@fred is @fred\n";
print "(Should be 6 7 8 9 10)\n";my @barney = above_average(100, 1..10);
print "\@barney is @barney\n" ;
print "(Should be just 100)\n";
参考答案:
sub average{
if (@_==0){return}
my $conut=@_;
my $sum=total(@_);
$sum/$count;
}
sub above_average{
my $average =average(@_)
my @list;
foreach my $element(@_)
if ($element>$average){
push @list,$element;}
}
@list;
}
- [10]写一个名为greet的子程序, 当给定一个人名作为参数时,打印出欢迎他的信息,并告诉他前一个来宾的名字:
greet( “Fred” );
greet( “Barney” )
按照语句的顺序,它应该打印出:
Hi Fred! You are the first one here!
Hi Barney! Fred is also here!
use 5.010;
greet('Fred');
greet('Barney');
sub greet{
state $last_person;
my $name=shift;
print "HI $name";
if (defined $last_person){
print "$last_person is also here!\n";}
else{
print "you are the first one here!\n"}
$last_person=$name;
}
- [10]修改 前面这个程序,告诉所有新来的人之前已经迎接了哪些人:
greet( “Fred” );
greet( “Barney” );
greet( “Wilma”
greet( “Betty” );.
按照语句的顺序,它应该打印出:
Hi Fred! You are the first one here!
Hi Barney! I’ve seen: Fred
Hi Wilma! I’ve seen: Fred Barney
Hi Betty! I’ve seen: Fred Barney Wilma
use 5.010;
greet('Fred');
greet('Barney');
greet('Wilma');
greet('Betty');
sub greet{
state $name;
my $name=shift;
print "HI $name";
if (@names){
print "$last_person is also here!\n";}
else{
print "you are the first one here!\n"}
push @names,$name;
}