[PERL] 12- 副程式

URL Link //n.sfs.tw/11707

2017-08-25 22:50:43 By 張○○

PERL的副程式就是所謂的函數。

副程式的寫法

以sub 開頭接著副程式的名稱

sub function_name {
   # 副程式內容
   return 1; # 副程式回傳,非必要
   # 在return 後的指令是不會被執行的
}

和別的語言函式定義不太一樣,就是沒有小括號讓你傳參數進去。

要回傳結果可用 return,這個指令非必要可省略。在return 後面的指令不會被執行。

副程式的叫用

&function_name;  # 前面加一個 '&'
&function_name( 12, 34, 56); # 傳入值
&function_name( @arr ); # 傳入陣列

此外副程式擺放的叫用順序沒有關係,因為PERL是全部載完再編譯執行,下面兩個同義:

sub p { ... }
&p; # 副程式在前

等同於

&p; # 副程式在後
sub p { ... }

副程式的取值

副程式中要怎麼取得傳入的引數?

可以使用二種方法:1shift 函數或2特殊陣列@_

使用shift

shift 在陣列中有提過,是從陣列的最開始取出一個項目,取出後引數少1,再執行一次再少1...

&p (10,24,32);

sub p {
  $_ = shift;
  print "$_\n"; #10
  $_ = shift;
  print "$_\n"; #24
  $_ = shift;
  print "$_\n"; #32
  $_ = shift;
  print "$_\n"; #Use of uninitialized value $_ in concatenation (.) or string at..
}

上面的範例中,傳入3個引數,理應只能取用(shift)3次,若取用到第4次時,就出現錯誤。眼尖的人也許會想到,shift這個是取出陣列最開頭的項目,後面應該接著陣列,可是範例中shift後並無接著陣列,為什麼能取出引數呢?原來預設傳入的引數會放到PERL的特殊變數 @_

使用@_

@_是預設副程式傳入的參數,若傳入的引數有3個,可以直接使用清單的方式指定。

&p (10,24,32);

sub p {
  ($a,$b,$c) = @_;
  print $a, $b, $c; #102432
  print "\n";
}

某些情況你可能不知道使用者會傳入幾個參數,例如要算平均數,使用者可傳多個引數,這時用 @_就最方便了

$avg= &avg (10,24,32,-15);
print $avg;

sub avg {
  $sum=0;
  foreach (@_){
    $sum += $_;
  }
  return $sum/ @_;
}

第1行 叫用副程式 avg,並傳入4個引數,回傳的結果設定給變數 $avg,這裡故意把副程式的名稱和變數取名一樣,在PERL裡面這兩個是不相干的。

第6-8行 對傳入的引數進行加總,迴圈的寫法應該不陌生了。

第9行 直接把總和除以引數的個數,注意這裡把@當成純量時就是項目的個數(前面有提過)。

這個程式看似沒有問題,但還是有缺點,就是若使用者叫用時無傳入引數,在平均時因為@_=0 (分母為0)則會出錯。因此加了一個檢查而確保。修正後的程式如下:

$avg= &avg ;
print $avg;

sub avg {
  return 0 if @_==0;
  $sum=0;
  foreach (@_){
    $sum += $_;
  }
  return $sum/ @_;
}

第5行 檢查傳入引數為0,則直接回傳0。

傳入引數應該注意的問題

在PERL中會把傳入的所有引數都放到陣列@_之中,假如在引數中放入陣列會發生什麼事?來看看下面的範例:

傳入一個陣列

@a= (1,3,4);
&avg(@a) ;

sub avg {
  print "@_"; # 1 3 4
}

第5行 沒問題的印出傳入的@a

傳入二個陣列

如果傳入二個或多個陣列,結果就不會是你要的

@a= (1,3,4);
@b= (6,7,8);
&avg(@a,@b);

sub avg {
  print "@_"; # 1 3 4 6 7 8
}

還記得在 08-陣列 #2 --操作 提過,PERL的陣列全是一元的,如果把多個陣列放到引數後,所有的引數都會被攤平成為一個 @_,如果使用 shift 取引數和你想的不一樣。

@a= (1,3,4);
@b= (6,7,8);
&avg(@a,@b);

sub avg {
  @p= shift;
  @q= shift;
  print "@p\n"; # 1
  print "@q\n"; # 3
}

第6-7行 原本以為可以取得原本傳入的2個陣列引數,結果竟然是被攤平了,只取取得了1和3。

所以知道多個陣列當成引數時,都自動被合成一個一維的陣列。

同時傳入變數和陣列

同時傳入變數和陣列的話,變數要先寫,陣列放最後,並用清單的方式就能取出資料。

下面的例子把變數$z放最後一個引數,並觀察它被陣列@r吃掉了。

$x= 123; $y=555; $z='ABC';
@b= (6,7,8);
&avg($x,$y,@b,$z);

sub avg {
  ($p,$q,@r,$z)= @_;
  print "$p\n"; # 123
  print "$q\n"; # 555
  print "@r\n"; # 6 7 8 ABC
  print "$z\n"; # Use of uninitialized value $z in concatenation (.) or string at ..
}

第9行 @r陣列把後面的引數全部收攏當成他的。

第10行 在陣列@r後的$z 取不到資料,出了錯。

結論
1. 無論傳入幾個變數或陣列,都會被合併成一個一維陣列@_
2. 傳入的引數有陣列的話,要放在最後一個引數
3. 使用清單取得傳入的引數,陣列要放最後一個,剩下的引數全會被陣列收走。
4. 用shift一次只能取一個項目,不是一個引數。

 

學會了副程式的寫法後,我們可能會寫很多副程式並重覆的利用,最好的方法就是存成一個個只有副程式存在的檔案,叫作「函式庫」。

使用函式庫

假設先把副程式存成一個檔案叫 mylib.pl作為函式庫,裡面暫時只放一個剛才寫的求平均數副程式

mylib.pl
sub avg { 
  return 0 if @_==0;
  $sum=0; 
  foreach (@_){ $sum += $_; } 
  return $sum/ @_;
 }

1;

接下來在我們要執行的主程式中,使用 require 保留字把我們函式庫 mylib.pl 載入

run.pl
#!/usr/local/bin/perl -w
require "mylib.pl";

print &avg(3,5,6,8,11);

第2行 使用require 載入mylib.pl這個函式庫

執行

$ ./run.pl
6.6

在上面的例子中,也許讀者注意到 mylib.pl這裡面最後多了一個數字1加上分號:1;

感覺非常的奇怪,這是什麼東西?如果好奇把他拿掉,就出現這樣的錯誤:

  mylib.pl did not return a true value at ./run.pl line 2.

首先要說PERL在某方面是很怪異的語言,1; 的意思其實是 return 1; 說真的讓你回傳一個1不是很莫名其妙嗎?那我寫成 2;可不可以?這次我不和你說結果你自己試!

但為什麼要寫一個 1; 在最後面?這是因為require+檔案時它需要一個true的回應:好吧,那就給他一個1;,這個東西在後面講到 [PERL] 18-套件及模組 時會再次出現。

這篇先介紹副程式的基本操作,使用副程式可以讓你的程式看起來更加簡潔,下一篇會說明如何建立自己的函式庫及變數的「視界」。

 

上一篇 11-雜湊的範例
回到目錄 01-撰寫第一隻PERL程式
下一篇 13-變數的視界