Perl 程式設計/函式
Perl 函式是一組程式碼,允許它輕鬆地重複使用。函式是除最小的程式之外的所有程式的關鍵組織元件。
到目前為止,我們每次只編寫了幾行 Perl 程式碼。我們的示例程式從檔案的頂部開始,一直執行到檔案的底部,並使用諸如if, elseandwhile之類的控制流關鍵字進行了一些跳轉。但是,在許多情況下,在程式中新增另一層組織很有用。
例如,當典型程式出錯時,它會列印“錯誤訊息”。列印錯誤訊息的程式碼可能如下所示
print STDOUT "something went wrong!\n";
其他訊息可能看起來略有不同,例如
print STDOUT "something ELSE went wrong!\n";
我們可以用數百行這樣的程式碼來填充我們的程式碼,並且事情會一直正常執行……一段時間。但是,遲早,我們會希望將錯誤訊息與報告程式有關的無害資訊的“狀態訊息”分離開來。為此,我們可以用單詞“ERROR”作為所有錯誤訊息的字首,並用“STATUS”作為所有狀態訊息的字首。典型錯誤訊息的程式碼將更改為
print STDOUT "ERROR: something went wrong!\n";
問題是,對於數百條錯誤訊息,更改它們全部將是一件麻煩事。這就是子程式可以提供幫助的地方。
維基百科將子程式定義為“作為較大程式的一部分執行特定任務的一系列指令。可以從程式中的不同位置呼叫子程式,從而允許程式重複訪問子程式,而無需多次編寫子程式的程式碼。”
所有這些令人費解的話都意味著我們可以將錯誤訊息程式碼包裝在一個地方,如下所示
sub print_error_message {
my($message) = @_;
print STDOUT "ERROR: " . $message . "\n";
}
每當程式出錯時,我們都可以啟用或呼叫此子程式,並使用我們喜歡的任何訊息
print_error_message("something bad happened");
print_error_message("something really horrible happened");
print_error_message("something sort of annoying happened");
並檢視以下訊息
ERROR: something bad happened
如果我們需要更改錯誤訊息的格式,例如,包括一些感嘆號,只需更改子程式即可
sub print_error_message {
my($message) = @_;
print STDOUT "ERROR: " . $message . "!!!\n";
}
這確實是一個簡單的例子,子程式還有一些其他優點,但總而言之就是這樣。在一個地方輸入,在一個地方修復,在一個地方更改。
現在讓我們更詳細地瞭解一下子程式。以下內容大部分將使用以下子程式,如果還不明顯,它將兩個數字加在一起並返回它們的總和。
sub add_two_numbers {
my($x, $y) = @_;
my $sum = $x + $y;
return $sum;
}
sub add_two_numbers {
my($x, $y) = @_;
my $sum = $x + $y;
return $sum;
}
函式的第一行以關鍵字sub開頭,後跟函式名。任何不是保留 Perl 單詞(例如for, while, ifandelse)的字母和數字字串都是有效的函式名。名稱描述了子程式作用的子程式可以使程式更易於閱讀。
sub add_two_numbers($$) {
可選的($$)指定此子程式期望多少個引數。($$)表示“此函式需要兩個標量值”。Perl 原型並非大多數有其他語言經驗的人所期望的那樣:相反,原型會更改傳遞給子程式的引數的上下文。
可以透過在函式名前加&來停用原型,但不建議這樣做。
原型很少使用,因為使用正常引數傳遞方法更容易。
子程式的主體執行“工作”,並由三個主要部分組成。
傳遞給子程式的資訊片段稱為引數或實際引數。例如,在
add_two_numbers(3, 4);
3and4是子程式的引數add_two_numbers.
Perl 透過一個由@_表示的陣列將引數傳遞給子程式。通常,為這些引數指定有意義的名稱會更方便,因此函式的第一行通常如下所示
sub add_two_numbers {
my($x, $y) = @_; # reading parameters
my $sum = $x + $y;
return $sum;
}
將內容放入名為@_的兩個變數中$xand$y. $xand$y被稱為形式引數。形式引數(引數)和實際引數之間的區別很細微,在大多數情況下並不重要。在維基百科文章引數(計算機科學)中有對其進行了一些描述。請注意不要將特殊變數$_and@_(傳遞給函式的引數陣列)混淆。
某些子程式不需要任何引數,例如
sub hello_world {
print STDOUT "Hello World!\n";
}
將“Hello World”列印到 STDOUT。此子程式不需要任何有關如何執行其工作的額外資訊,因此不需要任何引數。
大多數現代程式語言可以為程式設計師節省顯式分解引數陣列為變數的麻煩。不幸的是,Perl 沒有。另一方面,這使得編寫具有可變數量引數的子程式變得非常容易。
在程式設計上下文中,引數的含義與引數幾乎相同(有關詳細資訊,請參閱引數)。這兩個詞經常被混淆,但不會造成理解上的損失。
與 C 或 Java 等程式語言不同,在 Perl 子程式中建立或使用的所有變數預設情況下都是全域性變數。這意味著程式中子程式外部的任何部分都可能修改這些變數,並且您的子程式可能在不知情的情況下修改了它不應該修改的變數。在小型程式中,這通常很方便,但隨著程式變得越來越長,這通常會導致複雜性,並且被認為是不好的做法。
避免此陷阱的最佳方法是在變數第一次出現時在其前面加上關鍵字my。這告訴 Perl 您只希望這些變數在最近的封閉花括號組內可用。實際上,這些區域性變數充當子程式中使用的“臨時空間”,當子程式返回時會消失。行use strict;在程式的頂部將指示 Perl強制您在變數前使用my,以防止您意外建立全域性變數。
您可能在一些較舊的 Perl 程式中看到的my的替代方案是local關鍵字。local與my有點類似,但處理起來更復雜。最好在您自己的程式中堅持使用my。
“作用域”描述了變數是區域性變數還是全域性變數,以及其他一些複雜性。有關技術討論,請參閱作用域。
在子程式的中間,您可能會發現所有內容中更有趣的“核心”。在我們的 add_two_numbers 子程式中,這是實際執行加法的部分
sub add_two_numbers {
my($x, $y) = @_;
my $sum = $x + $y; # the interesting part
return $sum;
}
在此中間部分,您可以做任何您想做的事情,算術運算、列印到檔案,甚至呼叫其他子程式。
最後,一些子程式使用return關鍵字。
sub add_two_numbers {
my($x, $y) = @_;
my $sum = $x + $y;
return $sum; # the return statement
}
例如
$sum = add_two_numbers(4, 5);
將設定$sum為 9(4 和 5 的和)。
return也可以不用任何返回值作為快捷方式,在到達結束的}
子程式可以在 Perl 程式的任何地方宣告。它們可以像這樣呼叫
add_two_numbers(4, 5); # the safest approach
add_two_numbers 4, 5; # only if predeclared
&add_two_numbers(4, 5); # older Perl syntax, but still valid
如果沒有“&”字首,除非子程式已預先宣告,否則需要使用括號。
函式本身就為編寫良好的程式碼提供了重要的基石,但將函式組合在一起才能真正發揮其威力。
正如您可能預期的那樣,從另一個函式內部呼叫函式與從程式中位於任何花括號外部的部分呼叫函式沒有什麼區別。
此函式將兩個數字相加,然後將它們乘以 3。請耐心等待這些函式的無用性。在您構建自己的程式以解決您獨特的問題時,您會立即看到它們的用處。
sub add_two_numbers_and_mult_by_three {
my($x, $y) = @_; # read parameters
my $sum = add_two_numbers($x, $y); # add x and y, put result in sum
my $sum_times_three = $sum*3; # multiply by three
return $sum_times_three; # return result
}
這一行
my $sum = add_two_numbers($x, $y); # add x and y, put result in sum
呼叫我們的函式add_two_numbers並將結果放入我們的$sum變數中。很簡單,對吧?
在這個函式中,我們實際上編寫了比我們需要的更多的程式碼。它可以簡化為更小的東西,但同樣易於閱讀
sub add_two_numbers_and_mult_by_three {
my($x, $y) = @_; # read parameters
return 3*add_two_numbers($x, $y); # add x and y, mult by 3, return
}
我們已經看到函式呼叫其他函式,但程式設計中一個簡潔的概念是函式呼叫自身。這稱為遞迴。起初,這似乎可能導致所謂的無限迴圈,但它實際上是相當標準的程式設計。
在數學中,階乘函式將一個正整數乘以小於自身的所有正整數。例如,“5 的階乘”(通常寫成5!) 是透過將 5 乘以 4 乘以 3 乘以 2 乘以 1 計算得出的。當然,1 不會改變結果。階乘函式可用於計算諸如在餐桌上安排親屬的不同方式的數量。
階乘為遞迴提供了一個自然的例子,儘管它也可以用while迴圈輕鬆地編寫。
sub factorial {
my($num) = @_;
if ($num == 1) {
return 1; # stop at 1, factorial doesn't multiply times zero
} else {
return $num*factorial($num - 1); # call factorial function recursively
}
}
這裡的自引用行是
return $num*factorial($num - 1); # call factorial function recursively
它從階乘函式內部呼叫階乘函式。這將永遠持續下去,但我們有一種阻止它的停止標誌
if ($num == 1) {
return 1; # stop at 1, factorial doesn't multiply times zero
}
這停止了對階乘的呼叫序列,並防止了永無止境的無限迴圈。
用一種長篇大論的方式寫出來
factorial(5) = 5*factorial(4) = 5*4*factorial(3) = 5*4*3*factorial(2) = 5*4*3*2*factorial(1) = 5*4*3*2*1 = 120
我們只是簡單地接觸了遞迴。對於某些程式設計問題,它是一個非常自然的解決方案。對於其他問題,它有點……不自然。可以肯定地說,這是每個程式設計師都應該隨身攜帶的工具。
在閱讀程式設計文獻並與程式設計師交談時,您可能會遇到“函式”、“過程”和“子程式”這三個術語。大多數情況下,這些術語可以互換使用,但對於那些追求完美的人來說
函式始終返回一個值,並且在給定相同引數的情況下,始終返回相同的值。函式非常類似於您可能在數學課上學過的函式。
過程與函式不同,它可能根本不返回值。與函式不同,過程通常與其引數陣列之外的外部環境進行互動。例如,過程可以讀取和寫入檔案。
子程式指的是可以是函式或過程的一系列指令。
定義子程式的語法為
sub NAME PROTOTYPE ATTRIBUTES BLOCK
如果您理解這一點,您可能不需要閱讀本書 ;)