perl學習之六:變數範圍

來源:互聯網
上載者:User

標籤:

變數範圍

(一)包變數

$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學習之六:變數範圍

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.