my,our,local,Perl範圍變量聲明

 

my,our,local,Perl範圍變量聲明

<script>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>

 範圍聲明

和全局聲明類似,詞法範圍聲明也是在編譯時起作用的。和全局聲明不同的是,詞法範圍聲明的作用範圍是從聲明開始到閉合範圍的最裏層(塊,文件,或者 eval--以先到者爲準)。這也是爲什麼我們稱它爲詞法範圍,儘管"文本範圍"可能更準確些,因爲詞法範圍這個詞實在和詞法沒什麼關係。但是全世界的計 算機科學家都知道"詞法範圍"是什麼意思,所以在這裏我們還是用這個詞。

Perl 還支持動態範圍聲明。動態範圍同樣也伸展到最裏層的閉合塊,但是這裏的"閉合"是運行時動態定義的,而不是象文本那樣在編譯時定義。用另外一種方式來說, 語句塊通過調用其他語句塊實現動態地嵌套,而不是通過包含其他語句塊來實現嵌套。這樣的動態嵌套可能在某種程度上和嵌套的文本範圍相關,但是這兩者通常是 不一樣的,尤其是在調用子過程的時候。

我們曾經說過 use 的一些方面可以認爲是全局聲明,但是 use 的其他方面卻是詞法範圍的。特別是,use 不僅輸入包的符號,而且還實現了許多讓人不可思議的編譯器暗示,也就是我們說的用法(pragmas)。大多數用法是詞法範圍的,包括 use strict 'vars' 用法,這個用法強制你在使用前先聲明變量。參閱後面的“用法”節。

很有意思的是,儘管包是一個全局入口,包聲明本身是詞法範圍的。但是一個 package 聲明只是爲閉合塊的餘下部分聲明此缺省包的身份。Perl 會到這個包中查找未聲明的,未修飾的變量名(注:還有未定義的子過程,文件句柄,目錄句柄和格式)。換句話說,實際上從來沒有聲明什麼包,只是當你引用了 某些屬於那些包的東西的時候才突然出現。當然這就是 Perl 的風格。

4.7.1 範圍變量聲明

本章剩下的大部分內容是關於使用全局變量的。或者換句話說,是關於‘不’使用全局變量的。有各種各樣的聲明可以幫助你不使用全局變量——或者至少不會愚蠢地使用它們。

