自動目錄
此篇談談變數的使用範圍。
程式的區塊
一個程式可以分成幾個區域,例如主程式、迴圈、副程式等等,這些區域稱之為「區塊」,變數在每個區塊可見不可見,由變數的視界來決定,就算沒有迴圈或副程式,我們也可以自訂區塊,方法就是用一個大括號把他包起來。
# 自訂的區塊,程式碼可以寫在這裡
}
最上層稱為父區塊,子區塊就是父區塊下面的區塊
{
# 子區塊
}
下面的例子中,程式就分成好幾個區塊
@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--取回比對內容
