副程式
簡單的副程式
如果你發現程式當中, 有些五六行以上, 類似的程式片段重複出現好幾次, 就表示你的程式可能可以改進。 把類似的程式片段寫成一個 subroutine 副程式 或 function 函數, 把每次出現時小有變化的部分寫成副程式的 parameter/argument 參數/引數, 這樣就可用一句簡單的副程式呼叫來取代原來整個片段出現的部分。 這樣做有許多好處:
- 比較容易除錯 -- 改正副程式內的一個錯誤, 就等於同時改正好幾處的錯誤。
- 比較容易增加功能/改善效率 -- 改進副程式內一處的功能, 就等於同時改進好幾處的功能。
- 程式變得比較短, 比較漂亮。
試想: 如果一段程式碼重複出現三次, 以後或許也很可能再出現。 如果出現在不同的程式檔裡面, 就更麻煩了: 改進了一處的錯誤/功能, 很可能忘記改另一處。 如果寫成副程式, 不僅可以避免這種狀況, 也可以少打很多字。
執行結束後, 把運算結果傳回呼叫者的, 稱為函數; 純粹用以產生副作用 (例如列印/存檔/播放聲音/...) 沒有傳回值的, 稱為副程式。 在 perl 裡面, 兩者並無特殊分別, 唯一的差別是 return 敘述後面有沒有東西。 我們看一個簡單的 perl 副程式:
sub sum {
my ($x, $y) = @_;
return $x + $y;
}
Perl 的副程式, 用 sub 定義。 這裡的 sum 是自己任意取的副程式的名字。 Perl 的副程式, 所有參數一律靠 @_ 傳遞。 一般的 perl 副程式, 第一句話就是宣告幾個局部變數, 從 @_ 把參數接收過來。 這樣做有兩個用意:
- 參數有名字, 程式比較容易閱讀。
- 因為 perl 的參數傳遞是 call-by-reference, 這麼做可以避免不小心更改到呼叫者的變數。 後詳。
進入副程式之後, 就可以將先前學過的, 你熟悉的語法應用上來,
隨便你要算什麼。 我們這個副程式太簡單, 什麼都沒有算。
通常最後一句話是 return, 也就是將值傳回呼叫者去。
然後你就可以在程式其他地方使用這個副程式, 像這樣: print "5 + 3
is ", sum(5,3), "\n";
請將這兩小段程式碼剪貼到你的編輯器上,
立刻試試看。 舊的 perl 程式, 呼叫副程式時前面要加一個
&
, 像這樣: print "5 + 3 is ", & sum(5,3),
"\n";
。 Q: 如果呼叫 sum(5,3,2)
會發生什麼事?
Perl 不檢查參數的個數, 也不檢查參數的形態。 如果想將 sum 改成可以對任意個數字加總, 可以用一個陣列來接收參數:
sub sum_all {
my (@numbers) = @_;
my ($n, $total);
foreach $n (@numbers) {
$total += $n;
}
return $total;
}
從呼叫者的角度來看, 變得很有彈性, 想傳幾個數字進去都可以:
@data = (2, 3, 5, 7, 11, 13, 17, 19);
printf "sum_all(\@data) = %d\n", sum_all(@data);
printf "sum_all(5, 8, \@data) = %d\n", sum_all(5, 8, @data);
printf "sum_all(\@data, 97, 53, \@data) = %d\n",
sum_all(@data, 97, 53, @data);
Q: 這裡的倒斜線是什麼意思? 提示: 不是 "...的位址"; 實驗一下, 將它拿掉就知道。 它出現在 "..." 裡面, 意義及效果比較像是 " ... \$20 ..."。
你可以傳幾個數字進去, 也可以傳一個陣列, 或是將陣列與純量混著傳。 這一切其實不過就是先前 詳談變數 裡面所提到的規則: list 沒有層次。 同理, 有關 hash 與 list 互相轉換的規則也適用, 例如你可以在一個副程式裡面用一個 hash 來接收參數:
sub print_like_hash {
my (%data) = @_;
my ($key);
foreach $key (keys %data) {
print "$key: $data{$key}\n";
}
}
於是呼叫時可以寫成像是 hash 在設定初始值一樣:
print_like_hash("Jan"=>31, "Feb"=>28, "Mar"=>31,
"Apr"=>30);
如先前所述, =>
其實不過就是逗點, 傳進去的東西不過就是一個 list, 它仍舊被放入 @_
這個陣列當中; 只不過後來被複製到一個 hash 裡面去了。
文字模式遊戲函式庫
ANSI escape sequence 可以在文字模式下製造特效, 例如移動遊標, 改變顏色等等。 它不受限於一套作業系統, 也不受限於一種程式語言。 瞭解如何在 shell 底下直接手動用 ANSI escape sequence 製造特效後, 就可以自己寫一個簡單的函式庫 sitio
函式庫的最後一句話通常是 1;
(其實只要是有定義, 且非 0
非空字串的值就可以了, 總之要傳回 true。)
如何引用函式庫? 在主程式檔案裡面放一句: require
"sitio";
之後就可以任意使用 sitio 提供的那些副程式了。
不過我們先做一個錯誤示範: 請故意將 require 後面的檔案名稱打錯,
或故意將 sitio 放在其他目錄, 總之就是要讓主程式找不到函式庫。
錯誤訊息 說什麼呢?
以下尚未整理
- 程式範例:
-
傳入不定個數的參數; 傳出不只一個結果:
- 既然接收參數的是 @_ 這個陣列, 就表示在呼叫副程式時, 可以傳入任意個數的參數, 甚至可以把整個陣列或陣列與純量的組合一起傳入.
- 在副程式內用 my ( ... ) = @_; 取得參數時, my ( ... ) 的最後可以是一個陣列變數. 它會把 @_ 剩下的所有元素全部吃進去.
- 上述各點可以類推至傳回值: 例如在
15puzzle 中, 用 ($row, $col) =
& blankpos( ... ) 來呼叫副程式, 而副程式 blankpos 中使用
return ($r, $c) 則可以想像 perl 自動做了: ($row, $col) = ($r,
$c) 其中 assignment 左右都可以是 array 或 hash. 也就是說,
傳回值可以不只是一個純量. 但要注意若等號左邊只有一個變數,
小括弧還是不能省略, 否則會接收到最後一個元素!
請試試看這個簡單的程式片段:
($x) = (1,2,3,4);
看看 $x 變成多少? 再把$x
外面的小括弧拿掉, 看看 $x 又變成多少?
-
仔細探究 perlsub(1), 發覺 perl 副程式的參數傳遞其實是
call-by-reference! 如果在副程式當中不以 my 複製參數, 而是直接在
@_ 上操作, 則有機會修改到主程式中傳進來的變數.
所以這個副程式可以用來交換兩個變數的內容:
sub swap { ($_[0], $_[1]) = ($_[1], $_[0]); }
(但傳回值則沒有這麼複雜.) -
自建程式庫:
- 在你的 perl 程式當中先下 require "Xyz.pl", 之後 就可以使用 Xyz.pl 這個副程式庫內定義的副程式.
- 如果 perl 找不到你的副程式庫檔放在那個目錄底下, 可以在 require 一句的檔名當中加上完整的路徑 (像這樣: require "./Xyz.pl";) 或在 require 之前先下 use lib ... 以修改 @INC 變數的內容 (詳見 perlvar(1)), 或在 perl 命令列上指定 -I 選項 (詳見 perlrun(1)). 請實驗一下, 看看找不到副程式時印出來的錯誤訊息是什麼。
- Recursion (遞迴) 及 activation record
-
其他常識:
- formal argument/parameter 形式參數: 在副程式的 definition (定義) 的參數列當中出現的東西
- actual argument/parameter 實際參數: 在呼叫副程式 (invocation) 的地方出現
- "問號-冒號" 是一個三元運算子 (它接受三個參數). A ? B : C 的運算結果可能是 B 也可能是 C. 究竟是 B 還是 C 呢? 要看 A 這個條件是否成立. 如果成立, 則結果就是 B; 要不然結果就是 C.
- index 函數可以找子字串出現的位置; rindex 倒過來找; 與這兩個函數相反的是 substr, 可以取出指定位置的子字串.
- 一種有用的程式設計風格供參考: 能夠算出來的資訊, 盡量不要存入全域變數. (例如 15puzzle 中的空白位置) 目的不在節省空間, 而在減少資料修改時顧此失彼所造成的不一致 (inconsistence). 多花一點執行時間通常不是很大的問題.
- Perl 沒有真正的二維陣列, 但是可以用 hash 或陣列的陣列來模擬.
- 要測試一個變數內的值是否已有定義, 可用 defined; 要取消一個變數內的值 (讓它不是 0 也不是空字串, 更不是任何其他值, 就像已宣告但尚未給初始值一樣), 可用 undef.
- 作業:
- 本頁最新版網址: https://frdm.cyut.edu.tw/~ckhung/b/pl/subroutine.php; 您所看到的版本: February 14 2012 10:32:25.
- 作者: 朝陽科技大學 資訊管理系 洪朝貴
- 寶貝你我的地球, 請 減少列印, 多用背面, 丟棄時做垃圾分類。
- 本文件以 Creative Commons Attribution-ShareAlike License 或以 Free Document License 方式公開授權大眾自由複製/修改/散佈。