我們已經提到過 package 定義,它在很早以前就引入 Perl 了,這樣就允許全局量可以分別放到獨立的包裏。對於某些變量來說,這個方法非常不錯。庫,模塊和類都用包來存儲它們的接口數據(以及一些它們的半私有數 據)以避免和你的主程序或者其他模塊的變量或者函數衝突。如果你看到某人寫到 $Some::stuff(注:或者 $Some'stuff,不過我們不鼓勵這麼寫),他們是在使用來自包 Some 的標量變量 $stuff。參閱第十章。

如果這麼幹活的話,Perl 程序隨着變量的增長會很快變得不好用。好在 Perl 的三種範圍聲明讓它很容易做下面這些事:創建私有變量(用 my),進行有選擇地訪問全局變量(用 our),和給全局變量提供臨時的值(用 local):

   my $nose;
our $House;
local $TV_channel;

如果列出多於一個變量,那麼列表必須放在圓括弧裏。就 my 和 our 而言,元素只能是簡單的標量,數組或者散列變量。就 local 而言,其構造可以更寬鬆:你還可以局部化整個類型團和獨立的變量或者數組和散列的片段:

   my($nose, @eyes, %teeth);
our ($House, @Autos, %Kids);
local (*Spouse, $phone{HOME});

上面每種修飾詞都給它們修飾的變量做出某種不同類型的“限制”。簡單說:our 把名字限於一個範圍,local 把值限於一個範圍以及 my 把名字和值都限於一個範圍。

這些構造都是可以賦值的,當然它們對值的實際處理是不同的,因爲它們有不同的存儲值的機制。如果你不給它們賦任何值(象我們上面那樣),它們也有一些區別:my 和local 把涉及的變量初始化爲 undef 或 (),另一方面,our 不修改與之相聯的全局變量的當前值。

從語義上來講,my,our 和 local 都只是簡單的左值表達式的修飾詞(類似形容詞)。當你給一個被修飾的左值賦值時,修飾詞並不改變左值是標量狀態還是列表狀態。想判斷賦值將按照什麼樣的方式運行,你只要假設修飾詞不存在就行了。所以:

   my ($foo) = <STDIN>;
my @array = <STDIN>;

給右手邊提供了一個列表環境,而:

   my $foo = <STDIN>;

提供了一個標量環境。

修飾詞比逗號綁定得更緊密(也有更高優先級)。下面的例子錯誤地聲明瞭一個變量,而不是兩個,因爲跟在列表後面的修飾詞沒有用圓括弧包圍。

my $foo, $bar = 1; #錯

上面和下面的東西效果一樣:

my $foo; $bar = 1;

如果打開警告的話,你會收到一個關於這個錯誤的警告。你可以用 -w 或 -W 命令行開關打開警告,或者用後面在“用法”裏解釋的 use warning聲明。

通常,應儘可能在變量所適合的最小範圍內定義它們。因爲在流控制語句裏面定義的變量只能在該語句控制的塊裏面可見,因此,它們的可視性就降低了。同樣,這樣的英文讀起來也更通順。

   sub check_warehouse {
for my $widget (our @Current_Inventory) {
print "I have a $widget in stock today./n";
}
}

******************************************************************************
最常見的聲明形式是 my,它定義詞法範圍的變量;這些變量的名字和值都存儲在當前範圍的臨時中間變量暫存器裏,而且不能全局訪問。與之相近的是 our 聲明,它在當前範圍輸入一個詞法範圍的名字,就象 my 一樣,但是實際上卻引用一個全局變量,任何人如果想看地話都可以訪問。換句話說,就是僞裝成詞彙的全局變量。
另外一種形式的範圍,動態範圍,應用於 local 變量,這種變量實際上是全局變量,除了 “local(局部)”的字眼以外和局部的中間變量暫存器沒有任何關係。

*******************************************************************************

4.7.2 詞法範圍的變量:my

爲幫助你擺脫維護全局變量的痛苦,Perl 提供了詞法範圍的變量,通常簡稱爲詞彙。和全局變量不同,詞彙保證你的隱私。假如你沒有分發這些私有變量的引用(引用可以間接地處理這些私有變量),你就 可以確信對這些私有變量的訪問僅限於你的程序裏面的一個分離的,容易標識的代碼段。這也是爲什麼我們使用關鍵字 my 的原因。

一個語句序列可以包含詞法範圍變量的聲明。習慣上這樣的聲明放在語句序列的開頭,但我們並不要求這樣做。除了在編譯時聲明變量名字以外,聲明所起的作用就象普通的運行時語句:它們每一句都被仔細地放在語句序列中,就好象它們是沒有修飾詞的普通語句一樣:

   my $name = "fred";
my @stuff = ("car", "house", "club");
my ($vehicle, $home, $tool) = @stuff;

這些詞法變量對它們所處的最近的閉合範圍以外的世界而言是完全不可見的。和 local 的動態範圍效果(參閱下一節)不同的是,詞彙對任何在它的範圍內調用的子過程都是不可見的。甚至相同的子過程調用自身或者從別處調用也如此——每個子過程的實例都得到自己的詞法變量“中間變量暫存器”。

和塊範圍不同的是,文件範圍不能嵌套;也沒有“閉合”的東西 —— 至少沒有文本上的閉合。如果你用 do,require 或者 use 從一個獨立的文件裝載代碼,那麼在那個文件裏的代碼無法訪問你的詞彙,同樣你也不能訪問那個文件的詞彙。

但是,任何一個文件內部的範圍(甚至文件本身)都是平等的。通常,一個比子過程定義大一些的範圍對你很有用,因爲這樣你就可以在有限的一個子過程集合裏共享私有變量。這就是你創建在 C 裏被認爲是“static”(靜態)的變量的方法:
子例程定義的位置不同,my定義的詞彙的作用域會有什麼區別?
{
my $state = 0;
# $state變量會有效? 
      sub on { $state = 1}
      sub off { $state = 0}
      sub toggle { $state =!$state }
}
測試程序:
#!/usr/local/bin/perl
#
#
use strict;
my $var = 0;
 print " begin var = $var /n";   #結果0
 &on; # 打印1
 print "after on var = $var /n"; #打印0
 &off;  #打印0

 sub on {my  $var = 1; print " on :var = $var/n";}  # 子例程中單獨定義會臨時覆蓋外邊的$var定義
 sub off { $var = 0 ; print "off : var = $var /n"}

eval STRING 操作符同樣也作爲嵌套範圍運行,因爲 eval 裏的代碼可以看到其調用者的詞彙(只要其名字不被 eval 自己範圍裏的相同聲明隱藏)。匿名子過程也可以在它們的閉合範圍內訪問任意詞彙;如果是這樣,那麼這些匿名子過程就是所謂的閉包(注:一個記憶用詞,表示 在“閉合範圍”和“閉包”之間的普通元素。(閉包的真實定義源自一個數學概念,該概念考慮數值集合和對那些數值的操作的完整性。))結合這兩種概念,如果 一個塊 eval 了一個創建一個匿名子過程的字串,該子過程就成爲可以同時訪問 eval 和該塊的閉包,甚至在 eval 和該塊退出後也是如此。參閱第八章,引用,裏的“閉包”節。

新聲明的變量(或者是值--如果你使用的是 local)在聲明語句之後纔可見。因此你可以用下面的方法給一個變量做鏡像:

   my $x = $x;

這句話把新的內部 $x 初始化爲當前值 $x,不管 $x 的當前含義是全局還是詞彙。(如果你沒有初始化新變量,那麼它從一個未定義或者空值開始。)

定義一個任意名字的詞彙變量隱藏了任何以前定義的同名詞彙。它也同時隱藏任何同名無修飾全局變量,不過你總是可以通過明確聲明全局變量所處的包的方法來訪問全局變量,比如,$PackageName::varname。

4.7.3 詞法範圍全局聲明:our

有一個訪問全局變量的更好的方法就是 our 聲明,尤其那些在 use strice 聲明下運行的程序和模塊。這個聲明也是詞法 範圍內的,因爲它的應用範圍只擴展到當前範圍的結尾。但與詞法範圍的 my 或動態範圍的 local 不同的是:our 並不隔離當前詞法或者動態範圍裏的任何東西。相反,它在當前環境裏提供一個訪問全局變量的途徑,它把所有同名詞彙隱藏起來(否則這些詞彙會爲你隱藏全局變 量)。在這個方面,our 變量和 my 變量作用相同。

如果你把 our 聲明放在任何花括弧分隔的塊的外面,它的範圍就延續到當前編譯單元的結尾。通常,人們只是把它放在一個子過程定義的頂端以表明他們在訪問全局變量:

   sub check_warehouse {
our @Current_Inventory;
my $widget;

foreach $widget (@Current_Inventory) {
print "I have a $widget in stock today./n";
}
}

因爲全局變量比私有變量有更長的生命期和更廣的可見範圍,所以與臨時變量相比我們喜歡爲它們使用更長和更鮮明的名字。如果你有意遵循這個習慣,它可以象 use strict 一樣起到制約全局量使用的效果,尤其是對那些不願意敲字的人。

重複的 our 聲明並不意味着嵌套每個嵌套的 my 會生成一個新變量,每個嵌套的 local 也生成一個新變量。但是每次你使用 our 的時候,你實際上是說同一個變量,不管你有沒有嵌套。當你給一個 our 變量賦值時,其作用在整個聲明範圍都起作用。這是因爲 our 從不創建數值;它只是提供一種有限制地訪問全局量的形式,該形式永遠存活:

   our $PROGRAM_NAME = "waiter";
{
our $PROGRAM_NAME = "server";
# 這裏調用的代碼看到的是"server"
}
# 這裏執行的代碼看到的仍然是"server".

而對於 my 和 local 來說,在塊之後,外層變量或值再次可見

   my $i = 10;
{
my $i = 99;
...
}
# 這裏編譯的代碼看到外層變量。

local $PROGRAM_NAME = "waiter";
{
local $PROGRAM_NAME = "server";
# 這裏的代碼看到"server".
...
}
# 這裏執行的代碼再次看到"watier"

通常只給 our 賦值一次,可能是在程序或者模塊的非常頂端的位置,或者是很少見地用 local 前綴 our,獲取一個 local 自己的變量:

   {
local our @Current_Inventory = qw(bananas);
check_warehouse(); # 我們有香蕉(bananas)
}

4.7.4 動態範圍變量:local

在一個全局變量上使用 local 操作符的時候,在每次執行 local 的時候都給該全局量一個臨時值,但是這並不影響該變量的全局可視性。當程序抵達動態範圍的末尾時,臨時值被拋棄然後恢復原來的值。但它仍然是一個全局變量,只是在執行那個塊的時候碰巧保存了一個臨時值而已。如果你在該全局變量包含臨時值時調用其他函數,而且該函數訪問了該全局變量,那麼它看到的將是臨時值,而不是初始值。換句話說,該函數處於你的動態範圍,即使它並不處在你的詞法範圍也如此(注:這就是爲什麼有時候把詞法範圍叫做靜態範圍:這樣可以與動態範圍相比並且突顯它們的編譯時決定性。不要把這個術語的用法和 C 或 C++ 裏的 static 的用法混淆。這個術語用得太廣泛了,也是我們避免使用它的原因。)

如果你有個看起來象這樣的 local:

   {
local $var = $newvalue;
some_func();
...
}

你完全可以認爲它是運行時的賦值:

{
$oldvalue = $var;
$var = $newvalue;
some_func();
...
}
continue {
$var = $oldvalue;
}

區別是如果用 local那麼不管你是如何退出該塊的,變量值都會恢復到原來的,即使你提前從那個範圍 return(返回)。變量仍然是同樣的全局變量,其值則取決於函數是從從哪個範圍調用的。這也是爲什麼我們稱之爲動態範圍——因爲它是在運行時修改。

和 my 一樣,你也可以用一份同樣的全局變量的拷貝來初始化一個 local。在子過程執行過程中(以及任何在該過程中的調用,因爲顯然仍將看到的是動態範圍的全局變量)對那個變量的任何改變都會在子過程返回的時候被丟棄。當然,你最好還是對你乾的事加註釋:

   # WARNING: Changes are temporary to this dynamic scope.
local $Some_Global = $Some_Global;

不管一個全局變量是用 our 明確聲明的,還是突然出現的,還是它保存一個註定要在範圍退出後被丟棄掉的 local 變量,它對你的整個程序而言仍然是完全可見的。對小程序來說,這樣挺好;但是對大程序來說,你很快就會忘記代碼中在那裏使用了全局變量。如果你願意,你可 以禁止隨機地使用全局變量,你可以用下一節描述的 use strict 'vars' 用法來達到這個目的。

儘管 my 和 local 都提供了某種程度的保護,總的來說你還是應該優先使用my。 當然,有時候你不得不用 local 來臨時改變一個現有全局變量的值,就象我們在第二十八章,特殊名字,裏列出來的那樣。只有字母數字標識符才能處於詞法範圍,而那些特殊變量有許多並不是嚴 格的字母數字。你也需要用 local 來對一個包的符號表做臨時的修改——象我們在第十章 “符號表”裏顯示的那樣。最後,你可以把 local 用在數組或散列的單個元素或者整個片段上。甚至當數組或散列是詞法變量的時候也能這麼幹,這時候是把 local 的動態範圍建築在那些詞法(變量)的上層。我們不會在這裏就 local的語義講得太多。參閱第二十九章的 local 獲取更多知識。

發佈了9 篇原創文章 · 獲贊 0 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章