標籤:
變數範圍
(一)包變數
$x = 1
這裡,$x是個包變數。關於包變數,有2件重要的事情要瞭解:
1)假如沒有其他申明,變數就是包變數;
2)包變數總是全域的。
全域意味著包變數在每個程式裡總可訪問到。在你定義了$x=1後,程式的任何其他部分,甚至在其他檔案裡定義的子程式,都能影響和修改$x的值。這點毫無例外;包變數總是全域的。
包變數被歸類到族(叫做packages)。每個包變數的名字包括2部分。這2部分類似於變數自己的名字和族名。假如喜歡,你可以稱呼美國副總統為‘AL‘,但對其全名‘Al Gore‘來講,這確實太短。類似的,$x有一個全名,其形式如$main::x。主要部分是包限定詞,類似於‘Al Gore‘裡的‘Gore‘部分。Al Gore和AL Capone是不同的人,儘管他們都叫做‘AL‘。同樣的,$Gore::Al和$Capone::Al是不同的變數,$main::x and $DBI::x也是不同的變數。
允許指定變數名的包部分,假如這樣做,perl會明確知道你指的是哪個變數。但為簡潔起見,通常不會指定變數的包限定詞。
1.當前包
假如你僅說$x,perl假設你指的是當前包裡的變數$x。什麼是當前包?通常情況下是指main,但你可以改變當前包,在程式裡這樣寫:
package Mypackage;
從這點開始,當前包就是Mypackage。當前包做的唯一事情是,在你沒有指定包名時,它影響對包變數的解釋。假如當前包是Mypackage,這時$x實際指$Mypackage::x。假如當前包是main,這時$x實際指$main::x。
假如你在編寫一個模組,假設模組名稱是MyModule,你可能會將如下行放在模組檔案的頂部:
package MyModule;
從這裡開始,你在模組檔案裡使用的所有包變數將位於包MyModule裡,你可以非常放心,這些變數不會與程式其他部分的變數衝突。不必擔心你和DBI的作者是否會使用同一個變數$x,因為變數會被區分開,一個是$MyModule::x,另一個是$DBI::x。
記住包變數總是全域的。即使你不在DBI包裡,甚至即使你從來沒聽說過DBI包,也沒什麼能阻止你讀取或寫入$DBI::errstr。你不必做任何特殊事情。$DBI::errstr象所有包變數一樣,是個全域變數,它可全域訪問到;你要做的唯一事是用全名來擷取它。甚至可以這樣寫:
package DBI;
$errstr = ‘Ha ha Tim!‘;
這樣就修改了$DBI::errstr。
2.關於包變數的補充
1)若包名為空白,則等同於main。所以對任何x來講,$::x等同於$main::x。
2)某些變數總是強迫位於main包裡。例如,假如你提及%ENV,perl假設你指%main::ENV,即使當前包不是main。假如你想要%Fred::ENV,就必須明確申明,即使當前包是Fred。其他特殊的變數包括INC,所有的單標點符號變數名例如$_,$$, @ARGV,以及STDIN, STDOUT, 和STDERR。
3)包名,而非變數名,可以包含::。你可以命名變數為$DBD::Oracle::x。這意味著變數x位於包DBD::Oracle裡;它與包DBD沒有任何關係。Isaac Newton與 Olivia Newton-John無關,並且Newton::Isaac也與Newton::John::Olivia無關。儘管它們看起來都以Newton開頭,但實際上這有點欺騙性。Newton::John::Olivia位於Newton::John包裡,而不是Newton裡。
這是你要瞭解的關於包變數的所有東西。
包變數是全域的,這意味著它是危險的,因為不能保證某個人不會在背後來破壞它們。在Perl 4之前,所有的變數都是包變數,這點令人不安。所以perl 5增加了新的非全域變數。
(二)詞法變數
Perl裡其他類型的變數叫做詞法變數或私人變數,因為它們是私人的。它們有時也叫做my變數,因為總是以my來申明它們。你也許很想叫它們local變數,因為它們的影響被局限在程式的一小部分裡。但不要那樣做,因為其他人可能以為你在談論perl的local操作符。當你想要一個local變數時,請想到my,而不是local。
my $x;
如上申明建立了一個新變數,叫做x,它對程式的大部分完全不可訪問,大部分是指在申明變數的代碼塊之外的地方。這個塊叫做變數範圍。假如變數未在任何塊裡申明,它的範圍就是從申明它的地方開始,到檔案的結尾。
也可以申明和初始化一個my變數,這樣寫:
my $x = 119;
也能同時申明幾個變數:
my ($x, $y, $z, @args) = (5, 23, @_);
如下樣本展示私人變數在哪裡會很有用。考慮這個子程式:
sub print_report {
@employee_list = @_;
foreach $employee (@employee_list) {
$salary = lookup_salary($employee);
print_partial_report($employee, $salary);
}
}
假如lookup_salary碰巧也使用了名為$employee的變數,這個變數名和print_report使用的一樣,事情就會變得糟糕。負責print_report和lookup_salary的2個程式員必須協作,以確保他們不使用相同的變數名。這點是痛苦的。事實上,即使是在一個中等大小的項目裡,這點也令人無法忍受。
解決方案是:使用my變數:
sub print_report {
my @employee_list = @_;
foreach my $employee (@employee_list) {
my $salary = lookup_salary($employee);
print_partial_report($employee, $salary);
}
}
my @employee_list建立一個新的陣列變數,它在print_report函數之外完全不可訪問。 my $employee建立一個標量變數,它在foreach迴圈外完全不可訪問。你不必擔心程式裡的其他函數會破壞這些變數,因為它們沒這個能力;它們不知道這些變數在哪裡,因為變數的名字在my申明的範圍之外有不同的意義。my變數有時也叫做詞法變數,因為它們的範圍僅僅依賴於程式文本自身,不依賴於執行細節,例如以什麼順序來執行什麼。僅通過檢查原始碼,就可以弄清楚它們的範圍。無論何時你看到一個變數,請在同一代碼塊裡的先前位置找my申明。假如找到了,你可以確認該變數在代碼塊之外不可訪問。假如在最內層的代碼塊裡沒有找到my聲明,那就到上一層塊裡找,依此類推,直到找到為止。假如任何地方都沒有my申明,那麼這個變數是個包變數。
my變數並非包變數。它們不是包的一部分,並且沒有包限定詞。當前包不會因為變數的解釋方式而受到影響。如下是個例子:
my $x = 17;
package A;
$x = 12;
package B;
$x = 20;
# $x is now 20.
# $A::x and $B::x are still undefined
在頂部的my $x=17的申明建立了一個新的名為x的詞法變數,它的範圍持續到檔案結尾。$x的新意義覆蓋了預設的意義,預設意義是指$x是當前包的一個包變數。
package A改變了當前包,但因為$x指向了詞法變數,而不是包變數,$x=12不會對$A::x有任何影響。類似的,在package B後,$x=20修改了詞法變數,而不是任何包變數。
在檔案結尾,詞法變數$x值為20,包變數$main::x, $A::x, 和$B::x仍未定義。假如要使用它們,仍須通過全名來訪問它們。
必須記住的是:
包變數是全域變數。
對私人變數,必須使用my申明。
1. local和my
幾乎每個人都知道,有個local函數,它對本地變數有些影響。它到底是什麼呢,與my有關係嗎?答案簡單而奇怪:
my建立本地變數,然而local不這樣。
首先,local $x實際做的事是:它儲存包變數$x的當前值在一個安全的地方,然後用一個新值替換它,假如沒有指定新值,就使用undef代替。當控制離開當前塊時,它也會恢複$x的舊值。它影響的是包變數,這個包變數擷取了本地值。但包變數總是全域的,local申明的包變數亦無例外。為了顯示其區別,請看這個:
$lo = ‘global‘;
$m = ‘global‘;
A();
sub A {
local $lo = ‘AAA‘;
my $m = ‘AAA‘;
B();
}
sub B {
print "B ", ($lo eq ‘AAA‘ ? ‘can‘ : ‘cannot‘) ,
" see the value of lo set by A.\n";
print "B ", ($m eq ‘AAA‘ ? ‘can‘ : ‘cannot‘) ,
" see the value of m set by A.\n";
}
結果會列印:
B can see the value of lo set by A.
B cannot see the value of m set by A.
發生了什嗎?在A函數裡的local申明,給包變數$lo賦予了一個新的臨時值AAA。舊值global會被儲存起來,直到A返回,但在這點之前,A調用了B。B訪問$lo的內容沒有問題,因為$lo是包變數,包變數總是全域可見的,所以它能見到A設定的AAA值。
與之對照的是,my申明建立了一個新的詞法範圍的變數叫做$x,它僅僅在A函數裡可見。在A之外,$m保留它的舊意義:它指向包變數$m;其值仍是global。這是B所見到的變數。它不會見到AAA值,因為那個變數是個詞法變數,僅僅存在於A裡。
2.local有何好處?
因為local實際不建立本地變數,它並非很有用。在上述樣本裡,假如B碰巧修改了$lo的值,這樣A設定的值就被覆蓋掉。這點我們確實不想它發生。我們希望每個函數有它自己的變數,它們不會被其他函數觸及到。這就是my所能做到的。
為什麼會有local呢?答案90%是因為曆史原因。早期的perl版本僅有全域變數。local非常容易執行,它作為對本地變數問題的局部解決方案而增加到perl4裡。後來在perl5裡做了更多工作,真正的本地變數被添加到該語言裡。不同於local,新的本地變數以單詞my來申明。之所以選擇my,是因為它暗示著隱私,也因為它非常短;短小的單詞也許會鼓勵你使用它來代替local。my也比local運行更快。
何時使用my,以及何時使用local呢?
答案很簡單:總使用my,絕不要使用local。
3.my變數的其他特性
每次控制抵達my申明,perl建立一個新的,初始的變數。例如,如下代碼列印x=1 50次:
for (1 .. 50) {
my $x;
$x++;
print "x=$x\n";
}
每次遍曆迴圈時,你得到一個新的$x,其值初始化為undef。
假如申明在迴圈之外,控制只會通過它一次,所以這裡就只有一個變數:
{ my $x;
for (1 .. 50) {
$x++;
print "x=$x\n";
}
}
這會列印x=1, x=2, x=3, ... x=50.
可以利用這點來玩個遊戲。假設有個函數,它需要從一個調用到下一個調用中記住某個值。例如,考慮一個隨機數產生器。典型的隨機數產生器(類似perl的rand函數)有個種子在其中。種子就是一個數。當請求隨機數產生器來擷取隨機數時,該函數基於種子來執行某些運算,然後返回結果。它也會儲存該結果,並將其作為下一次函數調用的種子。
如下是典型代碼:
$seed = 1;
sub my_rand {
$seed = int(($seed * 1103515245 + 12345) / 65536) % 32768;
return $seed;
}
典型的輸出:
16838
14666
10953
11665
7451
26316
27974
27550
這裡有個問題,因為$seed是個全域變數,那意味著我們必須擔憂某個人可能無意中修改它。或者別人有意破壞它,這就影響了程式的結果。假如該函數用於賭博程式中,並且別人破壞了它的隨機數產生器,你想想會發生什嗎?
但是我們不能在函數裡申明$seed為my變數:
sub my_rand {
my $seed;
$seed = int(($seed * 1103515245 + 12345) / 65536) % 32768;
return $seed;
}
假如這樣做了,當每次調用my_rand時,$seed會被初始化為undef。我們實際需要的是,在每次調用my_rand時,$seed會保留其值。
如下是解決方案:
{ my $seed = 1;
sub my_rand {
$seed = int(($seed * 1103515245 + 12345) / 65536) % 32768;
return $seed;
}
}
申明在函數之外,所以它僅僅在程式編譯時間執行一次,而不是每次函數調用時都執行。但它是個my變數,並且其位於代碼塊裡,所以它僅對塊裡的代碼可見。my_rand是塊裡的唯一其他東西,所以$seed僅可被my_rand函數訪問。
4.關於my變數的補充
1)不能對以標點符號命名的變數使用my申明,例如_, @_, 或$$。也不能對後台引用的變數$1, $2, ... 使用my申明。my的作者認為那會把事情搞糟。
2)明顯的,不能申明my $DBI::errstr,因為那會有衝突:它認為包變數$DBI::errstr是個詞法變數。但是可以申明local $DBI::errstr;它儲存local $DBI::errstr的當前值,並在代碼塊結束處恢複它。
3)在perl 5.004及更高版本裡,可以這樣寫:
foreach my $i (@list) {
它限制$i在迴圈範圍內。類似的,
for (my $i=0; $i<100; $i++) {
限制了$i在for迴圈裡。
(三)變數申明
假如你在編寫某個函數,並且你想要它有私人變數,就必須使用my來申明變數。假如忘記了,會發生什麼事?
sub function {
$x = 42; # Oops, should have been my $x = 42.
}
在該情形下,你的函數修改了全域包變數$x。假如你在其他地方要用到那個包變數,那對程式將是災難。
最近版本的perl有針對這點的保護選項,你可以啟用它。假如放置:
use strict ‘vars‘;
在程式的頂部,perl將要求包變數有明確的包限定詞。$x=42裡的$x沒有這樣的限定詞,所以程式甚至不會通過編譯;代替的,編譯器會異常中斷,並輸出如下錯誤訊息:
Global symbol "$x" requires explicit package name at ...
假如你希望$x是個私人my變數,你可以回頭增加my。假如你確實想使用全域包變數,你能回頭改變它為:
$main::x = 42;
或其他相應的包。
use strict還有其他的檢測選項,請看perldoc strict的更多細節。
現在假設你在編寫Algorithms::KnuthBendix模組,你想使用strict vars保護模式,但假如任何時候你需要一遍又一遍的敲入$Algorithms::KnuthBendix::Error,你會覺得很煩。
你可以告訴strict vars產生一個例外:
package Algorithms::KnuthBendix;
use vars ‘$Error‘;
這樣就在你使用短名字$Error時,避免了包變數$Algorithms::KnuthBendix::Error導致的strict vars失敗。
如下寫法,也可以在某個代碼塊裡關閉strict vars:
{ no strict ‘vars‘;
# strict vars is off for the rest of the block.
}
(四)總結
包變數總是全域的。它們有一個名字和一個包限定詞。可以忽略包限定詞,這樣perl會使用預設的包,預設包可由package申明設定。對私人變數,請使用my。不要使用local,它已淘汰。
避免使用全域變數,因為它難以確保程式的2部分不會錯誤的使用對方的變數。
為了避免意外使用全域變數,請在程式裡使用use strict ‘vars‘。它會檢查並確保所有的變數要麼是申明為私人的,要麼明確使用了包限定詞,要麼明確使用use vars來申明。
(五)關於‘our‘的補充
perl 5.6.0介紹了一個新的our(...)申明。它的文法與my()相同,它是use vars的代替品。
如果不深究細節,our()就類似於use vars;它的唯一影響是申明變數,以便它們免除strict ‘vars‘的檢查。然而相對於use vars,它可能有2個優勢:文法不那麼怪異,影響是詞法範圍。也就是說,它讓stict檢查失效的範圍僅僅在當前塊之內:
use strict ‘vars‘;
{
our($x);
$x = 1; # 這裡使用全域變數$x沒問題
}
$x = 2; # 這裡使用$x通常引起編譯時間錯誤
所以使用use vars ‘$x‘申明時,可以在任何地方使用全域變數$x。our($x)僅僅允許在程式的某些塊裡申明全域變數$x,假如意外的在其他地方使用它,仍會導致錯誤。
perl學習之六:變數範圍