Cultured Perl: 吸引 C 和 Java 程式設計師目光的 Perl 5.6 (轉)

worldblog發表於2007-12-14
Cultured Perl: 吸引 C 和 Java 程式設計師目光的 Perl 5.6 (轉)[@more@]

:NAMESPACE PREFIX = O />  5.6 的新特性在哪些方面優於 C/C++/ ?

Teodor Zlatanov
Programmer, Northern Light, Inc.
January 2001

筆者側重於闡述 Perl 與 C 或 Java 不同的獨特之處。您一定會為 Perl 這些在其他語言中看不到的特性而心花怒放:運算子的容錯能力、一項任務多種實現、標點、正則以及變數機制等。所有這些都賦予您的手指更靈活的魔力。在某些方面 Perl 的確能給 C 和 Java 員很多有用幫助,可惜目前它還遠達不到眾所周知的程度。因此,抓緊機會提高您的 Perl 水平吧!

Perl 有時甚至令有的程式設計師也覺得頭疼,因為他們發現一不小心就會寫出模稜兩可的語句。但這種在結構、特性體系方面的模糊性從另一方面顯示了 Perl 語言強大的能力。畢竟 Perl 語言最開始的設計初衷就是希望能用多種方式來達到同一目的。

這裡我們將要探討 Perl 5.6 中那些最容易混淆的特性,並將它們與 C/C++/Java 中相應的特性做比較。主要圍繞 "Natural Language Principles in Perl" 中的原則展開(Larry Wall 著,參看本文末尾的 Linux/sdk/perl/culture-2/index.shtml#res">資料 部分),因為它們是 Perl 最能與 C、C++ 和 Java 語言區分開的地方。此外,關於 Perl 語法的結構可以在 "perldoc perlsyn" 參考文件中找到,另一本值得推薦的 Perl 指南讀物則是 Programming Perl

Perl 直譯器
初學者馬上就會發現 Perl 中似乎沒有任何。事實上,Perl 指令碼大多是由 Perl 直譯器直接執行的,例如 下的 "Perl"、DOS/ 下的 "perl.exe" 就是 Perl 直譯器。而在 Mac中則不需要這些直譯器。您可以試試看如何執行 Perl 指令碼。首先在您的上啟動相應的 Perl 直譯器,或是在 MacOS 系統中直接執行。在大多數系統中,尾標誌(UNIX 系統中為 Control-D)用來表示輸入結束。因此在 UNIX 系統中,下面這個指令碼將能得到 "5+6" 的計算結果:

從最簡單的 Perl 程式入手

> perl (Perl is waiting for user input here, because no script name is given) print 5+6 You press Control-D here 11



可以看到 Perl 直譯器執行了這個只有一行的指令碼程式,並輸入該表示式的計算結果 11。

Perl 直譯器有許多選項。例如,-e 選項表示將命令列的輸入作為指令碼檔案來,因此上面的指令碼例子也可以這樣實現:在命令列輸入 perl -e'print 5+6'(注意,要用單引號將命令括起來)。而 -i 選項則類似於透過一個過濾器,允許在檔案中不同的位置進行編輯。-n 和 -p 選項能讓程式設計師開啟或關閉輸出。-w 選項與 C/C++ 中的 "-Wall" 編譯選項類似,都能夠對程式中潛在問題給出警告資訊,但與 -Wall 不同的是,-w 功能在程式執行時也是被啟用的。

速度和 Benchmark
人們常常拿 Perl 與 C/C++ 比較,並抱怨 Perl 執行速度不夠快。某些時候這的確是事實,但是並非永遠如此。我建議您在認為 C 或 C++ 更快之前,使用 Benchmark 模組試試看 (perldoc Benchmark)。此外,Perl 能很方便地與 C/C++ 程式碼或庫連線,且某些 Perl 內建並不比 C 程式碼慢,如排序或列印等。這裡再次提醒您在堅信 C/C++ 比較快之前,先使用一下 Benchmark 模組。

要記住,過早的往往是錯誤的根源。如果您在 Perl 中寫了一個原型,並用其他語言來重寫是沒有問題的。原型意味著能夠方便地開發。

與 Java 相比而言,Perl 也能夠很好地工作。Perl 不象 Java 那樣擅長於執行緒,但它的 Tk GUI 介面工具箱卻比 Java 的 GUI 庫要好。並且 Java 程式碼總是能夠連線到 Perl 程式中,反之亦然。因此,有時您可以透過某種程度上的結合,使得程式在兩方面都做得很好。

異常、編譯和文件
Perl 透過 CPAN 中的模組或是其內建函式 eval() 來丟擲異常。就好像在 C++ 或 Java 中透過 try/catch 程式碼塊來處理異常一樣,eval 函式能處理某個程式碼段或某個字串操作的異常。

事實上,Perl 程式在執行之前還是需要編譯的,只是和 C/C++/Java 的編譯方式不相同。在設計和效果上,它和 Java 的位元組解釋過程很相似。關於編譯的更詳盡內容,可以參閱 "perldoc perlrun" 和 "perldoc perlcc" 文件。

可用 POD 格式來將文件嵌入 Perl 程式。這種文件嵌入方式比 Javadoc 格式(僅適合於 文件)要通用,但比不上 C/C++/Java 的註釋。

即使和 C、C++ 或 Java 比較起來,Perl 程式也不算是一種結構性強的語言。例如,BEGIN 語句塊會被首先執行,但它可以在程式中多次說明。定義、變數和函式體可以在程式的任意位置出現,Perl 所提供地強大功能可以最好地滿足這種隨意性。

由於這種鬆散結構、嵌入的註釋、以及為追求方便而導致的令人混淆的語句,使得書寫 Perl 程式更像是在寫一封英文信件。

Perl 的容錯能力
Perl 比 C/C++/Java 更能容忍一些模糊地寫法。例如可以用逗號來分隔語句或函式的引數:

語句之間或函式引數之間的分隔符

print 'Hello', ' ', 'there.', "n"; # print "Hello theren" foreach (1..10) { my $i; $i = $_ * 2, print "$in"; # print evens from 2 to 20 }



Perl 能盡最大可能地消除這些語句可能引起的歧義。當然,有些時候仍有無法解決的歧義(在這一點上,Perl 就象英語一樣)。

Perl 中另一個容易引起歧義的地方在於:變數經常會被隱含使用。例如,"print" 語句預設時會列印 $_ 變數的值。在其他一些含混的語句操作中,$_ 變數也是它們的預設值,這就造成了一種混亂。例如:

隱含使用變數

$_ = "hello"; s/hello/hi/; # $_ is "hi" now print; # prints "hi"



這裡你可以看到,使用預設變數能讓方便簡潔。也就是說,Perl 和英語類似,透過某種模糊性來簡化表示式。

一項任務多種實現
(There's more than one way to do it ,TMTOWTDI)

所有的語言在解決問題時都有自己的方法。在 C 裡面,for() 迴圈是在一定範圍內重複的最好方法;在 Java 裡,靜態函式的是直接透過類而不是某個例項。

但對於同一件事情,Perl 至少有兩種解決方法。TMTOWTDI 原則就是 Perl 的座右銘,各種處理上的差異在 Perl 程式設計中是深受鼓勵的。

下面來看一個列印陣列元素的例子。所有的表達方法都達到同一目的。

列印陣列元素

print foreach @array; foreach (@array) {print}; map {print} @array; print @array;



要理解以上這些程式碼的唯一途徑就是掌握所有 Perl 的語法。不要擔心哪種方法才是正確的,因為實現同一目標總是有多種正確的方法。考慮這些不同的表達方式,您可以更深刻體會 Perl 的這個座右銘。

另外,雖然一個任務的實現有多種方法,但這並不意味著所有方法都是正確的。通常情況下,更有可能寫出的是一些錯誤程式碼。為了保證程式碼的正確性,最好儘量使用那些 Perl 內建的函式,而較少使用自己所編寫的函式,並且注意證明並記錄這些不那麼顯然的方法。

正規表示式
如果沒有初始化,正規表示式很有可能造成一片混亂。大多數人都相信正規表示式是由 Kalahari bushmen 發明的,它滲透到了大學的科學程式設計的所有方面。

Perl 的正規表示式是從 指令碼程式以及 awk/grep 工具中繼承而來的。但它的能力卻遠遠超出了原來的模型。

基礎的正規表示式是非常容易書寫的,但難以讀懂。例如 "conw+" 和 "contra"、"contrary" 匹配,但與 "pro" 或 "con" 都不匹配。然而在 Perl 5.6.0 中,正規表示式被固化了。Unicode 字符集、內任意程式碼操作、flag toggles、條件表示式以及其他特徵都被新增到正規表示式庫中。

對於初學者的一個最好的建議就是:首先學習最基本的正規表示式(參閱 資料 部分,或 "perldoc perlre" 參考手冊),稍後才進一步學習那些複雜的高階特性。由於正規表示式必須全寫在一起,中間沒有辦法新增註釋,這就讓它們成為所有 Perl 程式碼中最難讀懂的一部分。因此建議大家書寫已成型的程式碼。

在 C/C++/Java 中正規表示式屬於外部的函式包,但 Perl 是目前最佳的正規表示式搜尋和代換工具。在極少數情況下,它可能會比純 C 程式慢一些,但對於那些純粹面向正規表示式的問題,Perl 依然是您首選的工具。

標量、陣列和雜湊雜湊
和 C、C++、Java 中變數不同的是,Perl 的變數的型別是由其名字決定的,並且會自動初始化成相應型別。這一點讓 Perl 初學者覺得很不習慣,但它非常地直觀而易於理解。

筆者推薦使用 "use strict"。透過它可以保證變數在使用之前宣告,從而避免打字錯誤等引起的程式錯誤。

如果沒有做到這一點,則有可能遇到下面這樣的問題:

一個常見的打字錯誤

$i = 5; print $j; # print $i



此例子中,程式設計師本來要列印變數 i 的值,結果卻敲成了 j。Perl 並不會覺得這段程式碼有什麼問題,它會繼續執行列印語句,顯示 $j 的值即什麼都沒有。有些時候,Perl 的自動生成的確很有用,但以我的經驗來說,最好還是用 "use strict" 來關掉這一自動功能,從而避免上述問題。

Perl 變數可以是標量 (scalars)、陣列 (arrays)或雜湊雜湊 (hashes,又叫做關聯陣列)。(事實上,Perl 中有多種資料型別,但是程式設計師並不會直接面對它們。)此外也可以是引用,通常它們也是一種標量型別。其中標量名稱以 "$" 開頭,陣列名以 "@" 開頭,而雜湊則以 "%" 開頭。

標量是 Perl 中最簡單的資料型別。每個標量都有唯一的值,或者是字串或者是引用。在必要的時候,字串和數字可以互相轉化。這常讓初學者覺得欣喜異常。看一下這個例子:

標量

$i = "hi there"; print 1+$i; # prints 1



其中標量 $i 的值是字串 "hi there",它對應的數值為 0。因此 1 + "hi there" 的值為 1,程式執行結果為 1。

不過這並不意味著 Perl 直譯器在對某個標量分別考慮其字串型別和數字型別。事實上,在中只是一個含有某個值的標量。如果在數值運算的語句中(如加法),這個標量值就轉化成數值形式;如果在字串操作語句中(例如列印),則以字串形式執行。但無論以什麼形式運算,該標量變數實質上只有一個值。

未定義的標量的值為 "undef"。如果在 C/C++/Java 程式中,您可以將其他值與 null 比較,但在 Perl 中卻不能拿任何東西來與 "undef" 做比較。可用這樣使用 defined() 函式:

Use of the 'defined()' function

$i = "hi there"; print $i if defined $i; # prints "hi there" undef $i; # set $i to be undef print $i if defined $i; # prints nothing



陣列實質上就是一組標量。如果需要,陣列大小可以自動改變,有點象 Java 中的 Vector 類。C 和 C++ 中沒有與 Perl 陣列型別相當的東西,但它們也有一些提供類似功能的庫(如 STL)。陣列的一個有趣特性在於,陣列的標量數值等於它的元素個數:

陣列中的元素個數

@a = ("hi there", "nowhere"); print scalar @a; # prints 2 push @a, "hello"; # add "hello" at the end print scalar @a; # prints 3



雜湊與陣列類似,但裡面的標量並不是按照位置排序的,而是透過另一個標量(必須是唯一值)來進行。例如一個用 social security number 作索引的名字列表就是一個雜湊。將某個鍵值插入到雜湊後,該雜湊會自動擴充套件。雜湊與 Java 中的 HashMap 和 Hashtable 類很相似。

引用型別其實也是標量,它們類似於 C 語言中的指標,能夠指向任何東西。這就允許 Perl 生成一個雜湊的陣列、陣列的雜湊、雜湊的雜湊、或是陣列的陣列(多維陣列)。有多種方法來獲得引用所指向的內容,或者直接使用引用的名字、或者使用 "->" 運算子。引用是一個涉及範圍非常廣的問題,可以參考 "perldoc perlref" 參考文件來獲得更多相關資訊。

C 和 C++ 只有一些固定型別的標量。當程式設計師要使用陣列或雜湊雜湊時,不得不去使用鉤子 (h) 或是 STL 等外部庫。

Java 中有相當於 Perl 裡陣列或雜湊功能的類庫,但它們在 Java 語言中並不是那麼直接。比如說要對雜湊上所有元素做操作所需要的時間大約是 Perl 的三倍。

對雜湊中所有元素進行操作的 Java 程式碼

import java.util.Enumeration; import java.util.Hashtable; Hashtable hi = new Hashtable(); // fill in hi's values // we can use an Iterator, still a lot of ty for (Enumeration enum = hi.elements(); enum.hasMoreElements();) { o = enum.nextElement(); // do something with o }



對雜湊中所有元素進行操作的 Perl 程式碼

# note that this even includes the definition and initialization of # the hash, and still is more compact than the Java code! %hash = { a => "hi", b => "hello" }; foreach (values %hash) { # do something with $_ }



Perl 的缺憾
Perl 缺少 C、C++ 和 Java 中的許多特性,但它畢竟是一門完全不同的語言。這幾種語言中甚至有許多特性是互相矛盾的。例如 Java 只支援單一繼承,而 C++ 則可以有多個父類。這種有衝突的情況下當然不可能繼承所有語言的特性,Perl 有它自己處理問題的方法。

由於 Perl 程式能夠被連線到 C 的庫中(事實上,這也就是 Perl 應用廣泛的原因之一),這就使得幾乎沒有任何 C 或 C++ 能做而 Perl 不能的事情。

與 C 和 C++ 相比而言,Perl 有時欠缺的是執行速度。這的確是一個問題,但是透過良好的程式設計演算法以及 Perl 內建函式的使用,就能夠克服這一缺點。

Perl 還不能直接使用 C 和 C++ 的庫。必須透過不同的模組和繫結,才能夠將這些庫中的常量以及函式功能轉化成適應 Perl 的樣子。這就會導致開發和程式執行的速度降低。但由於 CPAN 庫中釋出了大量這些方面的模組,因此這個問題並不是那麼難以解決,

在訓練程式設計技巧方面,Perl 並不象 C 和 C++ 那樣深入人心。它是一門年輕的語言,雖然很受歡迎,但並未被人們普遍接受。然而,大多數的 UNIX 系統上都了 Perl,且其他的作業系統也都支援 Perl。

Perl 支援單一繼承或多繼承、封裝以及多型,但這僅僅是透過外部模組或程式設計師的協同來實現的。也就是說,Perl 語言本身並沒有嚴格的物件導向程式設計規則,需要程式設計師自己來實現物件導向。這一點有好也有壞,這就要取決於程式設計師或專案本身了。

Perl 的執行緒以及統一字元編碼(Unicode)支援遠遠落後於 Java,也稍微次於 C/C++。Java 從最開始設計就支援執行緒和 Unicode,而 C/C++ 則比 Perl 擁有更多的時間來調整這方面的正確支援。在 Perl 中,對執行緒和 Unicode 的支援仍處於起步階段,但 5.6.0 之後的穩定版本釋出之後這一點將得到改觀。

Perl 的優勢
對於 C/C++/Java 程式設計師而言,Perl 在某些方面的優勢是無價的。例如正規表示式在 Perl 中的實現是輕而易舉的,但在 C、C++ 或 Java 中實現起來卻很麻煩。隱含的函式宣告、不嚴格的語法、以及象日用文件似的程式結構使得 Perl 更具吸引力。

Perl 並不適合於所有人。它需要讀者去適應,卻接受它的所有缺點和優點。我們並不是覺得 Perl 酷才採用它,而是因為它的確是一種非常好的工具。如果在解決某個問題時使用其他語言更合適,那麼就應該放棄 Perl。一個好程式設計師的手頭總是有好幾種有用的工具。

Perl 有一些小的不足,但那些不知疲憊的程式設計師會忽略掉這些缺點。如果的確需要執行緒和 Unicode 支援,或是嚴格的物件導向程式設計,那麼你只好根據這些需要來選擇其他更合適的語言了。

Perl 是一門通用的靈活的語言,可以象膠水一樣將其他許多不同的模型粘合起來。它能夠實現任何過程或函式的演算法。通常情況下,Perl 會大大減少開發的時間,因為它對某些常見的任務(例如對雜湊表中的所有元素做操作)只需要少量的程式碼。最重要的是,Perl 程式設計總是相當於一個有趣的學習過程。


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

相關文章