學習perl(3)

湖湘文化發表於2013-11-19
 

學習Perl (3)
這幾天學習Perl,我的感覺是越到後面的內容越難一些。

子例程
1)定義子例程
定義你自己的子例程時,將會用到關鍵字sub、子例程的名稱(不含&符號)以及(花括號內)經縮排的構成子例程主體的程式程式碼塊:(子例程的定義可以放在程式中的任何地方)
#!/usr/bin/perl -w

sub marine {

$n += 1;

print "Hello,sailor number $n!n";

}

2
)呼叫子例程
任何表示式中只要使用了子例程的名稱(加上&符號),就會呼叫子例程:&marine;
3
)返回值
Perl中,任何子例程都有返回值,子例程並沒有分成有返回值和沒有返回值兩種。
#!/usr/bin/perl -w

sub sum_of_fred_and_barney {

print "Hey,you called the sum_of_fred_and_barney subroutine!n";

$fred + $barney;

}

$fred = 3;

$barney = 4;

$c = &sum_of_fred_and_barney;

print "$c is $c.n";

$d = 3 * &sum_of_fred_and_barney;

print "$d is $d.n";

sub larger_of_fred_and_barney {

if ($fred > $barney){

$fred;

} else {

$barney;

}

}

$fred = 3;

$barney = 4;

$c = &larger_of_fred_and_barney;

print "The larger is $c.n";

3
)引數
要傳遞引數列表到子例程裡,只要在子例程呼叫的後面加上被括住的列表表示式就行了:$n = &max(10,15); #本子例程的呼叫包含了兩個引數
當然得有變數來儲存這個列表,所以Perl會自動將引數列表儲存到名為@_的特殊陣列變數,在子例程執行期間內都有效。子例程可以訪問這個陣列,以判斷引數的個數和這些引數的值。所以這表示子例程的第一個引數儲存於$_[0],第二個引數儲存於$_[1],依次類推。
但是請特別注意,這些變數和$_變數毫無關聯,就像$dino[3]$dino能彼此共存一樣。
sub max {

if ($_[0] > $_[1]) {

$_[0];

} else {

$_[1];

}

}

4)子例程裡的私有變數
Perl裡,所有變數都被預設為全域性變數,也就是說,在程式裡的任何地方都可以訪問它們。但是你可以隨時執行my運算子來建立稱為詞法變數的私有變數:
sub max {

my($m,$n) = @_;

if ($m > $n) { $m } else { $n }

}

5)長度可變的引數列表
更好的&max例程
#!/usr/bin/perl -w

$maximum = &max(3 , 5, 10, 4, 6);

sub max {

my($max_so_far) = shift @_;

foreach (@_) {

if ($_ > $max_so_far) {

$max_so_far = $_;

}

}

$max_so_far;

}

print "$maximum.n";
上面的程式碼使用了一般稱為高水位標記的演算法:大水過後,在最後一波浪消退時,高水位標記會表示所見過最高的水位。在本例中,$max_so_far記錄了最高水位標記,亦最大數字。

6)關於詞法(my)變數
事實上,詞法變數可以使用在任何塊內,而不僅限於子例程的塊。
還需要注意的是,my運算子並不會更改變數賦值時的上下文:
my($num) = @_; #列表上下文,和($num) = @_;相同
my $num = @_; #標量上下文,和$num = @_;相同
注意,在my不加括號時,只會宣告一個詞法變數而已:
my $fred,$barney; #錯,沒宣告$barney my ($fred,$barney); #兩個都宣告瞭

7use strict編譯命令
所謂編譯命令,就是對編譯器的指示,告訴它關於程式程式碼的一些資訊。
要告訴Perl你願意接受某些限制,請use strict這個編譯命令放在程式開頭(或在任何想要強制使用這些規則的塊或檔案裡): use strict; #強制使用一些良好的程式語言規則
這樣,撇開其他種種限制不談,Perl會要求你一定要用my來宣告每個新變數。當然,此限制只適用於新建立的變數。根據大部分人的建議,比整個螢幕長的程式都應該加上use strict

8return運算子
在子例程裡,return運算子會立即返回某個值:
#!/usr/bin/perl -w

use strict;

my @names = qw/ fred barney betty dino wilma pebbles bamm-bamm /;

my $result = &which_element_is("dino", @names);

sub which_element_is {

my($what,@list) = @_;

foreach (0..$#list) {

if ($what eq $list[$_]){

return $_;

}

}

-1;

}
Perl程式時return最常見的用法:馬上返回某個值,而不要執行子例程的其餘部分。

9)省略&符號
不要將子例程的宣告放到子例程的呼叫之後,不然編譯器就無法知道division的意義何在。
真正的陷阱是,假如這個子例程與Perl內建函式同名,你就必須適用&符號來呼叫:
#!/usr/bin/perl -w

use strict ;

sub chomp {

print " Munch, munch!n";

}

