perlreftut - Mark 的關於參照的極短教學
Perl 5 中最重要的其中一項新功能,就是管理多維陣列和巢狀雜湊等複雜資料結構的能力。為了啟用這些功能,Perl 5 引進了一項稱為「參照」的功能,而使用參照是管理 Perl 中複雜結構化資料的關鍵。很不幸地,有許多有趣的語法需要學習,而主要的說明文件頁面可能很難理解。說明文件非常完整,有時候人們會發現這是一個問題,因為很難分辨什麼是重要的,什麼是不重要的。
很幸運地,你只需要知道說明文件頁面中 10% 的內容,就能獲得 90% 的好處。本頁面將向你展示那 10%。
一直出現的一個問題是需要一個值為清單的雜湊。Perl 當然有雜湊,但值必須是純量;它們不能是清單。
為什麼需要清單雜湊?讓我們舉一個簡單的例子:您有一個包含城市和國家名稱的檔案,如下所示
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
您想要產生以下輸出,每個國家只出現一次,然後是該國家中城市按字母順序排列的清單
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.
這樣做的自然方式是有一個雜湊,其鍵為國家名稱。與每個國家名稱鍵關聯的是該國家中城市的清單。每次讀取一行輸入時,將其分割為國家和城市,查詢已知在該國家中的城市清單,並將新城市附加到清單中。當您完成讀取輸入時,像往常一樣迭代雜湊,在列印每個城市清單之前對其進行排序。
如果雜湊值不能是清單,您就輸了。您可能必須以某種方式將所有城市組合成一個字串,然後在寫入輸出的時候,您必須將字串分解為清單,對清單進行排序,然後再將其轉回字串。這很混亂且容易出錯。而且令人沮喪,因為 Perl 已經有非常好的清單,如果您可以使用它們,就可以解決問題。
當 Perl 5 推出時,我們已經被困在這個設計中:雜湊值必須是純量。解決方案是參考。
參考是一個純量值,它指向整個陣列或整個雜湊(或幾乎任何其他東西)。名稱是您已經熟悉的其中一種參考。每個人都是一個混亂、不方便的細胞集合。但是要指涉特定的人,例如第一位電腦程式設計師,不需要描述他們每一個細胞;您只需要簡單、方便的純量字串「Ada Lovelace」。
Perl 中的參考就像陣列和雜湊的名稱。它們是 Perl 的私有內部名稱,因此您可以確定它們是明確的。與人名不同,參考只指向一件事,而且您總是知道它指向什麼。如果您有陣列的參考,您可以從中恢復整個陣列。如果您有雜湊的參考,您可以恢復整個雜湊。但參考仍然是一個簡單、緊湊的純量值。
您不能擁有值為陣列的雜湊;雜湊值只能是純量。我們被困住了。但一個單一的參考可以指向整個陣列,而參考是純量,所以您可以擁有指向陣列的參考雜湊,它會像陣列雜湊一樣,而且它會像陣列雜湊一樣有用。
我們稍後會在看過一些管理參照的語法後,再回來探討這個城市國家的問題。
只有兩種方法可以建立參照,而且只有兩種方法可以在建立參照後使用它。
如果您在變數前面加上 \
,您就會取得該變數的參照。
$aref = \@array; # $aref now holds a reference to @array
$href = \%hash; # $href now holds a reference to %hash
一旦參照儲存在變數中(例如 $aref 或 $href),您就可以複製或儲存它,就像儲存任何其他純量值一樣
$xy = $aref; # $xy now holds a reference to @array
$p[3] = $href; # $p[3] now holds a reference to %hash
$z = $p[3]; # $z now holds a reference to %hash
這些範例顯示如何建立具有名稱的變數參照。有時您想要建立沒有名稱的陣列或雜湊。這類似於您希望能夠使用字串 "\n"
或數字 80 的方式,而無需先將其儲存在有名稱的變數中。
[ ITEMS ]
建立一個新的匿名陣列,並傳回該陣列的參照。{ ITEMS }
建立一個新的匿名雜湊,並傳回該雜湊的參照。
$aref = [ 1, "foo", undef, 13 ];
# $aref now holds a reference to an array
$href = { APR => 4, AUG => 8 };
# $href now holds a reference to a hash
您從規則 2 取得的參照與從規則 1 取得的參照是相同的參照類型
# This:
$aref = [ 1, 2, 3 ];
# Does the same as this:
@array = (1, 2, 3);
$aref = \@array;
第一行是以下兩行的縮寫,但它不會建立多餘的陣列變數 @array
。
如果您只寫 []
,您會取得一個新的空匿名陣列。如果您只寫 {}
,您會取得一個新的空匿名雜湊。
取得參照後,您可以對它做什麼?它是一個純量值,我們已經看到您可以將它儲存為純量值,並像任何純量值一樣再次取得它。只有另外兩種方法可以使用它
您總可以在大括弧中使用陣列參照,取代陣列的名稱。例如,使用 @{$aref}
取代 @array
。
以下是其中一些範例
陣列
@a @{$aref} An array
reverse @a reverse @{$aref} Reverse the array
$a[3] ${$aref}[3] An element of the array
$a[3] = 17; ${$aref}[3] = 17 Assigning an element
每行都有兩個執行相同操作的表達式。左邊的版本作用於陣列 @a
。右邊的版本作用於 $aref
參照的陣列。一旦找到它們作用的陣列,兩個版本對陣列執行的操作都是相同的。
使用雜湊參考完全相同
%h %{$href} A hash
keys %h keys %{$href} Get the keys from the hash
$h{'red'} ${$href}{'red'} An element of the hash
$h{'red'} = 17 ${$href}{'red'} = 17 Assigning an element
無論您想對參考做什麼,使用規則 1 會告訴您如何執行。您只需撰寫 Perl 程式碼,就像您會為常規陣列或雜湊撰寫相同的程式碼,然後將陣列或雜湊名稱替換為 {$reference}
。如果我只有一個參考,我如何迴圈陣列?嗯,若要迴圈陣列,您會撰寫
for my $element (@array) {
...
}
因此,將陣列名稱 @array
替換為參考
for my $element (@{$aref}) {
...
}
如果我只有一個參考,我如何列印雜湊的內容?首先撰寫列印雜湊的程式碼
for my $key (keys %hash) {
print "$key => $hash{$key}\n";
}
然後將雜湊名稱替換為參考
for my $key (keys %{$href}) {
print "$key => ${$href}{$key}\n";
}
使用規則 1 是您真正需要的一切,因為它告訴您如何對參考執行您需要執行的所有操作。但是,對陣列或雜湊最常見的操作是提取單一元素,而 使用規則 1 符號很繁瑣。因此,有一個縮寫。
${$aref}[3]
讀起來太困難,因此您可以改寫為 $aref->[3]
。
${$href}{red}
讀起來太困難,因此您可以改寫為 $href->{red}
。
如果 $aref
持有對陣列的參考,則 $aref->[3]
是陣列的第四個元素。不要將其與 $aref[3]
混淆,後者是完全不同陣列的第四個元素,一個具有欺騙性名稱 @aref
的陣列。$aref
和 @aref
的關係就像 $item
和 @item
一樣。
類似地,$href->{'red'}
是由標量變數 $href
參照的雜湊的一部分,甚至可能沒有名稱。$href{'red'}
是具有欺騙性名稱 %href
雜湊的一部分。很容易忘記省略 ->
,如果您這樣做,當您的程式從完全意外的雜湊和陣列(不是您想要使用的陣列和雜湊)中取得陣列和雜湊元素時,您將會得到奇怪的結果。
讓我們快速看一個範例,說明所有這些如何有用。
首先,請記住 [1, 2, 3]
會建立一個包含 (1, 2, 3)
的匿名陣列,並提供對該陣列的參考。
現在想想
@a = ( [1, 2, 3],
[4, 5, 6],
[7, 8, 9]
);
@a
是具有三個元素的陣列,每個元素都是對另一個陣列的參考。
$a[1]
是這些參考之一。它參考一個陣列,包含 (4, 5, 6)
的陣列,而且因為它是對陣列的參考,使用規則 2 說明我們可以寫 $a[1]->[2]
來取得該陣列的第三個元素。$a[1]->[2]
是 6。類似地,$a[0]->[1]
是 2。我們這裡有的就像一個二維陣列;你可以寫 $a[ROW]->[COLUMN]
來取得或設定陣列中任何列和任何欄的元素。
符號看起來還是有點繁瑣,所以還有另一個縮寫
在兩個下標之間,箭頭是可選的。
我們可以寫 $a[1][2]
,而不是 $a[1]->[2]
;它們的意思相同。我們可以寫 $a[0][1] = 23
,而不是 $a[0]->[1] = 23
;它們的意思相同。
現在它真的看起來像二維陣列了!
你可以看出為什麼箭頭很重要。沒有它們,我們必須寫 ${$a[1]}[2]
,而不是 $a[1][2]
。對於三維陣列,它們讓我們可以寫 $x[2][3][5]
,而不是難以閱讀的 ${${$x[2]}[3]}[5]
。
以下是對我先前提出的問題的解答,重新格式化一個城市和國家名稱檔案。
1 my %table;
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
8 for my $country (sort keys %table) {
9 print "$country: ";
10 my @cities = @{$table{$country}};
11 print join ', ', sort @cities;
12 print ".\n";
13 }
這個程式有兩個部分:第 2-7 行讀取輸入並建立一個資料結構,第 8-13 行分析資料並列印出報告。我們將有一個雜湊 %table
,其鍵是國家名稱,其值是城市名稱陣列的參考。資料結構看起來像這樣
%table
+-------+---+
| | | +-----------+--------+
|Germany| *---->| Frankfurt | Berlin |
| | | +-----------+--------+
+-------+---+
| | | +----------+
|Finland| *---->| Helsinki |
| | | +----------+
+-------+---+
| | | +---------+------------+----------+
| USA | *---->| Chicago | Washington | New York |
| | | +---------+------------+----------+
+-------+---+
我們將首先查看輸出。假設我們已經有這個結構,我們如何列印它?
8 for my $country (sort keys %table) {
9 print "$country: ";
10 my @cities = @{$table{$country}};
11 print join ', ', sort @cities;
12 print ".\n";
13 }
%table
是個普通的雜湊,我們從中取得一個鍵清單,對鍵進行排序,並像往常一樣迴圈鍵。唯一使用參考的地方是在第 10 行。$table{$country}
在雜湊中查詢鍵 $country
並取得值,該值是該國家中城市陣列的參考。 使用規則 1 說明我們可以透過說 @{$table{$country}}
來還原陣列。第 10 行就像
@cities = @array;
除了名稱 array
已被參考 {$table{$country}}
取代。@
告訴 Perl 取得整個陣列。在取得城市清單後,我們對它進行排序、合併,並像往常一樣列印它。
第 2-7 行負責建立結構。以下是這些行的內容
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
第 2-4 行取得城市和國家名稱。第 5 行查看國家是否已存在於雜湊中的金鑰中。如果不存在,程式會使用 []
符號 (建立規則 2) 建立新的空匿名城市陣列,並在雜湊中安裝對應金鑰的參考。
第 6 行將城市名稱安裝到對應陣列中。$table{$country}
現在持有到目前為止在該國家/地區中所見城市的陣列參考。第 6 行與
push @array, $city;
完全相同,但名稱 array
已替換為參考 {$table{$country}}
。 push
會將城市名稱新增到所參考陣列的結尾。
有一個重點我跳過了。第 5 行是不必要的,我們可以將其移除。
2 while (<>) {
3 chomp;
4 my ($city, $country) = split /, /;
5 #### $table{$country} = [] unless exists $table{$country};
6 push @{$table{$country}}, $city;
7 }
如果 %table
中已針對目前的 $country
有輸入,則不會有任何不同。第 6 行會找出 $table{$country}
中的值,這是陣列的參考,並將 $city
推入陣列中。但當 $country
持有金鑰(例如 Greece
)尚未存在於 %table
中時,會發生什麼事?
這是 Perl,因此它會執行完全正確的動作。它會看到您想將 Athens
推入不存在的陣列中,因此它會貼心地為您建立新的空匿名陣列,將其安裝到 %table
中,然後將 Athens
推入其中。這稱為自動化,自動建立事物。Perl 看到金鑰不存在於雜湊中,因此自動建立新的雜湊輸入。Perl 看到您想將雜湊值用作陣列,因此自動建立新的空陣列,並在雜湊中安裝對應參考。而且,Perl 一如往常地將陣列加長一個元素,以容納新的城市名稱。
我承諾只用 10% 的細節提供 90% 的好處,這表示我省略了 90% 的細節。現在您已概覽重要的部分,應該更容易閱讀 perlref 手冊頁面,其中會討論 100% 的細節。
perlref 的部分重點
您可以對任何事物建立參考,包括純量、函式和其他參考。
在 使用規則 1 中,當內部內容為原子純量變數(例如 $aref
)時,您可以省略大括號。例如,@$aref
與 @{$aref}
相同,而 $$aref[1]
與 ${$aref}[1]
相同。如果您剛開始,您可能想要養成總是包含大括號的習慣。
這不會複製基礎陣列
$aref2 = $aref1;
您會取得兩個指向同一個陣列的參照。如果您修改 $aref1->[23]
,然後查看 $aref2->[23]
,您會看到變更。
若要複製陣列,請使用
$aref2 = [@{$aref1}];
這會使用 [...]
符號建立一個新的匿名陣列,而 $aref2
會指定一個參照給新的陣列。新的陣列會初始化為 $aref1
所參照的陣列內容。
類似地,若要複製一個匿名雜湊,您可以使用
$href2 = {%{$href1}};
若要查看變數是否包含參照,請使用 ref
函數。如果其引數是參照,它會傳回 true。實際上它比這好一點:它會傳回雜湊參照的 HASH
和陣列參照的 ARRAY
。
如果您嘗試將參照用作字串,您會取得類似
ARRAY(0x80f5dec) or HASH(0x826afc0)
如果您曾經看到看起來像這樣的字串,您就知道您錯誤地列印出一個參照。
您可以將字串用作參照。如果您將字串 "foo"
用作陣列參照,它會被視為陣列 @foo
的參照。這稱為符號參照。宣告 use strict 'refs'
會停用此功能,如果您意外使用它,可能會造成各種問題。
您可能比較喜歡繼續閱讀 perllol 而不是 perlref;它詳細討論清單清單和多維陣列。之後,您應該繼續閱讀 perldsc;它是一本資料結構食譜,展示使用和列印雜湊陣列、陣列雜湊和其他種類資料的食譜。
每個人都需要複合資料結構,而 Perl 取得它們的方法是透過參照。管理參照有四個重要的規則:兩個是用於建立參照,兩個是用於使用它們。一旦您了解這些規則,您就可以使用參照來完成大部分您需要完成的重要工作。
作者:Mark Jason Dominus,Plover Systems(mjd-perl-ref+@plover.com
)
本文最初出現在《Perl Journal》(http://www.tpj.com/)第 3 卷,第 2 期。經許可轉載。
原始標題為《馬上了解參照》。
版權所有 1998 The Perl Journal。
此文件是免費的;您可以在與 Perl 相同的條款下重新散布或修改它。
不論其散布方式,這些檔案中的所有程式碼範例特此置於公有領域。您被允許且鼓勵在您自己的程式中使用此程式碼,以供娛樂或營利,視您所見為宜。在程式碼中加入簡單的註解以表示感謝會是很客氣的,但不是必要的。