自動目錄
此篇談談變數的使用範圍。
程式的區塊
一個程式可以分成幾個區域,例如主程式、迴圈、副程式等等,這些區域稱之為「區塊」,變數在每個區塊可見不可見,由變數的視界來決定,就算沒有迴圈或副程式,我們也可以自訂區塊,方法就是用一個大括號把他包起來。
# 自訂的區塊,程式碼可以寫在這裡
}
最上層稱為父區塊,子區塊就是父區塊下面的區塊
{
# 子區塊
}
下面的例子中,程式就分成好幾個區塊
@nums= (3,5,6,8,11); print &avg; print "這是主程式的區塊\n"; foreach (@nums){ print $_; print "這是迴圈中的區塊\n"; } print "又回到主程式的區塊\n"; $a=5; if($a>3){ print "A>3\n"; } sub avg { print "這是副程式的區塊\n"; @_= @nums; $sum=0; foreach (@_){ $sum += $_; } $sum/ @_; } print "又回到主程式的區塊\n"; { print "自己定義出來的區塊\n"; } print "又回到主程式的區塊\n";
分成了數個區塊
紫色底是主程式的區塊、黃色底是副程式的區塊、淺藍色底是迴圈的區塊、綠色底是自己訂義的區塊。上圖還有一個隱藏的區塊,就是if 的大括號{}這個區塊(不一定會執行)。
換句話說,用一個大括號包起來就是一個區塊,大刮號中的大括號就是子區塊以此類推。
變數的視界
每個變數都有有效的範圍,稱之為視界scope。
設定PERL變數的視界
PERL裡面有三種變數的視界可以設定
my 限定變數在當前這個區塊及其下的子區塊,父區塊及子區塊叫用的副程式區塊不可見,限度範圍最小。
local 限定變數在當前這個區塊、子區塊、叫用的副程式區塊,父區塊不可見,限度範圍次之。
our 設定變數為全域變數,可在所有區塊中存取
預設的變數是全域的
PERL預設變數都是全域的,如果不指定視界,在整個程式流程中都能被看見,來看範例:
$n=7; print &sqr; #49 sub sqr { return $n**2; # 竟然副程式能直接叫用外面的 $n }
我使用一個$n=7,然後呼叫sqr的副程式,並不傳入任何引數,這種情況裡面會直接使用外面的$n
沒想到這樣子也能正確運作,好嚇人啊!
甚至我可以不要return,直接把外面的$n作運算:
$n=7; &sqr; print $n; #49 sub sqr { $n**=2; # 直接運算 }
這就是PERL驚人的「彈性」。
但如果變數都是這樣子,難保這些變數不會把程式搞得亂七八糟,連錯哪裡都不知道。對大部分的程設師來說,這會導致除錯困難。
也許讀者會想,那我副程式和主程式的順序倒過來行不行?
sub sqr{ ... } # 副程式寫在上面
$n=7;
print &sqr;
甚至是載入函式庫的方式:
require "mylib.pl";
$n=7;
print &sqr;
結果都一樣!不知該高興還是難過啊~~這會是寫大型程式的災難,我們需要限定變數的有效範圍。
使用PERL變數的視界
經由以下幾個範例來說明
範例1、子區塊看得到父區塊的變數
$u= 'A'; #未指定為 our our $x= 'A'; my $y= 'A'; local $z= 'A'; { print "區塊:",$u,$x,$y,$z,"\n"; { print "子區塊:",$u,$x,$y,$z,"\n"; } }
無論是什麼視界的變數,子區塊中都能見到父區塊的變數,子區塊也都能修改父區塊傳來的變數。
結果
區塊:AAAA
子區塊:AAAA
範例2、區塊中可定義視界來限制變數的範圍
$u=$x=$y=$z= 'A'; # 預設是our { print "子區塊頭:",$u,$x,$y,$z,"\n"; #AAAA $u= 'B'; #等同our our $x= 'B'; #直接修改到全域的$x my $y= 'B'; #另外定義一個$y是屬於此區塊及以下 local $z= 'B'; #另外定義一個$z是屬於此區塊及以下 print "子區塊中:",$u,$x,$y,$z,"\n"; #BBBB { print "孫區塊:",$u,$x,$y,$z,"\n"; #BBBB孫區塊看得到子區塊的變數 } } print "父區塊:",$u,$x,$y,$z,"\n"; # BBAA my和local無法修改到父區塊的變數
結果
子區塊頭:AAAA
子區塊中:BBBB
孫區塊:BBBB
父區塊:BBAA
第5-8行 在區塊中分別把變數設為B,但是有限定不同的視界,$u, $x 修改的就是主區塊的 $u, $x。而 $y, $z兩個被限制在區塊和其子區塊中,所以回到主區塊以並不會改變。
只有宣告為 our的變數才能由子區塊來異動。
範例3、副程式的區塊是獨立的區塊,不屬於子區塊。
$u=$x=$y=$z= 'A'; { print "區塊頭:",$u,$x,$y,$z,"\n"; $u= 'B'; our $x= 'B'; my $y= 'B'; local $z= 'B'; &some; #叫用副程式 } print "主區塊:",$u,$x,$y,$z,"\n"; sub some{ print "副程式:",$u,$x,$y,$z,"\n"; }
結果
區塊頭:AAAA
副程式:BBAB
主區塊:BBAA
說明
第5-8行 在區塊中分別把變數設為B,但是有限定不同的視界,
第9行 叫用副程式&some,不傳參數進去,副程式會直接使用 local或our視界的變數
這裡各位應該有發現,在附程式中的 $y竟然是'A'。由於 my限定變數使用在自己及子區塊。這個變數不會傳到副程式中;
但是 $z有宣告成local,所以這個變數有傳遞到副程式中。這就是my和local最大的差別。
範例4、副程式的區塊是獨立的區塊,不屬於子區塊,但屬於父區塊,副程式中看得到父區塊的my視界變數。
my $n=2; &sqr; print $n; #4 <==副程式看得見my父區塊變數 { my $n=3; &sqr; <== 在子區塊作$n平方,對象竟然是父區塊的$n(此時為4) print $n; #3 這是子區塊的3 } print $n; #16 sub sqr{ $n**=2; }
非常難以理解的邏輯,但事實上就是這樣,因為副程式和父區塊屬於同一層,他看得見父程式的 my、local 和our視界變數,但子區塊以下的副程式卻見不到子區塊以下的 my變數。
範例5、foreach的迴圈變數預設是local,父區塊不可見
PERL對foreach 的變數有特別的待遇,非常不容易理解
@b= (6,7,8); our $n=1; foreach $n (@b){ print $n; #678 } print $n; #1 不是8
第2行 有定義一個$n,在外面定義他是 $n=1,
第4行 在迴圈中有用到一個迴圈變數$n,在迴圈中會自動變成local,因此就算裡面$n怎麼改變,迴圈結束後的$n還是原本的1。
這點要小心,因為有時我們會想取迴圈的最後一個值,如果用這樣的作法,取出來的結果會是錯的。
就算把第2行的 our變成 local或my,得到的結果還是一樣,這表示迴圈中的$n自動是最小範圍的。第4行也常看有人寫成這樣,強迫指定為my:
範例6、foreach的迴圈變數預設是local?
上面的範例說到預設是local,那如果把外面的$n變成my,回圈裡面的$n還是local不就會發生衝突嗎?
對的,會提出這個問題的程設師非常的厲害,但是前面有說PERL會對迴圈中的變數有特別的待遇:
這裡把外面的 $n宣告為 local,那麼裡面的 $n也是local,
local $n=3; @b= (6,7,8); foreach $n (@b){ &sqr; print $n; #36 49 64 } print $n; #3 sub sqr { $n**=2; }
邏輯上 foreach中的$n是local,可以叫用副程式sqr並把$n(6,7,8)也帶給他計算,果然如此。
但比較怪異的是如果把父區塊的$n指定為my視界(第1行),結果就出乎意料了:
my $n=3; @b= (6,7,8); foreach $n (@b){ &sqr; print $n; # 6 7 8 } print $n; #6561 = 3^8 sub sqr { $n**=2; }
因為PERL不允許父區塊為my的變數到子區塊中宣告為local,因此會自動轉成my視界,這樣子反而副程式讀不到迴圈中的$n,只好拿外面的$n=3來運算(參看範例4)
這裡非常不好理解,簡單說明一下:父區塊的$n=3是my視界,在子區塊中是可見的,所以此時$n=3;迴圈中的$n已被轉為my視界,所以副程式中看不見他。但是看得到父區塊的$n=3,就拿他來作運算。
限制程式的變數一定要指定範圍才能取用
對於越來越大的PERL程式而言(例如>200行),如果沒有系統化的使用變數,未來錯誤會除不完,套句工程師說的:
除蟲都能除成蝴蝶 (debug->de-butterfly)
所以決定採用嚴僅一點的寫法,每個變數在使用前,一定要宣告他的視界(my, local, our)才行,不然就不給跑。
作法是在程式的前面加上
use strict;
use 在前面雜湊的範例中有出現過,是使用模組的意思,使用strict這個模組,在後面的篇章會說明如何使用、自寫模組。
變數宣告視界
# 正確寫法 my $x; my %hash; local @arr; my ($a, @b, %c); our($u,$x,$y,$z); my $c=1; #直接賦值 my $x= my $y = my $z=1; #定義同時賦值 # 不建義寫法 $x; # 沒定義視界,預設是our my $x, $y; # 只定義到$x, 沒有定義到$y our $x; my $x; # 重覆定義 my $x= $y = $z=1; # 只定義到$x, 少定義 $y,$z
結論
1. 子區塊的local 不能蓋過父區塊的 my視界變數
my $a { local $a } NOT OK!
2. our 視界變數才能在子區塊中被異動
our $a=1; { our $a=2; } $a; #2 local $a=1; { our $a=2; } $a; #2 my $a=1; { our $a=2; } $a; #1 our $a=1; { local $a=2; } $a; #1 local $a=1; { local $a=2; } $a; #1 my $a=1; { local $a=2; } $a; #Can't localize lexical variable $n our $a=1; { my $a=2; } $a; #1 local $a=1; { my $a=2; } $a; #2 my $a=1; { my $a=2; } $a; #1
3. 副程式中宣告our代表要使用全域變數
sub fun{ our $a; }
4. 同區塊變數的視界不能重覆定義
my $a; our $a; NOT OK
5. 沒寫到的預設是 our
其實視界這篇我重寫好幾次,我相信大部分的人看駱駝書都看不懂它在寫什麼,很多書對於local和my的分別說不清楚,甚至有錯誤的結論。所以我才決定要用我自己能接受的方式來說明視界的意義。
上一篇 12- 副程式
回到目錄 01-撰寫第一隻PERL程式
下一篇 14-進階比對 #1--取回比對內容