[精讚] [會員登入]
3686

[PERL] 13- 變數的視界

變數的使用範圍,什麼是my,什麼是our?

分享此文連結 //n.sfs.tw/11709

分享連結 [PERL] 13- 變數的視界@新精讚
(文章歡迎轉載,務必尊重版權註明連結來源)
2020-11-08 13:53:44 最後編修
2017-08-26 11:10:13 By 張○○
 

自動目錄

此篇談談變數的使用範圍。

程式的區塊

一個程式可以分成幾個區域,例如主程式、迴圈、副程式等等,這些區域稱之為「區塊」,變數在每個區塊可見不可見,由變數的視界來決定,就算沒有迴圈或副程式,我們也可以自訂區塊,方法就是用一個大括號把他包起來。

{
   # 自訂的區塊,程式碼可以寫在這裡
}

最上層稱為父區塊,子區塊就是父區塊下面的區塊

# 父區塊
{
   # 子區塊
}

下面的例子中,程式就分成好幾個區塊

@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
副程式:BB
AB
主區塊:BBAA

說明

第5-8行 在區塊中分別把變數設為B,但是有限定不同的視界,

第9行 叫用副程式&some,不傳參數進去,副程式會直接使用 local或our視界的變數

這裡各位應該有發現,在附程式中的 $y竟然是'A'。由於 my限定變數使用在自己及子區塊。這個變數不會傳到副程式中;

但是 $z有宣告成local,所以這個變數有傳遞到副程式中。這就是my和local最大的差別。

因為程式在執行時有叫用副程式,local視界的變數此時在副程式中是可見的;相反的,my視界的變數在副程式中不可見,這就是駱駝書中說的「動態範圍」的意義。

範例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:

foreach my $n (@b){

範例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--取回比對內容

END

你可能感興趣的文章

[PERL] 24-呼叫系統程式及評估 Perl 如何呼叫系統程式並取回結果?

[PERL] 20- 檔案目錄處理 PERL寫入和讀出檔案

[PERL] 檢查IP是否在某個網段內:matchcidr 檢查IP是否在某個網段內 matchcidr in perl

[PERL] 命令列傳入參數 argv Perl 的外部傳參,是$ARGV[0]、$ARGV[1]、$ARGV[2].....

[PERL] 23-多執行緒 而多執行緒的程式,可在一次執行程式時間,同時進行多線程的計算,在效率上可獲得即大的提升。

[PERL] 08-陣列 #2 --操作 更多perl陣列的操作,如拆開、黏合、取出、加入等等

我有話要說

>>

限制:留言最高字數1000字。 限制:未登入訪客,每則留言間隔需超過10分鐘,每日最多5則留言。

訪客留言

[無留言]

隨機好文

為什麼要買長達二十年的保單? 為什麼要買長達二十年的保單?找一個可以說服我買二十年保單的理由。

NETCRAFT發現你的網站及作業系統 NETCRAFT可以發現你的網站及作業系統

好用的3+2碼郵遞區號查詢系統推薦 網路上找到用地址輸入判斷3+2碼郵遞區號的辨識率不高,除了這個網站…

魔球中小女孩唱的歌 The show 魔球中小女孩唱的歌 The show

設計的工作絕不接受比價 拿買陽春麵的價格想買牛肉麵,寧願倒掉也不賣