perlsub - Perl 子常式
宣告子常式
sub NAME; # A "forward" declaration.
sub NAME(PROTO); # ditto, but with prototypes
sub NAME : ATTRS; # with attributes
sub NAME(PROTO) : ATTRS; # with attributes and prototypes
sub NAME BLOCK # A declaration and a definition.
sub NAME(PROTO) BLOCK # ditto, but with prototypes
sub NAME : ATTRS BLOCK # with attributes
sub NAME(PROTO) : ATTRS BLOCK # with prototypes and attributes
use feature 'signatures';
sub NAME(SIG) BLOCK # with signature
sub NAME :ATTRS (SIG) BLOCK # with signature, attributes
sub NAME :prototype(PROTO) (SIG) BLOCK # with signature, prototype
在執行階段定義匿名子常式
$subref = sub BLOCK; # no proto
$subref = sub (PROTO) BLOCK; # with proto
$subref = sub : ATTRS BLOCK; # with attributes
$subref = sub (PROTO) : ATTRS BLOCK; # with proto and attributes
use feature 'signatures';
$subref = sub (SIG) BLOCK; # with signature
$subref = sub : ATTRS(SIG) BLOCK; # with signature, attributes
匯入子常式
use MODULE qw(NAME1 NAME2 NAME3);
呼叫子常式
NAME(LIST); # & is optional with parentheses.
NAME LIST; # Parentheses optional if predeclared/imported.
&NAME(LIST); # Circumvent prototypes.
&NAME; # Makes current @_ visible to called subroutine.
與許多語言一樣,Perl 提供使用者定義的子常式。這些子常式可以位於主程式中的任何位置,透過 do
、require
或 use
關鍵字從其他檔案載入,或使用 eval
或匿名子常式動態產生。您甚至可以使用包含其名稱或 CODE 參照的變數間接呼叫函式。
Perl 函式呼叫和傳回值的模型很簡單:所有函式都將一個單一的純量平面清單作為參數傳遞,而所有函式也同樣傳回一個單一的純量平面清單給呼叫函式。這些呼叫和傳回清單中的任何陣列或雜湊都將會摺疊,失去其身分,但您始終可以使用傳遞參照的方式來避免這種情況。呼叫和傳回清單都可以包含任意數量的純量元素。(通常沒有明確傳回語句的函式稱為子常式,但從 Perl 的角度來看,這並沒有什麼不同。)
在使用簽章的子常式中(請參閱以下的〈「簽章」〉),參數會指定到簽章所引入的詞彙變數中。在 Perl 的目前實作中,它們也可以透過 @_
陣列存取,就像非簽章子常式一樣,但現在不建議在使用簽章的子常式中以這種方式存取它們。
在不使用簽章的子常式中,傳入的任何參數都會顯示在 @_
陣列中。因此,如果您呼叫一個有兩個參數的函式,這些參數會儲存在 $_[0]
和 $_[1]
中。@_
陣列是一個區域陣列,但其元素是實際純量參數的別名。特別是,如果元素 $_[0]
已更新,則對應的參數也會更新(或如果它無法更新,則會發生錯誤)。如果參數是一個在函式呼叫時不存在的陣列或雜湊元素,則只有在修改該元素或引用該元素時(如果修改或引用),才會建立該元素。(Perl 的某些早期版本會建立元素,無論是否指定該元素。)指定到整個 @_
陣列會移除該別名,而且不會更新任何參數。
在不使用簽章時,Perl 沒有提供其他方法來建立命名形式參數。實際上,您所做的就是指定到 my()
清單中。未宣告為私有的變數是全域變數。有關建立私有變數的詳細資訊,請參閱〈「透過 my() 建立私有變數」〉和〈「透過 local() 建立暫時值」〉。若要為獨立套件(可能還有獨立檔案)中的函式集建立受保護的環境,請參閱 perlmod 中的〈「套件」〉。
return
語句可用於離開子常式,並選擇性地指定傳回值,該值會根據子常式呼叫的內容(清單、純量或空值)在適當的內容中評估。如果您未指定傳回值,則子常式會在清單內容中傳回一個空清單,在純量內容中傳回未定義值,或在空值內容中傳回空白。如果您傳回一個或多個集合(陣列和雜湊),這些集合會被壓縮成一個大的、無法區分的清單。
如果找不到 return
,且最後一個語句是一個表達式,則會傳回其值。如果最後一個語句是一個迴圈控制結構,例如 foreach
或 while
,則傳回值未指定。空的子常式會傳回空的清單。
範例
sub max {
my $max = shift(@_);
foreach $foo (@_) {
$max = $foo if $max < $foo;
}
return $max;
}
$bestday = max($mon,$tue,$wed,$thu,$fri);
範例
# get a line, combining continuation lines
# that start with whitespace
sub get_line {
$thisline = $lookahead; # global variables!
LINE: while (defined($lookahead = <STDIN>)) {
if ($lookahead =~ /^[ \t]/) {
$thisline .= $lookahead;
}
else {
last LINE;
}
}
return $thisline;
}
$lookahead = <STDIN>; # get first line
while (defined($line = get_line())) {
...
}
指定給私人變數清單以命名您的參數
sub maybeset {
my($key, $value) = @_;
$Foo{$key} = $value unless $Foo{$key};
}
由於指定會複製值,這也會產生將呼叫傳遞參考轉換成呼叫傳遞值的效應。否則,函式可以自由地對 @_
進行就地修改,並變更其呼叫者的值。
upcase_in($v1, $v2); # this changes $v1 and $v2
sub upcase_in {
for (@_) { tr/a-z/A-Z/ }
}
當然,您不能以這種方式修改常數。如果參數實際上是文字,而您嘗試變更它,您會發生(可能致命的)例外。例如,這不會起作用
upcase_in("frederick");
如果 upcase_in()
函式寫成傳回其參數的副本,而不是就地變更它們,會安全得多
($v3, $v4) = upcase($v1, $v2); # this doesn't change $v1 and $v2
sub upcase {
return unless defined wantarray; # void context, do nothing
my @parms = @_;
for (@parms) { tr/a-z/A-Z/ }
return wantarray ? @parms : $parms[0];
}
請注意這個(未建立原型)函式不關心它傳遞的是真正的純量還是陣列。Perl 將所有參數視為 @_
中一個龐大、長而扁平的參數清單。這是 Perl 簡單的參數傳遞樣式發揮作用的一個領域。即使我們提供像這樣的東西,upcase()
函式也可以在不變更 upcase()
定義的情況下完美運作
@newlist = upcase(@list1, @list2);
@newlist = upcase( split /:/, $var );
不過,不要試圖這樣做
(@x, @y) = upcase(@list1, @list2);
與扁平化的輸入參數清單一樣,傳回清單在傳回時也會扁平化。因此,您在此所做的所有事情都儲存在 @x
中,並使 @y
為空。請參閱 「傳遞參考」 以取得其他選項。
子常式可以使用明確的 &
前置詞呼叫。在現代 Perl 中,&
是可選的,如果子常式已預先宣告,則括號也是可選的。當僅命名子常式時,&
不是 可選的,例如當它用作 defined() 或 undef() 的參數時。當您想使用子常式名稱或使用 &$subref()
或 &{$subref}()
建構的參考進行間接子常式呼叫時,它也不是可選的,儘管 $subref->()
表示法解決了該問題。請參閱 perlref 以進一步了解所有這些內容。
子常式可以遞迴呼叫。如果使用 &
形式呼叫子常式,則參數清單是可選的,如果省略,則不會為子常式設定 @_
陣列:呼叫時的 @_
陣列對子常式可見。這是一種新使用者可能希望避免的效率機制。
&foo(1,2,3); # pass three arguments
foo(1,2,3); # the same
foo(); # pass a null list
&foo(); # the same
&foo; # foo() get current args, like foo(@_) !!
use strict 'subs';
foo; # like foo() iff sub foo predeclared, else
# a compile-time error
no strict 'subs';
foo; # like foo() iff sub foo predeclared, else
# a literal string "foo"
&
形式不僅使參數清單可選,而且還會停用對您提供的任何參數的原型檢查。這部分是出於歷史原因,部分是為了在您知道自己在做什麼的情況下提供一種方便的作弊方式。請參閱下面的 "原型"。
自 Perl 5.16.0 以來,__SUB__
令牌在 use feature 'current_sub'
和 use v5.16
下可用。它將評估為對當前正在執行的子常式的參考,這允許在不知道子常式名稱的情況下進行遞迴呼叫。
use v5.16;
my $factorial = sub {
my ($x) = @_;
return 1 if $x == 1;
return($x * __SUB__->( $x - 1 ) );
};
__SUB__
在正規表示式代碼塊(例如 /(?{...})/
)中的行為可能會發生變化。
名稱全部為大寫的子常式保留給 Perl 核心,而名稱全部為小寫的模組也是如此。全部大寫的子常式是一個鬆散的約定,表示它將由執行時間系統本身間接呼叫,通常是由於觸發的事件。名稱以左括號開頭的子常式也以相同的方式保留。以下是當前執行特殊預定義操作的一些子常式的清單。
AUTOLOAD
CLONE
、CLONE_SKIP
DESTROY
、DOES
BINMODE
、CLEAR
、CLOSE
、DELETE
、DESTROY
、EOF
、EXISTS
、EXTEND
、FETCH
、FETCHSIZE
、FILENO
、FIRSTKEY
、GETC
、NEXTKEY
、OPEN
、POP
、PRINT
、PRINTF
、PUSH
、READ
、READLINE
、SCALAR
、SEEK
、SHIFT
、SPLICE
、STORE
、STORESIZE
、TELL
、TIEARRAY
、TIEHANDLE
、TIEHASH
、TIESCALAR
、UNSHIFT
、UNTIE
、WRITE
BINMODE
、CLEARERR
、CLOSE
、EOF
、ERROR
、FDOPEN
、FILENO
、FILL
、FLUSH
、OPEN
、POPPED
、PUSHED
、READ
、SEEK
、SETLINEBUF
、SYSOPEN
、TELL
、UNREAD
、UTF8
、WRITE
VERSION
DB::DB
、DB::sub
、DB::lsub
、DB::goto
、DB::postponed
任何以 (
開頭
BEGIN
、UNITCHECK
、CHECK
、INIT
和 END
子例程不算是子例程,而是具名稱的特殊程式碼區塊,您可以在一個套件中擁有超過一個,而且您不能明確呼叫它們。請參閱 "BEGIN、UNITCHECK、CHECK、INIT 和 END" in perlmod
Perl 有一個設施,允許子例程的形式參數由特殊語法宣告,與子例程主體的程序碼分開。形式參數清單稱為簽章。
此設施必須在使用前啟用。它會自動由 use v5.36
(或更高版本)宣告啟用,或在目前的範圍中更直接地由 use feature 'signatures'
啟用。
簽章是子例程主體的一部分。通常子例程的主體只是一個帶大括號的程式碼區塊,但當使用簽章時,簽章是一個緊接在任何名稱或屬性之後的括號清單。
例如,
sub foo :lvalue ($x, $y = 1, @z) { .... }
簽章宣告在區塊中具有範圍的詞彙變數。當呼叫子例程時,簽章會先取得控制權。它會從傳遞的引數清單中填入簽章變數。如果引數清單不符合簽章的要求,則會擲回例外。當簽章處理完成時,控制權會傳遞給區塊。
位置參數透過在簽章中命名純量變數來處理。例如,
sub foo ($left, $right) {
return $left + $right;
}
會取得兩個位置參數,必須在執行時由兩個引數填入。預設情況下,參數是強制性的,而且不允許傳遞超過預期的引數。因此,上述程式碼等同於
sub foo {
die "Too many arguments for subroutine" unless @_ <= 2;
die "Too few arguments for subroutine" unless @_ >= 2;
my $left = $_[0];
my $right = $_[1];
return $left + $right;
}
可以透過從參數宣告中省略名稱的主要部分,只留下一個裸露的 $
標記,來忽略一個引數。例如,
sub foo ($first, $, $third) {
return "first=$first, third=$third";
}
雖然被忽略的引數不會進入變數,但呼叫者仍然必須強制傳遞它。
位置參數透過提供預設值(由 =
與參數名稱分隔)而變得是選用的
sub foo ($left, $right = 0) {
return $left + $right;
}
上述子常式可以呼叫一個或兩個引數。預設值表達式在呼叫子常式時評估,因此它可以提供不同呼叫的不同預設值。只有在呼叫中實際省略引數時才會評估。例如,
my $auto_id = 0;
sub foo ($thing, $id = $auto_id++) {
print "$thing has ID $id";
}
自動為呼叫者未提供 ID 的事物指定不同的順序 ID。預設值表達式也可以參照簽章中較早的參數,使一個參數的預設值根據較早的參數而變化。例如,
sub foo ($first_name, $surname, $nickname = $first_name) {
print "$first_name $surname is known as \"$nickname\"";
}
預設值表達式也可以使用 //=
算子撰寫,如果呼叫者省略值或提供的值為 undef
,則會評估並使用該表達式。
sub foo ($name //= "world") {
print "Hello, $name";
}
foo(undef); # will print "Hello, world"
類似地,||=
算子可用於提供預設表達式,以便在呼叫者提供假值時使用(並且請記住,遺失或 undef
值也是假的)。
sub foo ($x ||= 10) {
return 5 + $x;
}
可選參數可以像強制參數一樣沒有名稱。例如,
sub foo ($thing, $ = 1) {
print $thing;
}
即使不會將值儲存在任何地方,如果未提供對應的引數,仍會評估參數的預設值。這是因為評估它有重要的副作用。但是,它將在空內容中評估,因此如果它沒有副作用且不重要,如果啟用「空」警告類別,它將產生警告。如果無名可選參數的預設值不重要,則可以省略它,就像參數的名稱一樣
sub foo ($thing, $=) {
print $thing;
}
可選位置參數必須在所有強制位置參數之後。(如果沒有強制位置參數,則可選位置參數可以是簽章中的第一個項目。)如果有多個可選位置參數,並且提供的引數不足以填滿它們,它們將從左到右填滿。
在位置參數之後,可以在 slurpy 參數中擷取其他引數。最簡單的形式只是一個陣列變數
sub foo ($filter, @inputs) {
print $filter->($_) foreach @inputs;
}
在簽章中使用 slurpy 參數時,對於可以傳遞多少個引數沒有上限。slurpy 陣列參數可以像位置參數一樣沒有名稱,在這種情況下,它的唯一作用是關閉否則適用的引數限制
sub foo ($thing, @) {
print $thing;
}
slurpy 參數也可以是雜湊,在這種情況下,可用的引數被解釋為交替的鍵和值。必須有與值一樣多的鍵:如果有奇數個引數,則會擲出例外。鍵將被字串化,如果存在重複項,則後面的實例優先於前面的實例,就像標準雜湊建構一樣。
sub foo ($filter, %inputs) {
print $filter->($_, $inputs{$_}) foreach sort keys %inputs;
}
slurpy 雜湊參數可以像其他類型的參數一樣沒有名稱。即使它們沒有放入變數中,它仍然堅持它可用的引數數量必須是偶數。
sub foo ($thing, %) {
print $thing;
}
一個 slurpy 參數,陣列或雜湊,必須是簽章中的最後一個東西。它可以跟隨強制性和可選的位置參數;它也可以是簽章中的唯一東西。Slurpy 參數不能有預設值:如果沒有為它們提供參數,則會得到一個空陣列或空雜湊。
一個簽章可以完全為空,在這種情況下,它所做的只是檢查呼叫者沒有傳遞任何參數。
sub foo () {
return 123;
}
在 Perl 5.36 之前,這些被認為是實驗性的,並在 experimental::signatures
類別中發出警告。從 Perl 5.36 開始,這種情況不再發生,儘管警告類別仍然存在,以便與嘗試使用以下語句禁用它的程式碼向後相容:
no warnings 'experimental::signatures';
在當前的 Perl 實作中,當使用簽章時,參數仍然可以在特殊陣列變數 @_
中使用。但是,現在不建議透過這個陣列存取它們,並且不應該依賴於新編寫的程式碼,因為這個功能可能會在未來的版本中更改。嘗試存取 @_
陣列的程式碼會在編譯時在 experimental::args_array_with_signatures
類別中產生警告。
sub f ($x) {
# This line emits the warning seen below
print "Arguments are @_";
}
Use of @_ in join or string with signatured subroutine is
experimental at ...
存取參數的兩種方式之間存在差異:@_
別名 參數,但簽章變數會取得參數的副本。因此,寫入簽章變數只會更改該變數,而不會影響呼叫者的變數,但寫入 @_
的元素會修改呼叫者用於提供該參數的任何內容。
簽章和原型之間存在潛在的語法歧義(請參閱 "原型"),因為兩者都以開括號開頭,並且都可以在某些相同的地方出現,例如在子程式宣告中的名稱之後。由於歷史原因,當簽章未啟用時,此類上下文中任何開括號都會觸發非常寬容的原型解析。在這種情況下,大多數簽章將被解釋為原型,但不會是有效的原型。(有效的原型不能包含任何字母字元。)這將導致有點令人困惑的錯誤訊息。
為避免模稜兩可,當啟用簽章時,原型特殊語法會被停用。不會嘗試猜測括號組是要當成原型還是簽章。要在這種情況下為子常式提供原型,請使用原型屬性。例如,
sub foo :prototype($) { $_[0] }
子常式完全有可能同時具有原型和簽章。它們執行不同的工作:原型影響對子常式的呼叫編譯,而簽章在執行階段將引數值放入詞彙變數中。因此,您可以撰寫
sub foo :prototype($$) ($left, $right) {
return $left + $right;
}
原型屬性,以及任何其他屬性,都必須在簽章之前。簽章永遠緊接在子常式主體區塊之前。
概要
my $foo; # declare $foo lexically local
my (@wid, %get); # declare list of variables local
my $foo = "flurp"; # declare $foo lexical, and init it
my @oof = @bar; # declare @oof lexical, and init it
my $x : Foo = $y; # similar, with an attribute applied
警告:在 my
宣告中使用屬性清單仍在演進中。目前的語意和介面可能會變更。請參閱屬性和Attribute::Handlers。
my
算子宣告清單中的變數在詞彙上受限於封閉區塊、條件 (if
/unless
/elsif
/else
)、迴圈 (for
/foreach
/while
/until
/continue
)、子常式、eval
,或 do
/require
/use
的檔案。如果清單中有多個值,則必須將清單放在括號中。所有清單中的元素都必須是合法的左值。只有字母數字識別碼可以是詞彙範圍的,例如 $/
等神奇內建函式目前必須使用 local
來「在地化」。
與 local
算子建立的動態變數不同,使用 my
宣告的詞彙變數會完全對外隱藏,包括任何呼叫的子常式。如果從自身或其他地方呼叫同一個子常式,也是如此,每個呼叫都會取得自己的副本。
這並不表示在靜態封閉詞彙範圍中宣告的 my
變數會不可見。只有動態範圍會被切斷。例如,下方的 bumpx()
函式可以存取詞彙變數 $x,因為 my
和 sub
都發生在同一個範圍,可能是檔案範圍。
my $x = 10;
sub bumpx { $x++ }
不過,eval()
可以看到它正在評估的範圍的詞彙變數,只要這些名稱不會被 eval()
本身的宣告隱藏即可。請參閱perlref。
如果需要,可以指定 my() 的參數清單,讓您可以初始化變數。(如果沒有為特定變數提供初始化項,則會以未定義值建立該變數。)這通常用於命名子常式的輸入參數。範例
$arg = "fred"; # "global" variable
$n = cube_root(27);
print "$arg thinks the root is $n\n";
# outputs: fred thinks the root is 3
sub cube_root {
my $arg = shift; # name doesn't matter
$arg **= 1/3;
return $arg;
}
my
僅僅是您可能指派到的某個修飾詞。因此,當您指派到其引數清單中的變數時,my
也不會改變這些變數是視為純量或陣列。因此
my ($foo) = <STDIN>; # WRONG?
my @FOO = <STDIN>;
兩者都提供清單內容給右手邊,而
my $foo = <STDIN>;
提供純量內容。但下列僅宣告一個變數
my $foo, $bar = 1; # WRONG
這與
my $foo;
$bar = 1;
相同。宣告的變數直到目前的陳述式之後才會導入(不會顯示)。因此,
my $x = $x;
可用於使用舊 $x 的值初始化新的 $x,且表達式
my $x = 123 and $x == 123
除非舊 $x 碰巧具有值 123
,否則為假。
控制結構的詞彙範圍並非完全受限於分隔其受控區塊的大括弧;控制表達式也是該範圍的一部分。因此在迴圈中
while (my $line = <>) {
$line = lc $line;
} continue {
print $line;
}
$line 的範圍從其宣告延伸到迴圈結構的其餘部分(包括 continue
子句),但不會超出其範圍。類似地,在條件式中
if ((my $answer = <STDIN>) =~ /^yes$/i) {
user_agrees();
} elsif ($answer =~ /^no$/i) {
user_disagrees();
} else {
chomp $answer;
die "'$answer' is neither 'yes' nor 'no'";
}
$answer 的範圍從其宣告延伸到該條件式的其餘部分,包括任何 elsif
和 else
子句,但不會超出其範圍。請參閱 perlsyn 中的「簡單陳述式」,以取得有關具有修飾詞的陳述式中變數範圍的資訊。
foreach
迴圈預設使用 local
的方式動態設定其索引變數的範圍。不過,如果索引變數加上關鍵字 my
的前置詞,或如果範圍中已有一個具有相同名稱的詞彙,則會建立一個新的詞彙。因此在迴圈中
for my $i (1, 2, 3) {
some_function();
}
$i 的範圍延伸到迴圈的結尾,但不會超出其範圍,使 $i 的值無法在 some_function()
中存取。
有些使用者可能希望鼓勵使用詞彙範圍變數。作為捕捉隱含使用套件變數(始終為全域)的輔助工具,如果您說
use strict 'vars';
則從該處到封閉區塊結尾所提到的任何變數都必須參考詞彙變數、透過 our
或 use vars
預先宣告,否則必須使用套件名稱完全限定。否則會產生編譯錯誤。內部區塊可以使用 no strict 'vars'
來反對這一點。
my
具有編譯時間和執行時間效應。在編譯時間,編譯器會注意到它。這項功能的主要用途是平息 use strict 'vars'
,但對於產生如 perlref 中所述的閉包也至關重要。實際初始化會延遲到執行時間,因此它會在適當的時間執行,例如每次執行迴圈時。
使用 my
宣告的變數不屬於任何套件,因此絕不會使用套件名稱進行完全限定。特別是,您不得嘗試建立套件變數(或其他全域)詞法
my $pack::var; # ERROR! Illegal syntax
事實上,即使同名的詞法也處於可見狀態,動態變數(也稱為套件或全域變數)仍可以使用完全限定的 ::
表示法存取
package main;
local $x = 10;
my $x = 20;
print "$x and $::x\n";
這將列印出 20
和 10
。
您可以在檔案的最外層範圍宣告 my
變數,以對檔案外部世界隱藏任何此類識別碼。這與 C 的靜態變數在檔案層級使用時的精神類似。要使用子常式執行此操作,需要使用封閉(存取封閉詞法的匿名函式)。如果您想建立無法從該區塊外部呼叫的私人子常式,它可以宣告包含匿名子參考的詞法變數
my $secret_version = '1.001-beta';
my $secret_sub = sub { print $secret_version };
&$secret_sub();
只要模組內部沒有任何函式傳回參考,就沒有外部模組可以看到子常式,因為其名稱不在任何套件的符號表中。請記住,它並非實際上稱為 $some_pack::secret_version
或任何東西;它只是 $secret_version,未限定且無法限定。
不過,這不適用於物件方法;所有物件方法都必須位於某個套件的符號表中才能找到。請參閱 "perlref 中的「函式範本」,以了解如何解決這個問題。
有兩種方式可以在 Perl 5.10 中建立持續性私人變數。首先,您可以直接使用 state
功能。或者,如果您想與 5.10 之前的版本保持相容性,可以使用封閉。
從 Perl 5.10.0 開始,您可以使用 state
關鍵字取代 my
來宣告變數。不過,要這樣做,您必須事先啟用該功能,方法是使用 feature
實用程式,或對單行指令使用 -E
(請參閱 feature)。從 Perl 5.16 開始,CORE::state
形式不需要 feature
實用程式。
state
關鍵字會建立一個詞法變數(遵循與 my
相同的範圍規則),該變數會從一個子常式呼叫持續到下一個呼叫。如果狀態變數位於匿名子常式內部,則子常式的每個副本都會有自己的狀態變數副本。但是,狀態變數的值仍會在對同一匿名子常式的副本呼叫之間持續存在。(別忘了 sub { ... }
每次執行時都會建立一個新的子常式。)
例如,下列程式碼維護一個私人計數器,每次呼叫 gimme_another() 函式時都會遞增
use feature 'state';
sub gimme_another { state $x; return ++$x }
此範例使用匿名子常式來建立個別計數器
use feature 'state';
sub create_counter {
return sub { state $x; return ++$x }
}
此外,由於 $x
是詞彙的,因此無法由外部的任何 Perl 程式碼存取或修改。
與變數宣告結合時,對 state
變數的簡單指派(如 state $x = 42
)只會在第一次執行。當這些陳述式在後續時間評估時,指派會被忽略。目前尚未定義指派至 state
宣告的行為,其中指派的左側涉及任何括號。
詞彙變數詞彙(也稱為靜態)作用於其封閉區塊、eval
或 do
FILE,這並不表示它在函式內的工作方式類似於 C 靜態。它通常更類似於 C 自動,但具有隱含的垃圾回收。
與 C 或 C++ 中的局部變數不同,Perl 的詞彙變數並非一定會在作用域結束後才回收。如果更永久的東西仍然知道詞彙,它將會保留。只要有其他東西參照詞彙,該詞彙就不會被釋放,這是理所當然的。您不希望在完成使用記憶體之前就釋放記憶體,或在完成後保留記憶體。自動垃圾回收會為您處理這項工作。
這表示您可以傳回或儲存對詞彙變數的參照,而傳回對 C 自動的指標則是一個嚴重的錯誤。它也提供我們模擬 C 函式靜態的方法。以下是提供函式私有變數的機制,同時具有詞彙作用域和靜態生命週期。如果您確實想要建立類似於 C 靜態變數的東西,請將整個函式封閉在額外的區塊中,並將靜態變數放在函式外部,但放在區塊中。
{
my $secret_val = 0;
sub gimme_another {
return ++$secret_val;
}
}
# $secret_val now becomes unreachable by the outside
# world, but retains its value between calls to gimme_another
如果此函式透過 require
或 use
從個別檔案中來源,那麼這可能沒問題。如果它全部都在主程式中,您需要安排 my
提早執行,方法是將整個區塊放在主程式上方,或更有可能,僅在周圍放置 BEGIN
程式碼區塊,以確保在程式開始執行之前執行它
BEGIN {
my $secret_val = 0;
sub gimme_another {
return ++$secret_val;
}
}
請參閱 perlmod 中的 "BEGIN, UNITCHECK, CHECK, INIT and END",了解特殊觸發程式碼區塊 BEGIN
、UNITCHECK
、CHECK
、INIT
和 END
。
如果在最外層作用域(檔案作用域)宣告,則詞彙的工作方式有點類似於 C 檔案靜態。它們可供同一檔案中宣告在其下方的所有函式使用,但無法從檔案外部存取。此策略有時用於模組中,以建立整個模組都能看到的私有變數。
警告:一般來說,您應該使用 my
而不是 local
,因為它更快且更安全。例外情況包括全域標點符號變數、全域檔案處理常式和格式,以及 Perl 符號表的直接操作。local
主要用於變數的目前值必須對呼叫的子常式可見時。
概要
# localization of values
local $foo; # make $foo dynamically local
local (@wid, %get); # make list of variables local
local $foo = "flurp"; # make $foo dynamic, and init it
local @oof = @bar; # make @oof dynamic, and init it
local $hash{key} = "val"; # sets a local value for this hash entry
delete local $hash{key}; # delete this entry for the current block
local ($cond ? $v1 : $v2); # several types of lvalues support
# localization
# localization of symbols
local *FH; # localize $FH, @FH, %FH, &FH ...
local *merlyn = *randal; # now $merlyn is really $randal, plus
# @merlyn is really @randal, etc
local *merlyn = 'randal'; # SAME THING: promote 'randal' to *randal
local *merlyn = \$randal; # just alias $merlyn, not @merlyn etc
local
會修改其列出的變數,使其「局部」於封閉區塊、eval
或 do FILE
,以及從該區塊內呼叫的任何子常式。local
僅給予全域 (即套件) 變數暫時值。它不會建立局部變數。這稱為動態範圍。詞彙範圍則由 my
完成,其運作方式較類似於 C 的 auto 宣告。
某些類型的 lvalue 也能局部化:雜湊和陣列元素及區段、條件式 (前提是其結果總是可局部化的) 和符號參考。至於簡單變數,這會建立新的動態範圍值。
如果提供給 local
的變數或表達式超過一個,則必須將其置於括號中。此運算子會將這些變數的目前值儲存在其引數清單中的隱藏堆疊中,並在區塊、子常式或 eval 結束時將其還原。這表示呼叫的子常式也能參考局部變數,但不能參考全域變數。如果需要,可以指定引數清單,這允許您初始化局部變數。(如果未提供特定變數的初始化器,則會以未定義值建立該變數。)
由於 local
是執行時期運算子,因此每次執行迴圈時都會執行它。因此,在迴圈外局部化變數會更有效率。
local
只是 lvalue 表達式的修飾詞。當您指定給 local
化的變數時,local
不會變更其清單是視為純量還是陣列。因此
local($foo) = <STDIN>;
local @FOO = <STDIN>;
兩者都提供清單內容給右手邊,而
local $foo = <STDIN>;
會提供純量內容。
如果您局部化特殊變數,您會給予它新的值,但其魔法不會消失。這表示與此魔法相關的所有副作用仍會與局部化值一起運作。
此功能允許此類程式碼運作
# Read the whole contents of FILE in $slurp
{ local $/ = undef; $slurp = <FILE>; }
不過,請注意,這會限制某些值的局部化;例如,下列陳述會在 Perl 5.10.0 中產生錯誤嘗試修改唯讀值,因為 $1 變數是具有魔法且唯讀的
local $1 = 2;
預設純量變數是一個例外:從 Perl 5.14 開始,local($_)
將總是移除 $_ 中的所有魔法,以利在子常式中安全地重複使用 $_。
警告:目前,繫結陣列和雜湊的局部化無法如說明般運作。這將在 Perl 的未來版本中修正;在此同時,請避免依賴繫結陣列或雜湊的任何特定局部化行為的程式碼 (局部化個別元素仍然可以)。請參閱 "Localising Tied Arrays and Hashes Is Broken" in perl58delta 以取得更多詳細資料。
結構
local *name;
會在目前的套件中為全域變數 name
建立一個全新的符號表條目。這表示其全域變數槽中的所有變數 ($name、@name、%name、&name 和 name
檔案控制代碼) 都會動態重設。
這表示,在其他事項中,這些變數最終帶有的任何魔法都會在局部遺失。換句話說,local */
對輸入記錄分隔符號的內部值不會有任何影響。
花點時間說明當您在地化複合類型(例如陣列或雜湊元素)的成員時會發生什麼情況,這也是值得的。在這種情況下,元素會依名稱在地化。這表示當 local()
的範圍結束時,儲存的值會還原到 local()
中指定金鑰的雜湊元素,或 local()
中指定索引的陣列元素。如果該元素在 local()
生效時已刪除(例如雜湊的 delete()
或陣列的 shift()
),它將重新出現,可能會延伸陣列並以 undef
填入略過的元素。例如,如果您說
%hash = ( 'This' => 'is', 'a' => 'test' );
@ary = ( 0..5 );
{
local($ary[5]) = 6;
local($hash{'a'}) = 'drill';
while (my $e = pop(@ary)) {
print "$e . . .\n";
last unless $e > 3;
}
if (@ary) {
$hash{'only a'} = 'test';
delete $hash{'a'};
}
}
print join(' ', map { "$_ $hash{$_}" } sort keys %hash),".\n";
print "The array has ",scalar(@ary)," elements: ",
join(', ', map { defined $_ ? $_ : 'undef' } @ary),"\n";
Perl 會列印
6 . . .
4 . . .
3 . . .
This is a test only a test.
The array has 6 elements: 0, 1, 2, undef, undef, 5
local()
對複合類型中不存在的成員的行為可能會在未來發生變更。local()
對使用負索引指定的陣列元素的行為特別令人驚訝,而且很可能會變更。
您可以使用 delete local $array[$idx]
和 delete local $hash{key}
建構來刪除複合類型的目前區塊條目,並在區塊結束時還原它。它們會傳回在地化之前的陣列/雜湊值,這表示它們分別等於
do {
my $val = $array[$idx];
local $array[$idx];
delete $array[$idx];
$val
}
和
do {
my $val = $hash{key};
local $hash{key};
delete $hash{key};
$val
}
但 local
的範圍僅限於 do
區塊。它也接受切片。
my %hash = (
a => [ 7, 8, 9 ],
b => 1,
)
{
my $x = delete local $hash{a};
# $x is [ 7, 8, 9 ]
# %hash is (b => 1)
{
my @nums = delete local @$x[0, 2]
# @nums is (7, 9)
# $x is [ undef, 8 ]
$x[0] = 999; # will be erased when the scope ends
}
# $x is back to [ 7, 8, 9 ]
}
# %hash is back to its original state
此建構自 Perl v5.12 開始支援。
可以從子常式傳回可修改的值。為此,您必須宣告子常式傳回左值。
my $val;
sub canmod : lvalue {
$val; # or: return $val;
}
sub nomod {
$val;
}
canmod() = 5; # assigns to $val
nomod() = 5; # ERROR
子常式的純量/清單內容以及指定的一側的內容,其決定方式就好像子常式呼叫已替換為純量。例如,請考慮
data(2,3) = get_data(3,4);
這兩個子常式在此是在純量內容中呼叫,而在
(data(2,3)) = get_data(3,4);
和
(data(2),data(3)) = get_data(3,4);
中,所有子常式都是在清單內容中呼叫。
左值子常式很方便,但您必須記住,當與物件搭配使用時,它們可能會違反封裝。一般的變異器可以在設定它所保護的屬性之前檢查提供的引數,左值子常式則不能。如果您需要在儲存和擷取值時進行任何特殊處理,請考慮使用 CPAN 模組 Sentinel 或類似模組。
從 Perl 5.18 開始,你可以使用 my
或 state
宣告私有子常式。與狀態變數一樣,state
關鍵字僅在 use feature 'state'
或 use v5.10
以上版本中可用。
在 Perl 5.26 之前,詞彙子常式被視為實驗性質,且僅在 use feature 'lexical_subs'
實用程式中可用。除非停用「experimental::lexical_subs」警告類別,否則它們也會產生警告。
這些子常式僅在其宣告的區塊中可見,且僅在宣告之後。
# Include these two lines if your code is intended to run under Perl
# versions earlier than 5.26.
no warnings "experimental::lexical_subs";
use feature 'lexical_subs';
foo(); # calls the package/global subroutine
state sub foo {
foo(); # also calls the package subroutine
}
foo(); # calls "state" sub
my $ref = \&foo; # take a reference to "state" sub
my sub bar { ... }
bar(); # calls "my" sub
你無法(直接)撰寫遞迴詞彙子常式
# WRONG
my sub baz {
baz();
}
這個範例會失敗,因為 baz()
參照套件/全域子常式 baz
,而不是目前正在定義的詞彙子常式。
解決方案是使用 __SUB__
my sub baz {
__SUB__->(); # calls itself
}
可以預先宣告詞彙子常式。sub foo {...}
子常式定義語法會遵循任何先前的 my sub;
或 state sub;
宣告。然而,使用此方法定義遞迴子常式並非好主意
my sub baz; # predeclaration
sub baz { # define the "my" sub
baz(); # WRONG: calls itself, but leaks memory
}
就像 my $f; $f = sub { $f->() }
一樣,這個範例會造成記憶體外洩。名稱 baz
是子常式的參考,而子常式使用名稱 baz
;它們會讓彼此保持運作(請參閱 perlref 中的 「循環參考」)。
state sub
與 my sub
「state」子常式和「my」子常式之間的差異是什麼?每次執行進入宣告「my」子常式的區塊時,就會建立每個子常式的全新副本。「state」子常式會從包含區塊的一次執行持續到下一次執行。
因此,一般來說,「state」子常式較快。但如果你想要建立封閉,則需要「my」子常式
sub whatever {
my $x = shift;
my sub inner {
... do something with $x ...
}
inner();
}
在此範例中,當呼叫 whatever
時會建立新的 $x
,以及新的 inner
,它可以看到新的 $x
。「state」子常式只能看到從第一次呼叫 whatever
以來的 $x
。
our
子常式像 our $variable
,our sub
會建立一個詞彙別名給同名的封裝子常式。
這兩個主要用途是切換回在內部範圍內使用封裝子常式
sub foo { ... }
sub bar {
my sub foo { ... }
{
# need to use the outer foo here
our sub foo;
foo();
}
}
以及讓子常式在同一個範圍內對其他封裝可見
package MySneakyModule;
our sub do_something { ... }
sub do_something_with_caller {
package DB;
() = caller 1; # sets @DB::args
do_something(@args); # uses MySneakyModule::do_something
}
警告:本節所述的機制最初是模擬 Perl 舊版本中傳遞參照的唯一方式。雖然它在現代版本中仍然運作良好,但新的參照機制通常更容易使用。請參閱下方。
有時你不想傳遞陣列的值給子常式,而是傳遞陣列的名稱,以便子常式可以修改它的全域拷貝,而不是使用本機拷貝。在 Perl 中,你可以透過在名稱前面加上星號來參照特定名稱的所有物件:*foo
。這通常稱為「typeglob」,因為前面的星號可以視為變數和子常式等上的所有有趣前綴字元的萬用字元匹配。
評估時,typeglob 會產生一個標量值,代表該名稱的所有物件,包括任何檔案句柄、格式或子常式。指派時,它會讓所提到的名稱參照指派給它的任何 *
值。範例
sub doubleary {
local(*someary) = @_;
foreach $elem (@someary) {
$elem *= 2;
}
}
doubleary(*foo);
doubleary(*bar);
標量已經透過參照傳遞,因此你可以修改標量引數,而不需要使用此機制,方法是明確參照 $_[0]
等。你可以透過將所有元素傳遞為標量來修改陣列的所有元素,但你必須使用 *
機制 (或等效的參照機制) 來 push
、pop
或變更陣列的大小。傳遞 typeglob (或參照) 肯定會更快。
即使你不想修改陣列,此機制對於在單一 LIST 中傳遞多個陣列也很有用,因為通常 LIST 機制會合併所有陣列值,讓你無法提取個別陣列。有關 typeglob 的更多資訊,請參閱 perldata 中的「Typeglob 和檔案句柄」。
儘管有 my
,local
算子在三個地方仍然很出色。事實上,在這三個地方,你必須使用 local
而不是 my
。
你需要給全域變數一個暫時值,特別是 $_。
全域變數,例如 @ARGV
或標點符號變數,必須使用 local()
來「local」化。此區塊會讀取 /etc/motd,並將其分割成由等號行分隔的區塊,這些區塊會放在 @Fields
中。
{
local @ARGV = ("/etc/motd");
local $/ = undef;
local $_ = <>;
@Fields = split /^\s*=+\s*$/;
}
特別是,在任何指派給 $_ 的常式中,將 $_ local
化非常重要。注意 while
條件式中的隱式指派。
您需要建立一個本機檔案或目錄處理常式或一個本機函式。
需要自己檔案處理常式的函式必須對完整的型別 glob 使用 local()
。這可以用來建立新的符號表項目
sub ioqueue {
local (*READER, *WRITER); # not my!
pipe (READER, WRITER) or die "pipe: $!";
return (*READER, *WRITER);
}
($head, $tail) = ioqueue();
請參閱符號模組,了解建立匿名符號表項目的方法。
由於指派參考到型別 glob 會建立別名,因此這可以用來建立實際上是本機函式或至少是本機別名的東西。
{
local *grow = \&shrink; # only until this block exits
grow(); # really calls shrink()
move(); # if move() grow()s, it shrink()s too
}
grow(); # get the real grow() again
請參閱 perlref 中的「函式範本」,以進一步了解以這種方式透過名稱處理函式。
您只想暫時變更陣列或雜湊的一個元素。
您可以將聚合的只有一個元素local
化。通常這是在動態上完成的
{
local $SIG{INT} = 'IGNORE';
funct(); # uninterruptible
}
# interruptibility automatically restored here
但它也適用於以字彙宣告的聚合。
如果您想傳遞多於一個陣列或雜湊到函式中(或從函式中傳回它們)並保持它們的完整性,那麼您必須使用明確的傳遞參考。在執行此操作之前,您需要了解 perlref 中詳述的參考。否則,本節可能對您來說意義不大。
以下是幾個簡單的範例。首先,讓我們傳遞幾個陣列到函式中,並讓它pop
所有陣列,傳回它們所有前一個最後元素的新清單
@tailings = popmany ( \@w, \@x, \@y, \@z );
sub popmany {
my $aref;
my @retlist;
foreach $aref ( @_ ) {
push @retlist, pop @$aref;
}
return @retlist;
}
以下是如何撰寫傳回傳遞給它的所有雜湊中出現的鍵清單的函式
@common = inter( \%foo, \%bar, \%joe );
sub inter {
my ($k, $href, %seen); # locals
foreach $href (@_) {
while ( $k = each %$href ) {
$seen{$k}++;
}
}
return grep { $seen{$_} == @_ } keys %seen;
}
到目前為止,我們只使用正常的清單傳回機制。如果您想傳遞或傳回雜湊,會發生什麼事?嗯,如果您只使用其中一個,或者您不介意它們串接,那麼正常的呼叫慣例是可以的,儘管有點昂貴。
人們遇到問題的地方在這裡
(@w, @x) = func(@y, @z);
or
(%w, %x) = func(%y, %z);
該語法根本無法運作。它只設定 @w
或 %w
,並清除 @x
或 %x
。此外,函式並沒有傳遞到兩個單獨的陣列或雜湊:它在 @_
中取得一個長清單,一如往常。
如果您能安排讓所有人都透過參考來處理這個問題,那會是更簡潔的程式碼,雖然看起來不太好。以下是一個函式,它將兩個陣列參考作為引數,傳回兩個陣列元素,順序依它們包含的元素數量
($wref, $xref) = func(\@y, \@z);
print "@$wref has more than @$xref\n";
sub func {
my ($yref, $zref) = @_;
if (@$yref > @$zref) {
return ($yref, $zref);
} else {
return ($zref, $yref);
}
}
事實證明,您實際上也可以這樣做
(*w, *x) = func(\@y, \@z);
print "@w has more than @x\n";
sub func {
local (*y, *z) = @_;
if (@y > @z) {
return (\@y, \@z);
} else {
return (\@z, \@y);
}
}
在此,我們使用 typeglob 來進行符號表別名。不過,這有點微妙,而且如果你使用 my
變數,這也不會起作用,因為只有全域變數(即使偽裝成 local
)在符號表中。
如果你傳遞檔案句柄,通常你只需使用裸 typeglob,例如 *STDOUT
,但 typeglob 參考也可以使用。例如
splutter(\*STDOUT);
sub splutter {
my $fh = shift;
print $fh "her um well a hmmm\n";
}
$rec = get_rec(\*STDIN);
sub get_rec {
my $fh = shift;
return scalar <$fh>;
}
如果你打算產生新的檔案句柄,你可以這麼做。請注意,只傳回裸 *FH,而不是它的參考。
sub openit {
my $path = shift;
local *FH;
return open (FH, $path) ? *FH : undef;
}
Perl 使用函式原型支援非常有限的編譯時間引數檢查。這可以在 PROTO 區段中宣告,或使用 原型屬性 宣告。如果你宣告任一
sub mypush (\@@)
sub mypush :prototype(\@@)
則 mypush()
採用與 push()
完全相同的引數。
如果啟用子常式簽章(請參閱 "簽章"),則較短的 PROTO 語法不可用,因為它會與簽章衝突。在這種情況下,原型只能以屬性的形式宣告。
函式宣告必須在編譯時可見。原型只影響對函式的新式呼叫的詮釋,其中新式定義為不使用 &
字元。換句話說,如果你像呼叫內建函式一樣呼叫它,則它會像內建函式一樣運作。如果你像呼叫舊式子常式一樣呼叫它,則它會像舊式子常式一樣運作。自然而然地,從此規則中可以看出,原型對子常式參考(例如 \&foo
)或間接子常式呼叫(例如 &{$subref}
或 $subref->()
)沒有影響。
方法呼叫也不受原型影響,因為在編譯時無法確定要呼叫的函式,因為呼叫的確切程式碼取決於繼承。
由於此功能的目的主要是讓你定義像內建函式一樣運作的子常式,因此以下是其他一些函式的原型,其解析方式幾乎與對應的內建函式完全相同。
Declared as Called as
sub mylink ($$) mylink $old, $new
sub myvec ($$$) myvec $var, $offset, 1
sub myindex ($$;$) myindex &getstring, "substr"
sub mysyswrite ($$$;$) mysyswrite $buf, 0, length($buf) - $off, $off
sub myreverse (@) myreverse $x, $y, $z
sub myjoin ($@) myjoin ":", $x, $y, $z
sub mypop (\@) mypop @array
sub mysplice (\@$$@) mysplice @array, 0, 2, @pushme
sub mykeys (\[%@]) mykeys $hashref->%*
sub myopen (*;$) myopen HANDLE, $name
sub mypipe (**) mypipe READHANDLE, WRITEHANDLE
sub mygrep (&@) mygrep { /foo/ } $x, $y, $z
sub myrand (;$) myrand 42
sub mytime () mytime
任何反斜線原型字元都代表一個實際的引數,必須以該字元開頭(可選擇在前面加上 my
、our
或 local
),但 $
除外,它會接受任何標量 lvalue 表達式,例如 $foo = 7
或 my_function()->[0]
。傳遞為 @_
一部分的值將會是對子常式呼叫中給出的實際引數的參考,透過對該引數套用 \
來取得。
您可以使用 \[]
反斜線群組表示法來指定多於一種允許的引數類型。例如
sub myref (\[$@%&*])
將允許呼叫 myref() 為
myref $var
myref @array
myref %hash
myref &sub
myref *glob
而 myref() 的第一個引數將會是對標量、陣列、雜湊、程式碼或 glob 的參考。
未反斜線的原型字元具有特殊意義。任何未反斜線的 @
或 %
會吃掉所有剩餘的引數,並強制使用清單內容。由 $
表示的引數會強制使用標量內容。&
需要一個匿名子常式,如果傳遞為第一個引數,則不需要 sub
關鍵字或後續逗號。
*
允許子常式接受裸字、常數、標量表達式、類型 glob 或對該槽位中類型 glob 的參考。該值會以簡單標量或(在後兩種情況下)類型 glob 的參考提供給子常式。如果您希望總是將此類引數轉換為類型 glob 參考,請如下使用 Symbol::qualify_to_ref()
use Symbol 'qualify_to_ref';
sub foo (*) {
my $fh = qualify_to_ref(shift, caller);
...
}
+
原型是 $
的特殊替代,當給定文字陣列或雜湊變數時,它會像 \[@%]
一樣作用,但否則會強制對引數使用標量內容。這對於應該接受文字陣列或陣列參考作為引數的函式很有用
sub mypush (+@) {
my $aref = shift;
die "Not an array or arrayref" unless ref $aref eq 'ARRAY';
push @$aref, @_;
}
在使用 +
原型時,您的函式必須檢查引數是否為可接受的類型。
分號 (;
) 將強制引數與選用引數分開。在 @
或 %
之前是多餘的,它們會吞噬所有其他內容。
作為原型的最後一個字元,或在分號之前,您可以使用 _
取代 $
:如果未提供此引數,則會改用 $_
。
請注意表格中最後三個範例是如何被解析器特別處理的。mygrep()
被解析為真正的清單運算子,myrand()
被解析為真正的單元運算子,其單元優先權與 rand()
相同,而 mytime()
真的沒有參數,就像 time()
一樣。也就是說,如果你說
mytime +2;
你會得到 mytime() + 2
,而不是 mytime(2)
,這是它在沒有原型的情況下會被解析的方式。如果你想強制單元函數具有與清單運算子相同的優先權,請在原型的結尾新增 ;
sub mygetprotobynumber($;);
mygetprotobynumber $x > $y; # parsed as mygetprotobynumber($x > $y)
&
有趣的地方在於你可以用它產生新的語法,只要它在初始位置
sub try (&@) {
my($try,$catch) = @_;
eval { &$try };
if ($@) {
local $_ = $@;
&$catch;
}
}
sub catch (&) { $_[0] }
try {
die "phooey";
} catch {
/phooey/ and print "unphooey\n";
};
這樣會列印 "unphooey"
。(是的,仍然有未解決的問題與 @_
的可見性有關。我暫時忽略這個問題。(但請注意,如果我們讓 @_
具有詞彙範圍,那些匿名子常式可以像閉包一樣作用……(天啊,這聽起來有點像 Lisp 嗎?(別在意。))))
以下是 Perl grep
運算子的重新實作
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (@_) {
push(@result, $_) if &$code;
}
@result;
}
有些人會偏好完整的字母數字原型。字母數字已故意從原型中省略,其明確目的是在未來某天新增命名式正式參數。目前機制的首要目標是讓模組撰寫者為模組使用者提供更好的診斷。Larry 認為這個符號對 Perl 程式設計師來說很容易理解,而且它不會大幅干擾模組的主體,也不會讓它更難閱讀。行雜訊在視覺上被封裝成一個小藥丸,很容易吞嚥。
如果你嘗試在原型中使用字母數字順序,你會產生一個選用警告 - 「原型中有非法字元...」。不幸的是,較早版本的 Perl 允許使用原型,只要它的前綴是有效的原型即可。在大部分有問題的程式碼都已修正後,這個警告可能會在未來的 Perl 版本中升級為致命錯誤。
最好對新函數建立原型,而不是將原型追溯套用至舊函數。這是因為你必須特別小心清單與純量內容的不同列表的靜默施加。例如,如果你決定一個函數應該只接受一個參數,如下所示
sub func ($) {
my $n = shift;
print "you gave me $n\n";
}
而且有人使用陣列或傳回清單的表達式呼叫它
func(@foo);
func( $text =~ /\w+/g );
然後你會在它們的參數前面提供一個自動的 scalar
,這可能會有點令人驚訝。舊的 @foo
過去只會包含一個項目,但現在不會傳遞。相反地,func()
現在會傳遞一個 1
;也就是 @foo
中的元素數量。而 m//g
會在標量內容中呼叫,因此它會傳回布林結果,而不是字詞清單,並推進 pos($text)
。好痛!
如果子常式同時包含 PROTO 和 BLOCK,則在 BLOCK 完全定義後才會套用原型。這表示具有原型的遞迴函數必須預先宣告,才能讓原型生效,如下所示
sub foo($$);
sub foo($$) {
foo 1, 2;
}
當然,這一切都非常強大,而且應該適可而止地使用,才能讓世界變得更美好。
具有 ()
原型的函數是內嵌的潛在候選。如果最佳化和常數摺疊後的結果是常數或沒有其他參考的詞彙範圍標量,則會用它取代沒有 &
的函數呼叫。使用 &
進行的呼叫永遠不會內嵌。(請參閱 constant,了解宣告大部分常數的簡單方法。)
以下函數都會內嵌
sub pi () { 3.14159 } # Not exact, but close.
sub PI () { 4 * atan2 1, 1 } # As good as it gets,
# and it's inlined, too!
sub ST_DEV () { 0 }
sub ST_INO () { 1 }
sub FLAG_FOO () { 1 << 8 }
sub FLAG_BAR () { 1 << 9 }
sub FLAG_MASK () { FLAG_FOO | FLAG_BAR }
sub OPT_BAZ () { not (0x1B58 & FLAG_MASK) }
sub N () { int(OPT_BAZ) / 3 }
sub FOO_SET () { 1 if FLAG_MASK & FLAG_FOO }
sub FOO_SET2 () { if (FLAG_MASK & FLAG_FOO) { 1 } }
(請注意,最後一個範例在 Perl 5.20 及更早版本中並非總是會內嵌,因為它與包含內部範圍的子常式行為不一致。)你可以使用明確的 return
來取消內嵌
sub baz_val () {
if (OPT_BAZ) {
return 23;
}
else {
return 42;
}
}
sub bonk_val () { return 12345 }
如前所述,你也可以在 BEGIN 時動態宣告內嵌子常式,如果它們的主體包含沒有其他參考的詞彙範圍標量。這裡只有第一個範例會內嵌
BEGIN {
my $var = 1;
no strict 'refs';
*INLINED = sub () { $var };
}
BEGIN {
my $var = 1;
my $ref = \$var;
no strict 'refs';
*NOT_INLINED = sub () { $var };
}
這一點有一個不太明顯的警告(請參閱 [RT #79908]),也就是如果變數有可能被修改會發生什麼事。例如
BEGIN {
my $x = 10;
*FOO = sub () { $x };
$x++;
}
print FOO(); # printed 10 prior to 5.32.0
從 Perl 5.22 開始,這會產生不建議使用的警告,而從 Perl 5.32 開始,它會變成執行時期錯誤。之前變數會立即內嵌,而且會停止像一般的詞彙變數一樣運作;因此它會印出 10
,而不是 11
。
如果你仍然希望這樣的子常式內嵌(沒有警告),請確保變數不會在宣告的地方以外的內容中被修改。
# Fine, no warning
BEGIN {
my $x = 54321;
*INLINED = sub () { $x };
}
# Error
BEGIN {
my $x;
$x = 54321;
*ALSO_INLINED = sub () { $x };
}
Perl 5.22 也引入了實驗性的「const」屬性作為替代方案。(如果你想要使用它,請停用「experimental::const_attr」警告。)當套用至匿名子常式時,它會強制在評估 sub
表達式時呼叫子常式。回傳值會被擷取並轉換成常數子常式
my $x = 54321;
*INLINED = sub : const { $x };
$x++;
此範例中 INLINED
的傳回值永遠是 54321,不論之後如何修改 $x。你也可以在子程式中放入任何任意程式碼,它會立即執行,並以相同方式擷取其傳回值。
如果你真的想要一個具有 ()
原型的子常式,用以傳回詞彙變數,你可以透過新增一個明確的 return
來輕易強制它不要內嵌。
BEGIN {
my $x = 10;
*FOO = sub () { return $x };
$x++;
}
print FOO(); # prints 11
判斷一個子常式是否已內嵌的最簡單方法是使用 B::Deparse。考慮這個範例,兩個子常式傳回 1
,一個具有 ()
原型,導致它被內嵌,另一個則沒有(為清楚起見,已截斷 deparse 輸出)
$ perl -MO=Deparse -e 'sub ONE { 1 } if (ONE) { print ONE if ONE }'
sub ONE {
1;
}
if (ONE ) {
print ONE() if ONE ;
}
$ perl -MO=Deparse -e 'sub ONE () { 1 } if (ONE) { print ONE if ONE }'
sub ONE () { 1 }
do {
print 1
};
如果你重新定義一個符合內嵌資格的子常式,你會收到一個預設警告。你可以使用這個警告來判斷一個特定子常式是否被視為可內嵌,因為它與覆寫非內嵌子常式的警告不同
$ perl -e 'sub one () {1} sub one () {2}'
Constant subroutine one redefined at -e line 1.
$ perl -we 'sub one {1} sub one {2}'
Subroutine one redefined at -e line 1.
這個警告被視為足夠嚴重,不受 -w 開關(或其不存在)影響,因為先前編譯的函式呼叫仍會使用函式的舊值。如果你需要能夠重新定義子常式,你需要確保它沒有被內嵌,方法是移除 ()
原型(這會改變呼叫語意,因此請小心),或透過其他方式阻止內嵌機制,例如新增一個明確的 return
,如上所述
sub not_inlined () { return 23 }
許多內建函式都可以被覆寫,儘管這應該只在偶爾和有充分理由的情況下嘗試。這通常是由一個套件執行,試圖在非 Unix 系統上模擬遺失的內建功能。
覆寫只能透過在編譯時從模組匯入名稱來完成,一般的預先宣告是不夠的。然而,use subs
實用程式讓你實際上可以透過匯入語法預先宣告子常式,而這些名稱接著可以覆寫內建名稱
use subs 'chdir', 'chroot', 'chmod', 'chown';
chdir $somewhere;
sub chdir { ... }
若要明確地參照內建函數,請在內建函數名稱前面加上特殊套件限定詞 CORE::
。例如,說 CORE::open()
永遠是指內建的 open()
,即使目前的套件從其他地方匯入了另一個稱為 &open()
的子常式。儘管它看起來像一般的函數呼叫,但它不是:在這種情況下,CORE::
前綴是 Perl 語法的一部分,適用於任何關鍵字,無論 CORE
套件中是什麼。取得它的參考,也就是 \&CORE::open
,只適用於某些關鍵字。請參閱 CORE。
一般來說,函式庫模組不應將內建函數名稱(例如 open
或 chdir
)匯出為其預設 @EXPORT
清單的一部分,因為這些函數名稱可能會偷偷進入其他人的命名空間並意外地改變語意。相反地,如果模組將該名稱新增到 @EXPORT_OK
,則使用者可以明確地匯入該名稱,但不能隱含地匯入。也就是說,他們可以說
use Module 'open';
它將匯入 open
覆寫。但如果他們說
use Module;
他們將取得沒有覆寫的預設匯入。
前述用於覆寫內建函數的機制,非常刻意地限制在請求匯入的套件中。當您希望在不考慮命名空間邊界的任何地方覆寫內建函數時,有時可以適用第二種方法。這是透過將子常式匯入特殊命名空間 CORE::GLOBAL::
來實現的。以下是一個相當大膽地使用正規表示式取代 glob
算子的範例。
package REGlob;
require Exporter;
@ISA = 'Exporter';
@EXPORT_OK = 'glob';
sub import {
my $pkg = shift;
return unless @_;
my $sym = shift;
my $where = ($sym =~ s/^GLOBAL_// ? 'CORE::GLOBAL' : caller(0));
$pkg->export($where, $sym, @_);
}
sub glob {
my $pat = shift;
my @got;
if (opendir my $d, '.') {
@got = grep /$pat/, readdir $d;
closedir $d;
}
return @got;
}
1;
以下是它可以如何被(濫)用的方式
#use REGlob 'GLOBAL_glob'; # override glob() in ALL namespaces
package Foo;
use REGlob 'glob'; # override glob() in Foo:: only
print for <^[a-z_]+\.pm\$>; # show all pragmatic modules
最初的註解顯示了一個虛構的,甚至危險的範例。透過在全域覆寫 glob
,您將強制對 每個 命名空間使用 glob
算子的新(且顛覆性的)行為,而沒有擁有這些命名空間的模組的完全認知或合作。當然,這應該非常小心地執行,如果必須執行的話。
上述的 REGlob
範例並未實作完全支援以乾淨地覆寫 Perl 的 glob
算子所需的內容。內建的 glob
具有不同的行為,具體取決於它出現在標量或清單內容中,但我們的 REGlob
沒有。確實,許多 Perl 內建函數具有這種內容敏感的行為,而這些行為必須由適當地撰寫的覆寫來充分支援。若要取得覆寫 glob
的完整功能範例,請研究標準函式庫中 File::DosGlob
的實作。
當您覆寫內建函式時,您的替換函式應與內建原生語法一致(如果可能的話)。您可以使用適當的原型來達成此目的。若要取得可覆寫內建函式的原型,請使用參數為 "CORE::內建函式名稱"
的 prototype
函式(請參閱 perlfunc 中的「prototype」)。
但請注意,有些內建函式無法透過原型表達其語法(例如 system
或 chomp
)。如果您覆寫這些函式,您將無法完全模擬其原始語法。
內建函式 do
、require
和 glob
也可被覆寫,但由於特殊的魔法,其原始語法會被保留,而且您不必為其替換函式定義原型。(不過,您無法覆寫 do BLOCK
語法)。
require
具有特殊的附加黑魔法:如果您呼叫您的 require
替換函式為 require Foo::Bar
,它實際上會在 @_ 中接收參數 "Foo/Bar.pm"
。請參閱 perlfunc 中的「require」。
而且,正如您從前一個範例中所看到的,如果您覆寫 glob
,<*>
glob 運算子也會被覆寫。
類似地,覆寫 readline
函式也會覆寫等效的 I/O 運算子 <FILEHANDLE>
。此外,覆寫 readpipe
也會覆寫運算子 ``
和 qx//
。
最後,有些內建函式(例如 exists
或 grep
)無法被覆寫。
如果您呼叫未定義的子常式,通常會立即收到致命錯誤,抱怨子常式不存在。(同樣地,當方法不存在於類別套件的任何基底類別中時,也會發生這種情況。)但是,如果在用於尋找原始子常式的套件或套件中定義了 AUTOLOAD
子常式,則會呼叫該 AUTOLOAD
子常式,並傳遞原本會傳遞給原始子常式的參數。原始子常式的完全限定名稱會神奇地出現在與 AUTOLOAD
常式同一個套件的 $AUTOLOAD 全域變數中。名稱不會作為普通參數傳遞,因為,呃,好吧,就是因為這樣,所以這樣。(作為例外,對不存在的 import
或 unimport
方法的呼叫會被跳過。此外,如果 AUTOLOAD 子常式是 XSUB,則還有其他方法可以擷取子常式名稱。請參閱 perlguts 中的「使用 XSUB 自動載入」 以取得詳細資訊。)
許多 AUTOLOAD
常式會使用 eval() 載入請求子常式的定義,然後使用一種特殊的 goto() 形式執行該子常式,該形式會清除 AUTOLOAD
常式的堆疊框架,不留痕跡。(例如,請參閱 AutoLoader 中記錄的標準模組的原始碼。)但是,AUTOLOAD
常式也可以模擬常式,而從不定義它。例如,假設一個未定義的函式應該使用這些參數呼叫 system
。您所要做的就是
sub AUTOLOAD {
our $AUTOLOAD; # keep 'use strict' happy
my $program = $AUTOLOAD;
$program =~ s/.*:://;
system($program, @_);
}
date();
who();
ls('-l');
事實上,如果您預先宣告您想要以這種方式呼叫的函式,您甚至不需要括號
use subs qw(date who ls);
date;
who;
ls '-l';
一個更完整的範例是 CPAN 上的 Shell 模組,它可以將未定義的子常式呼叫視為對外部程式碼的呼叫。
有許多機制可協助模組寫作者將模組分割成可自動載入的檔案。請參閱 AutoLoader 和 AutoSplit 中所述的標準 AutoLoader 模組,SelfLoader 中的標準 SelfLoader 模組,以及 perlxs 中關於將 C 函式新增到 Perl 程式碼的文件。
子常式宣告或定義可具有與其關聯的屬性清單。如果存在此類屬性清單,則會在空白或冒號界線上將其分解,並視為已看到 use attributes
。請參閱 attributes 以取得目前支援哪些屬性的詳細資訊。與已淘汰的 use attrs
的限制不同,sub : ATTRLIST
語法用於將屬性與預先宣告關聯,而不仅仅是與子常式定義關聯。
屬性必須有效,作為簡單的識別名稱(除了 '_' 字元之外,沒有任何標點符號)。它們可以附加參數清單,僅檢查其括號 ('(',')') 是否正確巢狀。
有效語法的範例(即使屬性未知)
sub fnord (&\%) : switch(10,foo(7,3)) : expensive;
sub plugh () : Ugly('\(") :Bad;
sub xyzzy : _5x5 { ... }
無效語法的範例
sub fnord : switch(10,foo(); # ()-string not balanced
sub snoid : Ugly('('); # ()-string not balanced
sub xyzzy : 5x5; # "5x5" not a valid identifier
sub plugh : Y2::north; # "Y2::north" not a simple identifier
sub snurt : foo + bar; # "+" not a colon or space
屬性清單作為常數字串清單傳遞給將它們與子常式關聯的程式碼。特別是,上述有效語法的第二個範例目前在解析和呼叫方式方面看起來像這樣
use attributes __PACKAGE__, \&plugh, q[Ugly('\(")], 'Bad';
有關屬性清單及其操作的更多詳細資訊,請參閱 attributes 和 Attribute::Handlers。
請參閱 "perlref 中的「函式範本」 以取得更多關於參照和封閉的資訊。如果您想了解如何從 Perl 呼叫 C 子常式,請參閱 perlxs。如果您想了解如何從 C 呼叫 Perl 子常式,請參閱 perlembed。請參閱 perlmod 以了解如何在個別檔案中綑綁函式。請參閱 perlmodlib 以了解系統中標準的函式庫模組為何。請參閱 perlootut 以了解如何進行物件方法呼叫。