&chomp; #&符號不可省略!
所以,真正的省略規則如下:除非你知道所有Perl內建函式的名稱,否則請務必在呼叫函式時使用&符號!

10)返回非標量值
子例程不僅可以返回標量值,只要在列表上下文中呼叫它,它就可以返回列表值:
#!/usr/bin/perl -w

sub list_from_fred_to_barney {

if ($fred < $barney) {

$fred..$barney;

} else {

reverse $barney..$fred;

}

}

$fred = 11;

$barney = 6;

@c = &list_from_fred_to_barney;

print "@c.n";

習題:
1)寫一支名為&total的子例程,用來返回一串數字的總和:
#!/usr/bin/perl -w

use strict;

sub total {

my $sum;

foreach (@_) {

$sum += $_;

}

$sum;

}

my @fred = qw{ 1 3 5 7 9 };

my $fred_total = &total(@fred);

print "The total of @fred is $fred_total.n";

print "Enter some numbers on separate lines:";

my $user_total = &total();

print "The total of those number is $user_total.n";

2
)利用前一題的子例程,寫一支程式來計算從11000的數值的總和:
#!/usr/bin/perl -w

use strict;

sub total {

my $sum;

foreach (@_) {

$sum += $_;

}

$sum;

}

my @fred = 1..1000 ;

my $fred_total = &total(@fred);

print "The total of @fred is $fred_total.n";
參考答案如下:
#!/usr/bin/perl -w

use strict;

sub total {

my $sum = 0;

foreach (@_) {

$sum += $_;

}

$sum;

}

print "The number from 1 to 1000 add up to:",&total(1..1000),".n";

3)寫一支名為&above_average的子例程,用來傳入一串數字並返回所有大於平均值的數字:
#!/usr/bin/perl -w

sub total {

my $sum = 0;

foreach (@_) {

$sum += $_;

}

$sum;

}

sub average {

if (@_ == 0) { return }

my $count = @_;

my $sum = &total(@_);

$sum/$count;

}

sub above_average {

my $average = &average(@_);

my @list;

foreach $element (@_) {

if ($element > $average) {

push @list, $element;

}

}

@list;

}

my @fred = &above_average(1..10);

print "@fred is @fredn";

print "(Should be 6 7 8 9 10)n";

my @barney = &above_average(100,1..10);

print "@barney is @barneyn";

print "(Should be just 100)n";


輸入與輸出
1)讀取標準輸入
在標量上下文中使用運算子時,將會返回標準輸入的下一行:
#!/usr/bin/perl -w

while (defined($line = )) {

print "I saw $line";

}

#!/usr/bin/perl -w

while () {

print "I saw $_";

}

#!/usr/bin/perl -w

while (defined($_ = )) {

print "I saw $_";

}
在列表上下文中執行整行輸入運算子,它會返回一個列表,其中包含(剩下來的)所有輸入內容,每個列表的元素代表一行輸入內容:
#!/usr/bin/perl -w

foreach () {

print "I saw $_";

}
while
迴圈和foreach迴圈的區別:在while迴圈裡,Perl會讀取一行輸入,把它存入某個變數並且執行迴圈的主體,接下來,它會回頭去尋找其他的輸入行;但是在foreach迴圈裡,整行輸入運算子會在列表上下文中執行(因為foreach需要逐項處理列表的內容),為此,在迴圈能夠開始執行之前,它必須先將輸入內容全部讀進來。因此,最好的做好是儘量使用while迴圈的簡寫,讓它每次處理一行。

2)從鑽石運算子輸入
如果想寫出用法像catsedawksortgreplpr等工具的Perl程式,鑽石運算子”<>”將會是你的好幫手。程式的呼叫引數通常是命令列上跟在程式名稱後面的幾個“單詞”:
$ ./my_program fred barney betty #命令列引數代表依次處理的數個檔案的名稱
若不提供任何呼叫引數,程式應該改寫成處理標準輸入流。但有個特例,如果某個引數是連字元(-),那也代表標準輸入。讓程式以這種方式執行的好處,就是你可以在執行時指定程式的輸入來源。鑽石運算子是整行輸入運算子的特例,不過它並不是從鍵盤取得輸入,而是從使用者想要的輸入來源取得:
#!/usr/bin/perl -w

while (defined($line = <>)) {

chomp($line);

print "It was $line that I saw!n";

}
簡寫:#!/usr/bin/perl -w

while (<>) {

chomp; #chomp的預設用法,不加引數時,chomp會直接作用在$_上。

print "It was $_ that I saw!n";

}

3
)呼叫引數
嚴格來說,鑽石運算子其實不會真的去檢查呼叫引數,直譯器事先建立的特殊陣列,其內容就是呼叫引數所組成的列表。鑽石運算子如何決定該使用哪些檔名?方法如下:它會查詢@ARGV,如果它找到的是空列表,就會改用標準輸入流;否則,就會使用@ARGV裡的檔案列表。
4)輸出至標準輸出
#!/usr/bin/perl -w

