Learning Perl 第四章 子程序 系統內置函數,chomp,reverse和print等。Perl也能夠建立子程序,就是用戶自定義函數。 子程序屬於獨立的名字空間,不會將同一段代碼中的子程序&fred和標量$fred混淆。 定義子程序 子程序由關鍵字sub,子程序名以及花括號封閉起來的代碼塊組成。 sub marine { $n += 1; print "hello, number $n!\n" ; } 子程序被定義在程序中的任意位置,不須要事先聲明,是全局的,不存在所謂的私有子程序。 定義了兩個子程序,則後面的會覆蓋前面的。 調用子程序 能夠在表達式中使用子程序(前面加上與號)調用: &marine; &marine; hello, number 1! hello, number 2! 返回值 子程序被調用時必定是做爲表達式的某個部分,即便該表達式的求指結果不會被用到。 須要調用子程序並對它的返回值作進一步的處理,全部的子程序都有返回值,但不是全部的子程序都包含有用的返回值。 Larry簡化了"return",子程序執行過程當中不斷運算,而最後一次運算結果被自動看成子程序的返回值。 sub sum_of_fred_and_barnay { print "hi, you called the sum of fred and barnay!\n"; $fred + $barnay; } $fred = 4; $barnay = 5; $wilma = &sum_of_fred_and_barnay; print "\$wilma is $wilma.\n"; $betty = 3 * &sum_of_fred_and_barnay; print "\$betty is $betty.\n"; hi, you called the sum of fred and barnay! $wilma is 9. hi, you called the sum of fred and barnay! $betty is 27. print語句只用於協助調試,程序完成後能夠刪除,假設在子程序結尾新增一條print語句 則表達式最後並不是加法運算,而是print語句,返回值一般是1,表示成功輸出信息。 這種狀況術語是"void context"(空上下文),表示運算結果沒有存儲在變量裏面,未被任何函數使用。 sub larger_of_fred_or_barnay { if ($fred > $barnay) { return $fred; } else { return $barnay; } } larger of fred and barnay is 5. 必須等到執行階段獲得變量內容後,纔會知道返回值究竟是$fred仍是$barney。 參數 子程序不強制使用全局變量。 Perl子程序能夠有參數,要傳遞參數列表到子程序裏,只要在子程序調用的後面加上被括號圈引的列表表達式。 $n = &max(20, 15); 參數列表被傳遞到子程序中,能夠隨意調用,Perl會自動將參數列表化名爲特殊的數組變量@_,該變量在子程序執行期間有效。子程序能夠訪問數組,以判斷數組的個數以及參數的值。 子程序的第一個參數存儲在$_[0],第二個參數存儲在$_[1],以此類推。這些變量與$_毫無關聯。參數列表老是存在某個數組變量裏,可讓子程序調用,Perl將這個數組稱爲@_。 sub max { if ($_[0] > $_[1]) { $_[0]; } else { $_[1]; } } $qing = &max(20, 12); print "the larger argument is $qing.\n"; the larger argument is 20. 子程序只能接受兩個參數,多餘的參數會被忽略,參數若是不足也會被忽略——若是用到超過@_數組邊界的參數,只會獲得undef。 @_變量是子程序的私有變量。若是已經有了全局變量@_,則該變量在子程序調用是存起來,並在子程序返回時恢復本來的指。因此在當前的子程序調用中,@_老是包含了它的參數列表。 子程序中的私有變量 Perl裏面全部的變量都是全局變量,可是隨時可使用my操做符來建立私有變量,稱爲詞法變量(lexicak variable): sub max { my($m, $n); ($m, $n) = @_; if ($m > $n) {$m} else {$n}; } $a = &max(1,4); print "$a"; 這些變量屬於封閉語法塊的私有變量,能夠稱做有限做用域(scope)變量。 語法塊以外的任意地方的$m和$n都徹底不受這兩個私有變量的影響。外部變量也沒法影響內部的私有變量。 Perl容許省略語法塊的最後一個分號,通常只有簡單到整個語句塊內只有一行時,才能夠省略分號。 變長參數列表 在Perl代碼中,經常把任意長度的列表做爲參數傳遞給子程序。 習慣於強類型子程序,只容許必定個數的參數而且預訂限制它們的類型。不過子程序超過預期個數被調用時,也會形成問題。 檢查@_數組的長度也很容易肯定參數個數是否正確。能夠寫出這樣檢查參數列表: sub max { if (@_ != 2) { print "WARNING! &max should get exactly two arguments!\n"}; } . . . } if判斷使用Perl中在標量上下文中直接使用數組名稱來獲取數組元素的個數。 改進&max子程序 $maxmum = &max(1, 12, 3, 24, 5, 36); sub max { my($max_so_far) = shift @_; foreach (@_) { if ($_ > $max_so_far) { $max_so_far = $_; } } $max_so_far; } print $maxmum 36 上述程序代碼使用稱爲高水線(high-watermark)算法。 第一行代碼會對參數數組@_進行shift操做並返回1並存入$max_so_far變量,如今@_如今的內容爲(12, 3, 24, 5, 36),由於1已經被移走,也就是第一個參數。 foreach循環會遍歷參數列表@_裏剩餘的元素,循環的控制變量默認爲$_。 空列表參數 假如傳入的參數爲@number爲空列表。 第一行對參數數組@_進行shift操做,以此做爲$max_so_far的值。由於數組爲空,因此shift會返回undef。 foreach循環空數組,因此自己不會執行。 Perl會將$max_so_far的值undef做爲子程序的返回值。 關於詞法變量my 詞法變量能夠在任何語句塊內,而不只限於子程序的語法塊。能夠在if,while和foreach的語句塊內使用。 foreach (1..10) { my ($squre) = $_ * $_; print "$_ squred is $squre.\n " } 1 squred is 1. 2 squred is 4. 3 squred is 9. 4 squred is 16. 5 squred is 25. 6 squred is 36. 7 squred is 49. 8 squred is 64. 9 squred is 81. 10 squred is 100. $squre對於所屬的語句塊來講是私有的。若是變量定義並未出如今任何語句塊內,則該變量對於整個程序源文件來講是私有的。詞法變量的做用域受限於定義它的最內層語句塊或文件。只有語句塊上下文做用域的程序代碼才已$squre使用該變量。 my操做符並不會改變變量賦值時的上下文: my($num) = @_;#列表上下文 my $num = @_; #標量上下文 my操做符在不加括號時,只能用來聲明單個詞法變量: my $cao ,$qing; #erroe my($cao, $qing); #right 能夠用my來定義私有數組:my @numbers; 標量被設定爲undef,數組被設定爲空數組。 use strict編譯指令 編譯指令,就是給編譯器的某些提示,告訴它如何處理接下來的代碼。 從Perl5.12開始,使用編譯指令指定最低Perl版本號,就至關於隱式打開約束指令: use 5.012; return操做符 return操做符會當即中止執行並從子程序返回某個值: my @names = qw/cao qing wang huan xiao bao/; my $result = &which_element_is("huan", @names); sub which_element_is { my($what, @array) = @_; foreach (0..$#array) { if ($what eq $array[$_]) { return $_;} } -1; } print "$result"; 3 在子程序執行到中間部分就已經獲得結論的時候,就可使用return關鍵字當即返回. 省略與號 若是編譯器知道在調用子程序前看到子程序的定義,或者Perl經過語法規則判斷它只能是子程序調用,那麼對待該子程序就能夠像內置變量那種,在調用時省略與號。 只要將參數列表放在括號內,就必定是函數調用: my @cards = shuffle(@deck_of_cards); 若是編譯器已經見過子程序的定義,也能夠省略與號。而且能夠省略參數列表兩邊的括號。 sub division { $_[0] / $_[1]; } my $quotient = division 20, 4; print $quotient; 5 子程序子因此能夠正確運行,是由於符合加不加括號都不會產生歧義的原則。 可是不要把子程序定義放在語句調用的後面。 加入子程序與Perl內置函數重名,必須使用&號,爲了不歧義。 非標量返回值 子程序不只能夠返回標量值,若是在列表上下文使用時,還能夠返回列表值。 sub list_from_cao_to_qing { if ($cao < $qing) { return $cao..$qing; } else { reverse $qing..$cao; } } $cao = 5; $qing = 1; @c = &list_from_cao_to_qing; print "@c"; 5 4 3 2 1 單寫一個return不加任何參數時,在標量上下文的返回值就是undef,在列表上下文返回空列表。 持久性私有變量 在子程序中使用my操做符來建立私有變量,每次調用子程序時,私有變量都會被從新定義。而使用state操做符聲明變量,就能夠在子程序調用期間保留變量以前的值,並將變量做用域侷限於子程序內部。 use 5.012; sub cao { state $n = 0; $n += 1; print "Hello $n.\n"; } &cao; &cao; &cao; Hello 1. Hello 2. Hello 3. 相似標量變量,其餘任意類型的變量均可以被聲明爲state變量。 use 5.012; sum(5, 6); sum(1..4); sum(3); sub sum { state $sum = 0; state @numbers; foreach my $number (@_) { push @numbers, $number; $sum += $number; } say "The sum of (@numbers) is $sum"; } The sum of (5 6) is 11 The sum of (5 6 1 2 3 4) is 21 The sum of (5 6 1 2 3 4 3) is 24 在使用數組和哈希的state變量時,仍是有限制的,沒法在列表上下文初始化這兩種類型的state變量。 習題 1. 寫一個名字爲total的子程序,能夠返回給定列表中數字相加的和。 2. 使用以前的程序,計算1到1000的值。 3. 寫一個名字爲above_average的子程序,給定一個包含多個數字的列表,返回大於這些數平均值的數。 4. 寫一個名爲greet的子程序,給定人名爲參數,打印歡迎信息。 5. 修改子程序,告訴新來的人,以前已經來過的人。 答案 1. use 5.012; sub total { my $sum; foreach (@_) { $sum += $_; } return $sum; } $a = &total(qw \1 2 3 4 5\); print "$a"; 2. use 5.012; sub total { my $sum; foreach (@_) { $sum += $_; } return $sum; } print "The sum of number form 1 to 1000", &total(1..1000),".\n"; 3. use 5.012; sub total { my $sum; foreach (@_) { $sum += $_; } return $sum; } sub average { if (@_ == 0) { return} my $count = @_; my $sum = &total(@_); $sum / $count; } sub above_average { my $average = average(@_); my @list; foreach my $element (@_){ if ($element > $average) { push @list, $element; } } @list; } my @fred = above_average(1..10); print "\@myfred is @fred.\n"; 4. use 5.012; &greet("caoqing"); &greet("xiaohuan"); &greet("baobao"); 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; 5. use 5.012; &greet("caoqing"); &greet("xiaohuan"); &greet("baobao"); sub greet { state @names; my $name = shift; print "Hi, $name! "; if ( @names ) { print "I have seen: @names!\n"; } else { print "You are the first one here!\n"; } push @names, $name; }