子程序(subroutine)
- perl中的子程序其實就是自定義函數。它使用
sub
關鍵字開頭,表示聲明一個子程序 - 子程序名稱有獨立的名稱空間,不會和其它名稱沖突
- Perl中的子程序中可以定義、引用、修改全局變量,這和幾乎所有的語言都不同。當然,也可以定義局部變量
- perl中使用
&SUB_NAME()
的方式調用SUB_NAME子程序,&
有時候可以省略,括號有時候也可以省略。具體的規則見后面
sub mysub {
$n += 1;
print "\$n is: $n","\n";
}
&mysub;
print "\$n is: $n","\n";
- perl的子程序最后一個執行的語句的結果或返回值就是子程序的返回值(是最后一個執行的語句,不是最后一行語句)。所以子程序總會有返回值,但返回值並不總是有用的返回值
sub mysub {
$n += 2;
print "hello world\n";
$n + 3;
}
$num = ⊂
這里的&sub
有返回值,它的返回值是最后執行的語句$n+3
,並將返回值賦值給$num
,所以$num
的值為5。
如果把上面的print作為最后一句:
sub mysub {
$n += 2;
$n + 3; # 這不是返回值
print "hello world\n"; # 這是返回值
}
$num = ⊂
上面的print才是子程序的返回值,這個返回值是什么?這是一個輸出語句,它的返回值為1,表示成功打印。這個返回值顯然沒什么用。所以,子程序最后執行的語句一定要小心檢查。
那上面的$n+3
的結果是什么?它的上下文是void context,相加的結果會被丟棄。
子程序還可以返回列表。
($m,$n)=(3,5);
sub mysub {
$m..($n+1);
}
@arr=&mysub;
print @arr; # 輸出3456
- 如果想在子程序的中間退出子程序,就可以使用
return
子句來返回值。當然,如果這個返回語句是最后一行,就可以省略return
# 返回一個偶數
$n=20;
sub mysub {
if ($n % 2 == 0){
return $n;
}
$n-1; # 等價於 return $n-1;
}
$oushu=&mysub;
print $oushu,"\n";
子程序參數
Perl子程序除了可以直接操作全局變量,還可以傳遞參數。例如:
&mysub(arg1,arg2...);
&mysub arg1,arg2...;
至於什么時候可以省略括號,后面再解釋。
- 調用子程序時傳遞的參數會傳入子程序,它們存儲在一個特殊的數組變量
@_
中 - 既然
@_
是數組,就可以使用$_[index]
的方式引用數組中的各元素,也就是子程序的各個參數 @_
數組只在每個子程序執行期間有效,每個子程序的@_
都互不影響。子程序執行完成,@_
將復原為原來的值
# 返回最大值
@_=qw(perl python shell); # 測試:先定義數組@_
sub mysub {
if($_[0] > $_[1]){
$_[0];
}else{
$_[1];
}
}
$max = &mysub(10,20);
print $max,"\n";
print @_; # 子程序執行完,@_復原為qw(perl python shell)
如果上面的子程序給的參數不是兩個,而是3個或者1個(&mysub(10,20,30);
、&mysub(10);
)會如何?因為參數是存在數組中的,所以給的參數多了,子程序用不到它,所以忽略多出的參數,如果給的參數少了,那么缺少的那些參數被引用時,值將是undef。
所以,在邏輯上來說,參數多少不會影響子程序的錯誤,但結果可能會受到一些影響。但可以在子程序中判斷子程序的參數個數:
if(@_ != 2){ # 如果參數個數不是2個,就退出
return 1;
}
這里的比較是標量上下文,@_
返回的是參數個數。
調用子程序的幾種方式分析
一般情況下,調用我們自定義的子程序時,都使用&
符號,有時候還要帶上括號傳遞參數。
&mysub1();
&mysub2;
&mysub3 arg1,arg2;
&mysub4(arg1,arg2);
但有時候,&
符號和括號是可以省略的。主要的規則是:
- 只有子程序定義語句在子程序調用語句前面,才可以省略括號
- 當使用了括號,perl已經知道這就是一個子程序調用,這時可以省略
&
也可以不省略&
- 不能省略
&
的情況比較少。基本上,只要子程序名稱不和內置函數同名,或者有特殊需求時(如需要明確子程序的名稱時,如defined(&mysub)
),都可以省略&
- 不能在使用了
&
、有參數傳遞的情況下,省略括號 - 最安全、最保險的調用方式是:
- 有參數時:
&subname(arg1,arg2)
,即不省略&
和括號 - 無參數時:
&subname
- 使用
&
的調用方式是比較古老的行為,雖然安全。但直接使用括號調用也基本無差別,但卻更現代,所以建議用func()的方式調用自定義的子程序
- 有參數時:
sub mysub{
print @_,"\n";
}
# 先定義了子程序
mysub; # 正常調用
mysub(); # 正常調用
mysub("hello","world3"); # 正常調用
mysub "hello","world4"; # 正常調用
&mysub; # 安全的調用
&mysub("hello","world6"); # 安全的調用
&mysub "hello","world7"; # 本調用錯誤,因為使用了&,且有參數
上面是先定義子程序,再調用子程序的。下面是先調用子程序,再定義子程序的。
mysub; # 本調用無括號,不報錯,當做內置函數執行,但無此內置函數,所以忽略
mysub(); # 有括號,不報錯
mysub("hello","world3"); # 有括號,不報錯
mysub "hello","world4"; # 無括號,本調用錯誤
&mysub "hello","world7"; # 本調用錯誤
&mysub; # 安全的調用
&mysub("hello","world6"); # 安全的調用
sub mysub{
print @_,"HELLO","\n";
}
如果子程序名稱和內置函數同名,則不安全的調用方式總會優先調用內置函數。
子程序中的私有變量:my關鍵字
my關鍵字可以聲明局部變量、局部數組。它可以用在任何類型的語句塊內,而不限於sub子程序中。
sub mysub {
my $a=0; # 一次只能聲明一個局部目標
my $b; # 聲明變量,初始undef
my @arr; # 聲明空數組
my($m,$n); # 一次可以聲明多個局部目標
($m,$n)=@_; # 將函數參數賦值給$m,$n
my($x,$y) = @_; # 一步操作
}
foreach my $var (@arr) { # 將控制變量定義為局部變量
my($sq) = $_ * $_; # 在foreach語句塊內定義局部變量
}
在my定義局部變量的時候,需要注意列表上下文和標量上下文:
@_=qw(perl shell python);
my $num = @_; # 標量上下文
my (@num) = @_; # 列表上下文
print $num,"\n"; # 返回3
print @num,"\n"; # 返回perlshellpython
在Perl中,除了my可以修飾作用域,還有local和our也可以修飾作用域,它們之間的區別參見:Perl的our、my、local的區別。
state關鍵字
my關鍵字是讓變量、數組、哈希私有化,state關鍵字則是讓私有變量、數組、哈希持久化。注意兩個關鍵字:私有,持久化。
使用state關鍵字聲明、初始化的變量對外不可見,但對其所在子程序是持久的:每次調用子程序后的變量值都保存着,下次再調用子程序會繼承這個值。
這個特性是perl 5.10版才引入的,所以必須加上use 5.010;
語句才能使用該功能。
use 5.010;
$n=22;
sub mysub {
state $n += 1;
print "Hello,$n\n";
}
&mysub; # 輸出Hello,1
print $n,"\n"; # 輸出22
&mysub; # 輸出Hello,2
print $n,"\n"; # 輸出22
當然,如果在子程序中每次都用state將變量強制初始化,那么這個變量持久與否就無所謂了,這時用my關鍵字的效果是一樣的。
use 5.010;
sub mysub {
state $n=0;
print "hello,$n","\n";
}
&mysub;
&mysub;
&mysub;
state除了可以初始化變量,還可以初始化數組和hash。但初始化數組和hash的時候有限制:不能在列表上下文初始化。
use 5.010;
sub mysub {
state $n; # 初始化為undef
state @arr1; # 初始化為空列表()
state @arr2 = qw(perl shell); # 錯誤,不能在列表上下文初始化
}
perl作用域初探
因為perl中支持先定義子程序再調用,也支持先調用再定義的方式。不同的調用方式有可能會有區別。
例如:
#!/usr/bin/env perl -w
use strict;
# do_stuff(1); # (1).在定義的前面
{
my $last = 1;
sub do_stuff {
my $arg = shift;
print $arg + $last;
}
}
do_stuff(1); # (2).在定義的后面
上面在不同的位置調用子程序do_stuff(1)
,但只有第二種方式是正確的,第一種方式是錯誤的。
原因在於my定義的變量是詞法作用域變量,先不用管詞法作用域是什么。只需要知道my定義的變量是在程序編譯期間定義的好的,但是賦值操作是在程序執行期間進行的。而子程序sub的名稱do_stuff是無法加my關鍵字的,所以perl中所有的子程序都是全局范圍可調用的。子程序的調用是程序執行期間的。
所以上面的程序中,整個過程是先在編譯期間定義好詞法變量$last
(但未賦值初始化),子程序do_stuff。然后開始從程序頭部往下執行:
當執行到(1)的位置處也就是調用子程序,這個子程序中引用了變量$last
,但$last
至今未賦值,所以會報變量未初始化的錯誤。
如果沒有(1),那么從上往下執行將首先執行到my $last = 1
,這表示為已在編譯期間定義好的變量賦值。然后再繼續執行到(2)調用子程序,但子程序引用的$last
已經賦值初始化,所以一切正常。
在perl中的子程序是在編譯期間定義好的,還是執行期間臨時去定義的,目前我個人還不是太確定,按照perl的作用域規則,它應該是在執行期間臨時去定義的。但無論如何,它先定義還是后定義,都不影響對變量作用域的判斷。