$name = "Larry Wall" ;

print "Hello there,$name,did you know that 3+4 is",3+4,"?n";
輸出陣列和替換陣列是兩回事:
print @array; #輸出列表中的專案,各個專案之間沒有空格
print “@array”; #輸出一個字串(內容是陣列替換的結果),專案間以空格隔開
一般來說,如果陣列裡的元素包含換行符號,那麼只要直接將它們輸出來就可以了;通常在使用引號的場合,字串後面都應該加上n

print <>; #’cat’的原始碼 print sort <>; #’sort’的原始碼
print的呼叫看起來像函式呼叫,函式呼叫:print (2+3);
print
的返回值不是“真”就是“假”,代表print是否執行成功。除非發生了I/O錯誤,否則一定會執行成功:$result = print(“Hello world!n”);
#!/usr/bin/perl -w

$result = print("Hello world!n");

print "The result is : $result.n";
如果print(或其他函式名稱)後面接著一個左括號,請務必在函式的所有引數之後也有相應的右括號。
省略括號的問題: print (2+3)*4 # print ((2+3)*4); #正確

5)用printf格式化輸出結果
printf運算子的引數包括“格式字串”及“所要輸出的資料列表”,後面的列表裡的專案的個數應該要和轉換的數目一樣多,否則就會無法正常執行。
printf可用的轉換格式很多,如%g%s%f%d等:
#!/usr/bin/perl -w

printf "in %d days!n",17.85; #輸出結果為in 17 days! 無條件捨去小數點後的數字


6)檔案控制程式碼
檔案控制程式碼就是Perl程式裡的某個名稱,代表Perl程式與外界之間的輸入/輸出聯絡。它是“聯絡”的名稱,不一定是檔名。建議全部使用大寫字母來命名檔案控制程式碼。Perl保留了6個具有特殊用途的檔案控制程式碼名稱:STDINSTDOUTSTDERRDATAARGVARGVOUT
標準的STDERR通常是使用者的螢幕,但可以將STDERR以如下的shell命令送到某個檔案:
開啟檔案控制程式碼
當你需要其他普通的檔案控制程式碼時,請使用open運算子告訴Perl,要求作業系統為你的程式和外界開啟一座橋樑:open CONFIG, “,dino”; open LOG, “>>logfile”;open BEDROCK,”>fred”;
不良檔案控制程式碼
如果你嘗試從不良檔案控制程式碼(即沒有完全開啟的檔案控制程式碼)來讀取資料,將會立刻讀到檔案結尾。如果試圖將資料寫入不良檔案控制程式碼,這些資料將無聲無息地被丟棄掉。可以避免:一開始就用-w選項,或者warnings編譯命令來啟用警告資訊。
關閉檔案控制程式碼
當不再需要時,可以使用close運算子來關閉檔案控制程式碼:close BEDROCK;

7
)用die來處理嚴重錯誤
die函式會輸出你指定的資訊(到此類資訊該去的標準錯誤流裡),並且讓你的程式在非零的狀態下立即終止。通常,對於程式的結束狀態來說,零代表成功,非零代表失敗。
Perl特殊變數“$!”,程式錯誤資訊。
if ( !open LOG,">>logfile") {

die "Cannot create logfile:$!";

}
一個經驗法則就是,用來指示用法錯誤的資訊裡可以加上結尾的換行符,但若想在除錯過程中追蹤相關的錯誤,就不要加上換行的結尾符。請一定檢查open的返回值,因為之後的程式程式碼必須在檔案開啟成功時才能順利進行。
warn送出警告資訊
warn函式不會停止程式的執行。

8)使用檔案控制程式碼
當檔案控制程式碼以讀取模式開啟後,你可以輕易的從它讀取一行資料,就像從STDIN讀取標準輸入流一樣。
以寫入或新增模式開啟的檔案控制程式碼,可以在printprintf函式中使用。使用時,請直接放在關鍵字之後,引數列表之前:print LOG “Captain’s log,stardate 3.14159n”;
改變預設的輸出檔案控制程式碼
重新開啟標準的檔案控制程式碼

習題
1)寫一支功能跟cat類似的程式,但是將各行反序輸出:
#!/usr/bin/perl -w

print "Please enter some lines,then press Ctrl+D!n";

print reverse<>;
參考答案如下:
#!/usr/bin/perl -w

print reverse<>;
2
)寫一支程式,要求使用者分行鍵入各個字串,然後以20個字元寬、向右對齊的方式輸出每個字串:
#!/usr/bin/perl -w

print "Please enter some lines,each line for one,then press Ctrl+D:n";

chomp(my @lines = );

print "1234567890" x 7,"12345n";

foreach(@lines) {

printf "%20sn",$_;

}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/21256317/viewspace-776983/,如需轉載,請註明出處,否則將追究法律責任。