我們已經見過一些內建的系統函式, 像是 chomp, reverse 和 print 等等. 但是如同其他程式語言一樣, Perl 也可以讓你定義 副常式 (subroutine), 也就是由使用者定義的函式. 副常式可以讓我們在程式中重複利用一塊程式碼. 而在Perl 中使用副常式可以使用識別字 & 符號. Perl 有一到準則規定哪些時候可以省略 & 符號, 哪些時候不行. 目前除了禁止使用情況下, 我們一律使用 & 符號來使用副常式 ; 這是萬無一失的做法.
定義副常式 :
定義你自己的副常式, 將會使用到關鍵字 sub, 副常式的名稱 (不含 &) 以及 (大括號內) 經縮排的程式碼塊, 此區塊構成副常式主體, 範例如下 :
- sub marine {
- $n += 1;
- print "Marine $n!!\n";
- }
調用副常式 :
任何運算式中只要有使用副常式的名稱 (加上 & 符號), 就會調用該副常式 :
- &marine; # Show : Marine 1!!
- &marine; # Show : Marine 1!!
- &marine; # Show : Marine 1!!
既然任何 Perl 副常式都有傳回值, 所以 Larry 將之簡化成在副常式的執行過程中, 它會不斷的進行運算 ; 其中最後一次運算結果不管是多少, 都會自動被當作副常式的傳回值. 舉例來說, 我們定義了一個副常式如下 :
- sub sum_of_fred_and_barney {
- print "He, you call sum_of_fred_and_barney subroutine!!\n";
- $fred + $barney; # Return value of this subroutine.
- }
- $fred = 3;
- $barney = 4;
- $wilma = ∑_of_fred_and_barney;
- print "\$wilma is $wilma.\n";
- $betty = 3 * ∑_of_fred_and_barney;
- print "\$betty is $betty.\n";
- sub sum_of_fred_and_barney {
- print "He, you call sum_of_fred_and_barney subroutine!!\n";
- $fred + $barney; # Return value of this subroutine.
- }
引數 :
Perl 副常式可以有引數 (argument). 要將引數串列傳給副常式, 只要在副常式調用後面加上被圓括號括住的串列運算式就行了, 作法如下 :
$n = &max(10, 15); # 此副常式的呼叫包含兩個參數
引數串列會被傳遞給副常式, 當然會有個變數來儲存這個串列, 所以在參數串列會自動賦值給名為 @_ 的特殊陣列變數, 在副常式執行其間都有效. 所以這表示副常式的第一個變數存放於 $_[0], 第二個變數存放在 $_[1] 依此類推 - 請特別注意這些變數與 $_ 變數毫無關聯, 就像 $name[0](陣列變數) 與 $name (純量變數) 能彼此共存一樣, 而 @_ 就是給副常式使用的參數陣列而以. 接著我們來完成上面範例的 max 副函式 :
- sub max {
- if($_[0] > $_[1]) {
- $_[0];
- } else {
- $_[1];
- }
- }
副常式裡的私用變數 :
在 Perl 裡所有的變數都是全域變數, 也就是說在程式中任何地方你都可以存取它們. 但是你隨時可以使用 my 關鍵字建立稱為 語彙變數 (lexical variable) 的私有變數 :
- sub max {
- my($m, $n) = @_; # 將參數賦值給私用變數
- if($m > $n) {$m} else {$n}
- }
而前面範例副函式的第一列會建立並設定私用變數的值. 幾乎所有的副常式都會以類似的程式碼做為開頭. 但看到這一列你就會知道這個副常式具有兩個純量參數. 而在副常式中它們分別被成為 $m 與 $n.
不定長度的參數串列 :
Perl 哲理 "去除不必要的限制" 讓我們可以在副常式傳入任意數目的參數, 不過當傳入不如預期參數個數時也許會造成問題. 因此我們可以檢查 @_ 陣列以確定引數個數是否正確, 簡單範例如下 :
- sub max {
- if(@_ != 2) {print "Warning! Only two arguments required!\n";}
- print "Dealing as usual...\n";
- }
- sub max {
- my($max_so_far) = shift @_;
- foreach(@_) {
- if($_ > $max_so_far){$max_so_far = $_;}
- }
- $max_so_far;
- }
- print &max(1, 2, 4, 10, 8);
return 運算符 :
在副常式裡, return 運算符會立即傳回某個值 :
- my @names = qw/ fred barney betty john ken /;
- foreach(@names) {
- print "$_ ";
- }
- print "\n";
- print &which_element_is("john", @names);
- sub which_element_is {
- my($what, @array) = @_;
- foreach(0..$#array) {
- if($what eq $array[$_]) {
- return $_;
- }
- }
- -1;
- }
傳回非純量值 :
考慮你想取出某段範圍的數字 (如同範圍運算符 ..), 而你不但想往取, 也想往下取. 所以在不透過範圍運算符你可以定義副函式如下 :
- $a = 11;
- $b = 6;
- sub list_from_a_to_b {
- if($a < $b) {
- return $a..$b;
- } else {
- return reverse($b..$a);
- }
- }
- my @result = list_from_a_to_b; # 得到 (11, 10, 9, 8 ,7, 6)
- foreach(@result) {
- print "$_ ";
- }
持續的私用變數 :
使用 my 關鍵字, 我們可以定義副常式使用私用的變數, 但是副常式的每次呼叫都必須重新定義這些私用變數. 使用 state 關鍵字, 我們仍然可以定義副常式的私用變數, 不同的是 Perl 會在副常式的兩次呼叫之間保存這些變數. 考慮之前的 &marine 副函式改寫如下 :
- use strict;
- use 5.010;
- sub marine {
- state $n = 0;
- $n += 1;
- print "Marine $n!!\n";
- }
- marine;
- marine;
- marine;
- use strict;
- use 5.010;
- sub running_sum{
- state @numbers;
- state $sum=0;
- foreach my $number (@_) {
- push (@numbers, $number);
- $sum += $number;
- }
- print "Total Sum(@numbers) is $sum!\n";
- }
- running_sum(5, 6);
- running_sum(1..3);
- running_sum(4);
state @array = qw( a b c ) # 錯誤!!!!
這個問題可能在未來新版本的Perl 獲得解決.
補充說明 :
* Tutorialspoint > Perl Tutorial > Perl Subroutines
* Perl 學習手扎 > 副常式
沒有留言:
張貼留言