$純量值 (scalar) (數字,字串或參考值 [reference]) @陣列 %雜湊陣列 (關連陣列) *代表同一個變數名的所有類形。在第四版中它們常用來達到指標 (pointers)的功能,但現在在新版的 perl中這個角色已被參 考值 (reference)取代了。
雖然這些符號在某些場合下可省略,但建議你隨處都用。
有些其他的符號你可能會碰到但卻不是指定形態用的有:
<>這是用來從一個檔案把手 (filehandle)裡輸入一份記錄 \取某樣東西的參考值 (reference)
注意 <
FILE> 不是用來指定檔案的形態,亦非此把手的名字。它只是
將<>
這個運算子用在
FILE這個把手上。在純量的情境 (scalar context)
下,它自
FILE
把手一次讀入一行
(嗯,該說一筆記錄,參看
$/),在序列情境 (list context)下,則一次將 全部的內容讀
入。當對檔案使用開、關或其它 <>
之外的動作、或甚至只是提到把
手時,切記不要使用 <>
。下面的用法是正確的:eof(FH)
,
seek(FH, 0,2)
以及 ``copying from
STDIN to
FILE''。
use strict
下則是必須的)。但由一個簡單的字(不
能是一個已定義的副函數之名稱)所構成的索引值,和 =>
左端的運算子,都會被視為已納入引號了:
這些是和這些一樣的 ------------ --------------- $foo{line} $foo{"line"} bar => stuff "bar" => stuff
一個區塊末端的分號可有可無,一個序列的最後一個逗號亦同。良好的寫作風格 (參看perlstyle)中建議除了在單行程式 (one-liners)的情況外都將他們加上去:
if ($whoops) { exit 1 } @nums = (1, 2, 3);
if ($whoops) { exit 1; } @lines = ( "There Beren came from mountains cold", "And lost he wandered under leaves", );
$dir = (getpwnam($user))[7];
另一種方法就是在等號左端用 undef 作元素:
($dev, $ino, undef, undef, $uid, $gid) = stat($file);
$^W
變數 (在 perlvar中有說明)控制一個區塊在執行期 (runtime)的警告訊息:
{ local $^W = 0; #暫時關掉警告訊息 $a = $b + $c; #我知道這些變數可能未定義 }
注意,像所有的標點符號變數一樣,目前不能對 $^W
用 my,只能用 local()。
一個發展中的新 use warnings
編譯器指揮模組 (pragma)
提供了更精細的控制。好奇寶寶們應該翻翻 perl5-porters
郵件論壇的檔案庫以獲得更詳細的說明。
一個常犯的錯誤像是:
unlink $file || die "snafu";
這會被解譯器看成是:
unlink ($file || die "snafu");
要避免此問題,須加上括號或是用超低優先的 or
運算子:
(unlink $file) || die "snafu"; unlink $file or die "snafu";
這些“英文的”運算子 (and
, or
, xor
,及 not
)是刻意設計成較一般序列運算子低的優先順序,這就是為了解決前述的狀況。
另一個擁有出人意料的優先順序者為指數。它甚至高於負號,這使得 -2**2
變成負四而非正四。他同時也會“向右靠”(right-associate),意思是說
2**3**2
代表二的九次方,而不是八的平方。
$person = {}; #新的不具名雜湊陣列 $person->{AGE} = 24; #把 AGE欄的值設成 24 $person->{NAME} = "Nat"; #將 NAME欄設成 "Nat"
如果你要的是更嚴謹的寫法,看看 perltoot 。
下面是個方便的樣板,你也許希望在撰寫第一個模組時將他派上用場。記得要改名 字。
package Some::Module; #假設是 Some/Module.pm
use strict;
BEGIN { use Exporter (); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
##設定版本以備版本檢查之用;去掉 "#"號即可使用。 ## $VERSION = 1.00;
#如果有使用 RCS/CVS,那應該考慮將下一行保留, #但是小心兩位數版本編號可能造成的影響。 $VERSION = do{my@r=q$Revision: 1.1 $=~/\d+/g;sprintf '%d.'.'%02d'x$#r,@r};
@ISA = qw(Exporter); @EXPORT = qw(&func1 &func2 &func3); %EXPORT_TAGS = ( ); #例如: TAG => [ qw!name1 name2! ],
#整個包裹要輸出的全域變數(exported package globals)在此, #還有其他選擇要輸出的函數。 @EXPORT_OK = qw($Var1 %Hashit); } use vars @EXPORT_OK;
#沒有輸出的全域變數在此。 use vars qw( @more $stuff );
#起始包裹內的全域變數,首先是要輸出的那幾個。 $Var1 = ''; %Hashit = ();
#接下來是其他的 (還是能以 $Some::Module::stuff的方式擷取他們的值) $stuff = ''; @more = ();
#所有以檔案為範圍的變數名都 #必須在讓後面的函數使用前先創造出來。
#以檔案為範圍的變數名在此。 my $priv_var = ''; my %secret_hash = ();
#下面是一個以檔案為限的函數,當作一個閉包 #可以用 &$priv_func的方式呼叫它;但不能使用原型定義 my $priv_func = sub { #程式碼放在這裡 };
#不論是否要輸出,都記得要將你的函數造出來。 #別忘了在 {}括號間放些有趣的內容。
sub func1 {} #沒有定義原型 sub func2() {} #定原型為 void sub func3($$) {} #定原型為兩個純量值
#這個函數雖然未被輸出,但是可以被呼叫 sub func4(\%) {} #定原型為一個雜湊陣列的參考值
END { } #模組清潔大隊在此 (global destructor)
1; #模組必須傳回真值
kill()
沒有將任何程序交給訊號):
sub is_tainted { return ! eval { join('',@_), kill 0; 1; }; }
然而,此方法會觸發 -w
參數的警告訊息。目前並無任何不會觸發 -w
的方法可下偵測變數污染 --
就把警告訊息當成是在提醒你,該把所有可能被污染的資料給 ``漂白''
(untaint)。
【譯註:這裡所提的 ``被污染'' (tainted),指的是當使用 -T這個參數時,或當 perl程式做 setuid或 setgid模式執行時 (在 UNIX 下), perl會自動將有安 全顧慮的變數列為受污染, 也就是 tainted。除非先做解除污染 (untaint)處理,否則 perl不會容許受污染的變數做出可能危害系統的舉動。因此 CGI程式應盡可能地使用 -T這個參數以策安全。】
閉包 (closure)是個精確但又很難解釋的電腦名詞。在 Perl 裡面,閉包是以 匿名函數的形式來實現,具有持續參照位於該函數範圍之外的文字式變數值的能力。 這些外部的文字變數會神奇地保留它們在閉包函數最初定義時的值 (深連結)。
如果一個程式語言容許函數遞回另一個函數的話 (像 Perl 就是),閉包便具有意 義。要注意的是,有些語言雖提供匿名函數的功能,但卻無法正確處理閉包; Python 這個語言便是一例。如果要想多了解閉包的話,建議你去找本功能性程式 設計的教科書來看。Scheme這個語言不僅支援閉包,更鼓勵多加使用。
以下是個典型的產生函數的函數:
sub add_function_generator { return sub { shift + shift }; }
$add_sub = add_function_generator(); $sum = &$add_sub(4,5); # $sum現在是 9了
閉包用起來就像是個 函數樣板,其中保留了一些可以在稍後再填入的空格。
add_function_generator()
所遞回的匿名函數在技術上來講並不能算是一個閉包,
因為它沒有用到任何位在這個函數範圍之外的文字變數。
把上面這個例子和下面這個 make_adder()
函數對照一下,下面這個函數所遞回的匿名函數中使用了一個外部的文字變數。這種指名外部函數的作法需要由 Perl遞回一個適當的閉包,因此那個文字變數在匿名函數產生之時的值便永久地被鎖進閉
包裡。
sub make_adder { my $addpiece = shift; return sub { shift + $addpiece }; }
$f1 = make_adder(20); $f2 = make_adder(555);
這樣一來 &$f1($n)
永遠會是 20加上你傳進去的值 $n
,而
&$f2($n)
將
永遠會是 555加上你傳進去的值 $n
。$addpiece的值會在閉包中保留下來。
閉包在比較實際的場合中也常用得到,譬如當你想把一些程式碼傳入一個函數時:
my $line; timeout( 30, sub { $line = <STDIN> } );
如果要執行的程式碼當初是以字串的形式傳入的話,即 '$line =
<STDIN>'
,那麼 timeout()
這個假想的函數在回到該函數被呼叫時所在的範圍後便無法再擷取
$list
這個文字變數的值了。
my()
和 local()和閉包或
foreach()
迴圈變數及函數參數相互影響
所致。
從前【在舊版 perl的時代】大家寫程式的時候很容易因為這樣而不小心把變數值 給弄丟。但現在 perl提供了一些保護措施,因此犯這種錯的機率要小多了。
my $f = "foo"; sub T { while ($i++ < 3) { my $f = $f; $f .= "bar"; print $f, "\n" } } T; print "Finally $f\n";
有三個 ``bar''加進去的 $f
變數應該是一個新的 $f
(因為 my $f
在每個迴圈都應該創造一個新的區域變數)。然而,實際上並非如此。這個臭蟲在未來的 perl
版本中將會被修正。
func( \$some_scalar );
func( \$some_array ); func( [ 1 .. 10 ] );
func( \%some_hash ); func( { this => 10, that => 20 } );
func( \&some_func ); func( sub { $_[0] ** $_[1] } );
*FH
或 \*FH
(這叫 ``typeglobs'' --請參看 perldata
),或是使用舊名 FileHandle的 IO::File模組以動態方式來產生檔案把手亦可,這兩個模組都附在標準 Perl
版本內。
use Fcntl; use IO::File; my $fh = new IO::File $filename, O_WRONLY|O_APPEND; or die "Can't append to $filename: $!"; func($fh);
sub compare($$) { my ($val1, $regexp) = @_; my $retval = eval { $val =~ /$regexp/ }; die if $@; return $retval; }
$match = compare("old McDonald", q/d.*D/);
確定絕對不要用以下的寫法:
return eval "\$val =~ /$regexp/"; #錯誤
不然某人可以靠雙引號括起來的字串以及 eval 雙重解譯的本質而偷偷插入 shell指令來作壞事。例如:
$pattern_of_evil = 'danger ${ system("rm -rf * &") } danger';
eval "\$string =~ /$pattern_of_evil/";
想學非常非常聰明的方法的讀者可以參考 O'Reilly
出的 Mastering Regular
Expressions這本書,作者是 Jeffrey Friedl。其中第 273頁的
Build_MatchMany_Function()
特別的有趣。在 perlfaq2中可以找到有關本書
的資料。
call_a_lot(10, $some_obj, "methname") sub call_a_lot { my ($count, $widget, $trick) = @_; for (my $i = 0; $i < $count; $i++) { $widget->$trick(); } }
不然你就用個閉包 (closure) 把物件和它的方法以及其參數都包在一起:
my $whatnot = sub { $some_obj->obfuscate(@args) }; func($whatnot); sub func { my $code = shift; &$code(); }
你也可以研究
UNIVERSAL
類別中的 can()
方法 (附於標準 Perl
版本中)。
以下就是實作函數私有變數的程式:
BEGIN { my $counter = 42; sub prev_counter { return --$counter } sub next_counter { return $counter++ } }
prev_counter()
和 next_counter()
將會共用一個於編譯時起始的私有變數 $counter。
要宣告一個檔案私有(file-private)變數,你仍然得使用
my(),將它放在檔案開
頭處最外圍。假設現在是在 Pax.pm
這個檔案裡:
package Pax; my $started = scalar(localtime(time()));
sub begun { return $started }
當用 use Pax
或 require Pax
載入此模組時,這個變數就會被起始。不過它不會被資源回收,像其他出了有效範圍的變數那樣,因為 begun()
函數要用到它,但是沒有其他函數能擷取它。這個變數不能以 $Pax::started
的形式來擷取,因為它所存在的範圍與此包裹無關。它存在的範圍是這個檔案。可想見地,一個檔案裡可以放好幾個包裹,而所有的包裹都擷取同一個私有變數,但從另一個檔案中,即使是屬於同一個包裹(package),也不能取得它的值。
local($x)
將全域變數 $x
的原值存起來,並在此函數執行期間賦予一個新
值,此值可以從此函數所呼叫的其他函數裡看見。這整個步驟是在執行期間完成的,所以才叫做動態範圍選取 (dynamic
scoping)。local()影響的是全域變數,或者稱作包裹變數或動態變數。
my($x)
會創造一個只能在目前這個函數裡看得見的新變數。這個步驟是在編譯
期完成(compile-time),所以稱作文字式或是靜態範圍選取。my()總是作用在私
有變數,也稱作文字式變數或(不當地)稱作靜態(範圍選取)變數。
例如:
sub visible { print "var has value $var\n"; }
sub dynamic { local $var = 'local'; #授予 $var這個全域變數 visible(); #一個暫時的新值 }
sub lexical { my $var = 'private'; #新的私有變數,$var visible(); # (無法從此函數外看到) }
$var = 'global';
visible(); #會印出 global dynamic(); #會印出 local lexical(); #會印出 global
你可以發現在整個過程中 ``private''這個值都印不出來。那是因為
$var
的值只存在於lexical()
函數的區塊裡面,對它所呼叫的函數來說是看不到的。
總結來說,local()不會產生你想像中的私有、區域變數。它只是將一個暫時的值
授予一個全域變數。如果你要的是私有的變數,那麼 my()
才是你要找的。
參看 perlsub ,裡面有更詳盡的解說。
use strict "refs"
設定取掉。然後使用 ${'var'}
,而非 $var。
local $var = "global"; my $var = "lexical";
print "lexical is $var\n";
no strict 'refs'; print "global is ${'var'}\n";
如果你知道你所在的是哪一個包裹(package)的話,你可以直接指名,就像寫
$Some_Pack::var這樣。注意 $::var這個寫法 並非表示目前此包裹 (package)
內的動態變數 $var,而是指在 main
包裹(package)
裡的那個,就等價於 $main::var。直接指定包裹(package)的名稱雖然需要你把名字敲進程式碼
中,但是它執行起來比較快,也避免和
use strict "refs"
起衝突。
my()
創造的)式的深連結。然而,動態變數(也稱作全域(global),區域(local),或包裹(package)變數)在功效上是淺連結。就把這當作是少用它們的另一個理由好
了。請參考
閉包 (closure)是啥?
一節。
local()
會把 =
號右邊以序列情境來對待。而 <
FH>這個閱讀的
動作,就像 Perl裡許多的函數以及運算子一樣,會自動分辨出自己被呼叫時所在的情境並且採取適當的作法。一般來說,scalar()函數可以幫點忙。這個函數實際上對資料本身不會有任何作用(與一般所認為的相反),但是會告訴它所作用的函數要以對待純量值的方法來運算。如果那個函數沒有預先定義好碰到純量情境的行為,那麼它當然也幫不了你(例如 sort()
函數)。
然而,在以上這個例子 (local...)中,只要省略括號便可強制使用純量情境:
local($foo) = <FILE>; #錯誤 local($foo) = scalar(<FILE>); #可以 local $foo = <FILE>; #正確
其實在這個例子中,或許你該改用文字式變數 (lexical variables),不過會碰到 的問題跟上面一樣:
my($foo) = <FILE>; #錯誤 my $foo = <FILE>; #正確
如果你要覆蓋掉某個內建函數,例如說 open(),那你得將其定義從另一個模組載
入。參考
Overriding Builtin Functions。在
Class/Template裡面也有個範例。
如果你要覆蓋掉一個 Perl運算子,像是 +
或 **
,那你該使用 use
overload
這個編譯器指揮模組(pragma),其文件在 overload
。
如果你要覆蓋父類別 (parent class)裡的方法呼叫 (method calls),請看 Overridden Methods 。
&foo
的方式呼叫一個函數時,你等於讓這個函數擷取你目前 @_
裡面的值,同時也跳過原型定義 (prototypes)不用。這表式此函數抓到的是你當時的
@_,
而非一個空的
@_!雖然嚴格講起來它也不能算是個 bug (但是在
perlsub裡面是這麼說的)但在大部份情況下,這也算不上是個特別功能。
當你用 &foo()
的方式呼叫你的函數時,你會得到一個新的 @_,但是原型定義
仍然會被避開不用。
在一般情況下,你該用 foo()
的方式去呼叫函數。只有在編譯器已事先知道這
個函數的定義時,括號才能省略,譬如當這個函數所在的模組或包裹被 use
(但如果是被 require
則不行)時,或是透過先前提及或 use subs
宣告等
方法,讓編譯器先接觸到這個函數的定義。用這種呼叫方式,即使是當括號省掉時,
你都會得到一個乾淨的 @_,不會有任何不該出現的舊值殘留在上面。
下面這個簡單的 switch範例以模式對應為基礎。我們將要做的是對儲存在
$whatchamacallit
裡面的參考值 (reference)的類型進行多重條件的判斷。【譯註:$whatchamacallit
函意為 $what_you_might_call_it
】
SWITCH: for (ref $whatchamacallit) {
/^$/ && die '不是個參考值';
/SCALAR/ && do { print_scalar($$ref); last SWITCH; };
/ARRAY/ && do { print_array(@$ref); last SWITCH; };
/HASH/ && do { print_hash(%$ref); last SWITCH; };
/CODE/ && do { warn '無法印出函數的 ref'; last SWITCH; };
# DEFAULT
warn '跳過使用者自定的類型';
}
如果是要處理一些在 -w
之下觸發警告訊息的未定義變數,你可以使用一個處理元 (handler)來捕捉 __WARN__
這個虛擬信號 (pseudo-signal),範例如下:
$SIG{__WARN__} = sub {
for ( $_[0] ) {
/Use of uninitialized value/ && do { #將警訊提升為致命行動 die $_; };
#其它要捕捉的狀況可以寫在此。
warn $_; }
};
print
ref($object)
來找出 $object
這個物件是被歸到哪個類別底下。
另一個可能的原因是你在 Perl還不知道這個包裹 (package)存在之前便將某個
類別名稱在間接式物件語法中使用 (例如 find Guru "Samy"
)。最好是在開始使用你的包裹前,先確定都已經先把它們定義好了,如果你用的是 use
而非
require
的話,這件事便會自動處理好。不然的話,確定你使用箭頭式語法 (例如,Guru-
find(``Samy'')>)。在
perlobj
裡面對於物件的記號有詳盡解釋。
my $packname = ref bless [];
但如果是一個方法的話,而且印出的錯誤訊息中要包含呼叫此方法的物件 (不見得 就是把這個方法編譯進去的那個物件)則:
sub amethod { my $self = shift; my $class = ref($self) || $self; warn "我是被 $class這個物件所召喚"; }
#這是程式
=for nobody 這段就變成了註解
#程式繼續下去
=begin comment text
接下來此處所有
的文字都會被 所有人忽略
=end comment text
=cut
譯者:陳彥銘、蕭百齡
中譯版著作權所有:陳彥銘、蕭百齡及兩隻老虎工作室。 本中譯版遵守並使用與原文版相同的使用條款發行。