內容

名稱

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 的私有內部名稱,因此您可以確定它們是明確的。與人名不同,參考只指向一件事,而且您總是知道它指向什麼。如果您有陣列的參考,您可以從中恢復整個陣列。如果您有雜湊的參考,您可以恢復整個雜湊。但參考仍然是一個簡單、緊湊的純量值。

您不能擁有值為陣列的雜湊;雜湊值只能是純量。我們被困住了。但一個單一的參考可以指向整個陣列,而參考是純量,所以您可以擁有指向陣列的參考雜湊,它會像陣列雜湊一樣,而且它會像陣列雜湊一樣有用。

我們稍後會在看過一些管理參照的語法後,再回來探討這個城市國家的問題。

語法

只有兩種方法可以建立參照,而且只有兩種方法可以在建立參照後使用它。

建立參照

建立規則 1

如果您在變數前面加上 \,您就會取得該變數的參照。

$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 的方式,而無需先將其儲存在有名稱的變數中。

建立規則 2

[ 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

如果您只寫 [],您會取得一個新的空匿名陣列。如果您只寫 {},您會取得一個新的空匿名雜湊。

使用參照

取得參照後,您可以對它做什麼?它是一個純量值,我們已經看到您可以將它儲存為純量值,並像任何純量值一樣再次取得它。只有另外兩種方法可以使用它

使用規則 1

您總可以在大括弧中使用陣列參照,取代陣列的名稱。例如,使用 @{$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";
}

使用規則 2

使用規則 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 的部分重點

您可能比較喜歡繼續閱讀 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 相同的條款下重新散布或修改它。

不論其散布方式,這些檔案中的所有程式碼範例特此置於公有領域。您被允許且鼓勵在您自己的程式中使用此程式碼,以供娛樂或營利,視您所見為宜。在程式碼中加入簡單的註解以表示感謝會是很客氣的,但不是必要的。