M.Hiroi's Home Page

Linux Programming

お気楽 Perl プログラミング超入門

[ PrevPage | Perl | NextPage ]

リファレンス (前編)

前回は、再帰定義を中心に関数の使い方を説明しました。今回は「リファレンス (reference)」です。リファレンスはオブジェクト指向とともに Perl 5 からサポートされた機能で、「C言語のポインタ」に相当する機能を持っています。

プログラミング言語の学習には、どの言語にもいくつかの難関があります。C言語の場合、「ポインタ」が最大の難関と言われていますが、コンピュータの基本 (CPU やメモリの概念) を正しく理解していれば、けっして難しい話ではありません。問題があるとすれば、C言語のポインタが不適切な操作や演算によってプログラムを簡単に暴走させてしまうことでしょう。

Perl のリファレンスは、C言語のポインタのように危険なものではありません。もともとC言語は、UNIX という OS を記述するために設計されたプログラミング言語です。マシン語なみの操作ができないようでは役に立たないわけで、ポインタという危険なものでもユーザーに開放されているのです。Perl の場合、リファレンスの操作でプログラムが暴走することはないので、安心して使ってください。

まず最初に、基本となるメモリの構成から説明しましょう。C言語のポインタがわかっている方は読み飛ばしてもらってかまいません。

Perl のリファレンスへ

●メモリの構成

メモリにはプログラムやデータが記憶されています。メモリのことを「主記憶装置」といいます。CPU はメモリに格納されているプログラムを実行します。ハードディスクや CD-ROM, USB メモリなどに格納されているプログラムは、メモリに読み込まないと実行することはできません。

メモリ以外の記憶装置を「補助記憶装置」といいます。ハードディスクや CD-ROM, USB メモリなどいろいろな記憶装置がありますが、すべての方法に共通しているのは、情報をなんらかの方法で ON / OFF という 2 つの状態で表していることです。

この ON / OFF を数値の 1 と 0 に対応させます。つまり、コンピュータは情報を 0 と 1 で表すわけです。これを「ビット (bit)」といいます。ひとつのビットでは、0 か 1 かの 2 つの情報しか表せませんが、使用するビットの数を増やすと、それだけたくさんの情報を表すことができます。たとえば、4 ビット使用すると 16 通りの情報を表すことができます。このビットをたくさん集めたものがメモリなのです。

ビットでは情報が細かすぎるので、いくつかのビットをまとめた「バイト (byte)」を単位として、メモリは構成されています。現在は、1 バイトを 8 ビットとしてメモリを構成するコンピュータ [*1] がほとんどです。1 バイトは数値で表すと 0 から 255 までの 256 通りの情報を記憶できます。メモリは大きさをバイト単位で表します。

メモリから値を読み出す、または書き込む場合、最小の大きさがバイトとなります。どのメモリから値を読み出すのか、またはどのメモリに値を書き込むのかを指定するために、メモリにはバイト単位で「アドレス (番地 : address)」がつけられています。これは私たちの住所や電話番号と同じです。メモリの場合は単純に数値で表します。下図を見てください。

コンピュータの世界では 0 から数えるのが普通です。Perl やC言語の場合も、配列は 0 から数えましたね。ビットもアドレスも 0 から数えます。

数値計算をするときなど、1 バイトでは情報量が少ない場合は、2 バイトまたは 4 バイトまとめてメモリを使用します。文字を表したい場合は、文字を数値に対応させます。アルファベットは 26 種類ありますから、大文字小文字、そしてほかの記号を合わせても 1 バイトあれば表現できます。たとえば、パソコンで使われる文字コードの規則にアスキー (ASCII) コードがありますが、この規則では A という文字は 0x41 に対応します。しかし、1 バイトでは日本語を表現できません。そこで複数のバイトを使って日本語を表現します。

グラフィックの場合は、点 (ドット : dot) をメモリに対応させて表現します。たとえば、1 ドットを 1 バイトで表現してみましょう。そのメモリの内容が 0 ならば、ドットが書かれていないことにします。それ以外のときはドットが書かれているという規則にします。このとき、1 から 255 に対応する色を決めておけば、256 色の絵が描けるわけです。もし、65536 色の絵を描くのであれば、1バイトでは表現できないので、1 ドットにつき 2 バイト使用することになります。フルカラー (1600 万色) 表示であれば、1 ドットにつき 3 バイト必要になるわけです。

このように、メモリに格納されるデータは、単なる数値に過ぎないのですが、使うソフトウェアによってその意味は異なるのです。ある場合は、数値計算のために使用され、ほかでは文字を格納するために、またあるときはグラフィックデータを保持します。

メモリをどのように使うかは、プログラマが決めます。そして、それを実現するための道具がプログラミング言語なのです。変数、配列、文字列といったデータも、すべてメモリに割り当てられますが、マシン語以外の高級言語ではアドレスを意識することはありません。プログラマは変数を定義するだけで、メモリの割り当てといっためんどうなことはすべてプログラミング言語 (と OS) が行ってくれます。

-- note --------
[*1] 昔は 1 word = 16 ビットでメモリを構成するコンピュータもありました。

●C言語のポインタ

一般に、プログラミング言語で扱う名前は、そのデータが割り当てられたメモリの先頭アドレスを表しています。変数名や配列名は、そのデータが割り当てられたメモリの先頭アドレスであり、関数名は、そのコードが配置されたメモリの先頭アドレス (関数の開始アドレス) を表しています。

高水準と呼ばれるプログラミング言語の場合、名前から実際のアドレスを求めることはできません。プログラマが勝手にメモリ割り当てを操作したり変更すると困る場合があるからです。もっとも、そのようなことをしなくてもプログラミングできないようでは、とても高水準とはいえません。

ところがC言語の場合は違います。C言語では名前からアドレスを求める演算子 & [*2] が用意されているのです。変数名にこの演算子を適用することで、変数に割り当てられたメモリの先頭アドレスを求めることができます。また、配列名や関数名はデータやコードの先頭アドレスにつけられた名前にすぎず、& 演算子を使わなくてもアドレスとして使用することができます。

そして、C言語ではアドレスを格納する変数を定義することができます。これが「ポインタ」です。アドレスは整数値ですから、ポインタの中身は整数値です。ですが、その値はある変数が割り当てられているメモリの先頭アドレスです。つまり、ポインタは「ある変数を指し示している変数」ということになります。また、関数名もアドレスを示しているのですから、ポインタに代入することができます。これを「関数へのポインタ」と呼びます。どちらにしても、ポインタはあるデータを指し示している変数なのです。これを図に示すと次のようになります。

変数 p はポインタです。図に示すように、ポインタもメモリ [*3] に割り当てられます。この例では 0x68000 番地になっています。CPU が 32 bit の場合、Cコンパイラはポインタに 4 byte のメモリを割り当てる場合がほとんどで、0 から 0xFFFFFFFF までのアドレス指定が可能になります。これだと約 4 G byte のメモリを扱うことができます。CPU が 64 bit の場合、ポインタには 8 byte のメモリを割り当てることになりますが、本ページではポインタ変数の大きさは 4 byte として説明することにします。

変数 i は整数値を格納します。C言語には整数値を表すデータ型がいくつかありますが、今回は int を使いましょう。CPU が 32 bit の場合、Cコンパイラは int に 4 byte のメモリを割り当てるのが普通です。この場合、int は -2147483648 から 2147483647 までの整数を扱うことができます。上図の場合、変数 i は 0x70000 番地から割り当てられていて、値は 0x100000 です。

変数 p はポインタなので、変数 i のアドレスを代入することができます。すると、図のように変数 p の値は 0x70000 となり、変数 i を指し示すことになります。そして、ポインタ p を使って、変数 i の値を読み書きすることができるのです。これがポインタの基本的な考え方です。

ちょっと脱線しますが、実際にC言語のプログラムを示しましょう。ところで、図のアドレスは説明のためのもので、これから示すプログラムとは関係ありません。プログラムがロードされるアドレスは実行環境によって異なるので、変数 i と p のアドレスは実際にプログラムを実行してみないとわからないのです。ご注意くださいませ。

リスト : ポインタの使用例

int i;
int *p;
i = 0x100000;
p = &i;

C言語の場合、変数は格納するデータ型を宣言しないといけません。int i; は、整数値を格納する変数 i を用意します。次の int *p; は整数値を指し示すポインタを用意します。C言語の場合、変数名にアスタリスク * をつけると、それはポインタとして定義されます。

変数 p にはポイントする変数のアドレスが格納されます。そして、* をつけた *p を使って、ポイントしている変数 (この場合は整数値) にアクセスすることができます。つまり、*p は p が保持しているアドレスに格納されているデータを参照 [*4] するのです。

変数の定義は値を入れる容器を用意するだけなので、その中身はまだ定まっていません。i = 0x100000; で変数 i に値がセットされます。次の p = &i; で、ポインタ p に変数 i のアドレスがセットされます。これでポインタ p は変数 i を指し示すことになります。

このあとは *p を使って変数 i の値にアクセスできます。*p の値を読み出せば 0x100000 になり、*p = 0x999; と値を代入すれば、i の値も 0x999 となります。p は変数 i のアドレスを保持し、*p はそのアドレスに格納されているデータにアクセスできるのですから、書き込みを行えば、変数 i の値も書き変わるのは当然ですね。

ところでこのポインタ、一体何の役に立つのでしょうか。C言語の場合、次の利点があります。

(1) 「参照呼び (call by reference)」を実現する
(2) コンパクトで効率的なプログラムを書くことができる

(1) ですが、C言語の関数は「値呼び (call by value)」です。関数の引数にポインタを渡すことで、呼び出し先の関数から呼び出し元の関数に定義されている変数にアクセスすることができるようになります。値呼びについてはあとで説明します。

(2) はアドレス計算によって実現されます。ポインタが格納しているアドレスは単なる正の整数値です。四則演算ができるわけではありませんが、整数値の代入や加減算 [*5]、ポインタ同士の比較を行うことができます。とくに、インクリメント (++)、デクリメント (--) 演算子と組み合わせることで、効率的なプログラムを書くことができます。

このほかに、プログラムの実行時にメモリを取得したり、連結リストや二分木といった複雑なデータ構造を作るときにも、ポインタはとても役に立ちます。

もっとも、いいことばかりではありません。よくある間違いがポインタの初期化忘れです。ポインタを定義しただけでは、その値は定まっていません。どこをポイントしているのかわからないのですから大変危険です。また、ポインタには整数値を代入することができますが、次のように 0 を代入したらどうなるでしょうか。

リスト : ポインタの危険な操作

int *p;
p = 0;

一般に、0 番地からある番地までは OS が使用するため、ユーザーが勝手にアクセスすることはできません。近代的な OS の場合、メモリはシステムエリアとユーザーエリアに区別されていて、ユーザーのプログラムがシステムエリアにアクセスすることを禁止しています。もし、このメモリにアクセスすると OS で例外 (エラー) が発生します。

このように、ポインタの操作には危険がつきまとうのですが、そのかわりに、ハードウェアを制御するプログラム [*6] でも、C言語だけで作ることができます。このため、C言語は高級アセンブラとか汎用アセンブラと呼ばれています。

-- note --------
[*2] アドレス演算子といいます。

[*3] これは一般的な話で、Cコンパイラの最適化によって「レジスタ」に割り当てられることもあります。レジスタ (register) とは、CPU 内部にある一時記憶メモリのことです。一般に、レジスタはメモリよりも高速にアクセスすることができるので、変数はメモリよりもレジスタに割り当てた方がプログラムを高速に実行できます。

[*4] これを「間接参照」といいます。マシン語でいえば「間接アドレッシング」ですね。C言語では * を間接参照演算子と呼びます。

[*5] 実は単純な加減算ではなく、データのサイズが考慮されます。たとえば、int *p というポインタに p++ という操作を行うと、p に格納されているアドレスの値は +1 されるのではなく、int の大きさである 4 が加えられます。このことにより、配列のようにデータが連続して配置されている領域では、p++ だけで次のデータをポイントすることができるわけです。

[*6] メモリマップド I/O という考え方を採用している CPU では、ハードウェアとの入出力 (I/O) は、特定のメモリ (本当はメモリではないがメモリと同じようなアクセスが可能) を介して行われます。C言語であれば、ポインタを使うことで特定のメモリにアクセスすることが可能です。

●リファレンスとは?

Perl なのになんでC言語のポインタを説明するのか、と疑問に思われたことでしょう。実は、C言語のポインタは単純明解で、メモリの概念をきちんと把握しておけば、理解するのはそれほど難しいことではありません。そして、ポインタの基本を押さえておくと、Perl のリファレンス (reference) は簡単に理解できるのです。リファレンスはポインタと同じくらい簡単に扱うことができますが、ポインタと違って安全です。

Perl のリファレンスは、C言語のポインタと同じく、あるメモリ領域を指し示すデータのことです。C言語の場合、メモリ領域のアドレスをそのまま使いましたが、 Perl ではリファレンスを、数値や文字列と同じスカラー型データとして扱います。その使い方はポインタとよく似ています。それでは、さっそくリファレンスを使ってみましょう。

my $i = 0x100000;

変数 $i には 0x100000 がセットされています。この変数を指し示すリファレンスを作るには、変数名の前に \ (バックスラッシュ) を付けるだけです。

my $p = \$i;

これで変数 $p には、変数 $i を指すリファレンスが格納されます。これはC言語の & 演算子とよく似ていますね。リファレンスはスカラー型データなので、数値や文字列と同じく変数に格納することができます。実際に $p の内容を出力すると、次のようになります。

print $p;      # たとえば SCALAR(0x8506b94) と表示

このように、参照先のデータ型とアドレスを表示しますが、C言語のポインタと違って、アドレスを操作することはできません。また、アドレスの値は実行環境によって異なります。まあ、リファレンスそのものを表示する意味はあまりないのですが、最低限 print で画面に表示できないと、デバッグのときに困ってしまいます。

リファレンス先の値にアクセスする場合も簡単です。変数名の前に $ をつけるだけです。

print $$p;    # 1048576 (0x100000 の 10 進表示) を表示

Perl ではリファレンス先の値を求めることを「デリファレンス (dereference)」 [*7] といいます。変数名の前に $ をつけてアクセスするところは、C言語の間接参照演算子 * と似ていますね。もちろん、$$p に値を代入することもできます。

$$p = 1000;
print $$p;        # 1000 と表示
print $i;         # 1000 と表示

このように、変数 $i の値が変わるのもポインタと同じです。

この例では、数値と文字列を格納するスカラー変数へのリファレンスを作成しましたが、このほかにも、数値や文字列の定数、配列、連想配列、関数、まだ説明していませんが「型グロブ」など、ほとんどのデータ [*8] に対してリファレンスを作成することができます。どのデータでも変数名に \ をつけることでリファレンスを作成できますが、デリファレンスはデータによって異なります。

-- note --------
[*7] 耳慣れない言葉ですが、ようするに間接参照のことです。参考文献 2. では「デリファレンス」とカタカナで表記しているので、本ページもそのように書くことにします。

[*8] 例外がファイルハンドルです。ファイルハンドルへのリファレンスが必要な場合は、型グロブへのリファレンスで代用します。また、Perl 5 にはファイルハンドルの操作に便利なライブラリが用意されています。

●配列へのリファレンス

スカラー変数のほかによく使われるのが、配列と連想配列へのリファレンスです。まず、配列へのリファレンスから説明します。

my @a = (10, 20, 30);
my $ra = \@a;          # $ra は @a へのリファレンス

print @$ra;         # 配列全体を表示
print $$ra[0];      # 0 番目の要素 10 を表示
print $ra->[0];     # 矢印演算子

リファレンスの生成は簡単ですね。@a の前に \ をつけるだけです。デリファレンスですが、配列全体にアクセスする場合は $ra の前に @ をつけます。配列の要素にアクセスする場合は $ をつけます。これは $ra を配列名と考えれば、いままでと同じです。

リファレンスを使って配列の要素にアクセスする場合、矢印演算子 -> を使うと便利です。$$ra[0] では、$ra をデリファレンスするのか、$ra[0] をデリファレンスするのか、明確ではありません。$ra->[0] と書けば、$ra は配列へのリファレンスで、その 0 番目の要素をアクセスすることが明確になります。

このほかに、ブロック { } を使う方法があります。$$ra[0] は ${$ra}[0] と同じです。まず、ブロックの中が評価され、それが配列へのリファレンスであれば、その 0 番目の要素にアクセスします。$ra[0] をデリファレンスしたいのであれば、${$ra[0]} とすればいいのです。たとえば、配列にリファレンスを格納することを考えてみましょう。

my ($a, $b, $c) = (10, 20, 30);
my @ra = \($a, $b, $c);         # (\$a, \$b, \$c) と同じ
print ${$ra[0]};                # 10 と表示

配列 @ra は変数 $a, $b, $c のリファレンスを格納しています。リスト ( ) の前に \ をつけると、リストへのリファレンスが作られるのではありません。各要素のリファレンスを格納するリストを生成します。そして、@ra の 0 番目の要素をデリファレンスしたい場合は、$$ra[0] ではなく ${$ra[0]} としなくてはいけません。ご注意くださいませ。

●連想配列へのリファレンス

連想配列 (ハッシュ) へのリファレンスも簡単です。

my %h = (abc => 10, def => 20, ghi => 30);
my $rh = \%h;
print %$rh;            # ハッシュ全体を表示
print $rh->{"abc"};    # 10 と表示、$$rh{"abc"} と同じ

リファレンスの生成は簡単ですね。デリファレンスも、ハッシュ全体にアクセスするには、$rh の前に % をつけます。要素にアクセスする場合も、矢印演算子 -> を使えば簡単です。ハッシュですから、要素のアクセスには { } を使います。

ところで、配列やハッシュの要素もスカラー型データを格納できるのですから、そのリファレンスも生成することができます。次の例を見てください。

@a = (10, 20, 30);
$ra1 = \$a[1];       # $a[1] へのリファレンス
print $$ra1;         # 20 を表示する
$$ra1 = 200;         # $a[1] に 200 が代入される

%h = ("abc", 10, "def", 20, "ghi", 30);
$rha = \$h{"abc"};   # $h{"abc"} へのリファレンス
print $$rha;         # 10 を表示する
$$rha = 100;         # $h{"abc"} に 100 が代入される

$$ra1 や $$rha に値を代入すれば、当然ですが配列やハッシュの要素も、値が書き換えられます。

●データの探索

次はリファレンスの具体的な使い方を紹介しましょう。すぐに思いつくのが、リファレンスを使って配列や連想配列を関数に渡すことです。簡単な例題として、データの探索処理を作ってみましょう。

データの探索とは、データの集まりの中から特定のデータを見つける処理のことです。データの探索はプログラムの中で最も基本的な操作の一つです。たとえば配列からデータを探す場合、いちばん簡単な方法は先頭から順番にデータを比較していくことです。これを「線形探索 (linear searching)」といます。次のリストを見てください。

リスト : データの探索 (sample0701.pl)

use strict;
use warnings;

sub find {
    my ($n, $ra) = @_;
    foreach my $x (@$ra) {
	return 1 if ($n == $x);
    }
    0;
}

sub position {
    my ($n, $ra) = @_;
    for (my $i = 0; $i < @$ra; $i++) {
	return $i if ($ra->[$i] == $n);
    }
    -1;
}

sub count {
    my ($n, $ra) = @_;
    my $c = 0;
    foreach my $x (@$ra) {
	$c++ if ($n == $x);
    }
    $c;
}

my @a = (1, 2, 3, 4, 5, 2, 3, 4, 3, 6);
print find(6, \@a), "\n"; 
print find(3, \@a), "\n"; 
print find(1, \@a), "\n"; 
print find(0, \@a), "\n"; 
print position(6, \@a), "\n"; 
print position(3, \@a), "\n"; 
print position(1, \@a), "\n"; 
print position(0, \@a), "\n"; 
print count(6, \@a), "\n"; 
print count(4, \@a), "\n"; 
print count(3, \@a), "\n"; 
print count(0, \@a), "\n"; 
$ perl sample0701.pl
1
1
1
0
9
2
0
-1
1
2
3
0

関数 find は配列 @$ra の中から引数 $n と等しいデータを探します。foreach 文で配列の要素を一つずつ順番に取り出して $n と比較します。等しい場合は 1 を返します。ループが終了する場合は $n と等しい要素が見つからなかったので 0 を返します。関数 position は、データを見つけた場合はその位置 $i を返し、見つからない場合は -1 を返します。

position は最初に見つけた要素の位置を返しますが、同じ要素が配列に複数あるかもしれません。関数 count は等しい要素の個数を数えて返します。局所変数 $c を 0 に初期化し、$n と等しい要素 $x を見つけたら c の値を +1 します。最後に $c の値を返します。

このように、線形探索は簡単にプログラムできますが、大きな欠点があります。データ数が多くなると処理に時間がかかるのです。近年、パソコンの性能は著しく向上しているので、線形探索でどうにかなる場合もありますが、データ数が多くて時間かかかるのであれば、高速な探索アルゴリズム (ハッシュ法や二分探索木など) の使用を検討してみてください。

●無名の配列

今まで説明してきたリファレンスは、指し示すデータの実体がないと生成することができません。プログラムの実行時に新しい配列を割り当てることができると便利な場合があります。Perl 5 では角カッコ [ ] を使うことで、新しい配列を生成 [*9] することができます。簡単な使用例を示しましょう。

my $ra1 = [];         # 空の配列を生成してリファレンスをセット
my $ra2 = [10,20];    # 配列を生成して初期化する

my @a = (10, 20, 30);
my $ra3 = [@a];       # 配列 @a の内容で初期化

[ ] で生成される配列には名前が付いていないので「無名の配列」と呼ばれます。名無しの配列といって甘く見てはいけません。今までの Perl は、スカラー (数値、文字列) と配列や連想配列といった単純なデータしかありませんでした。このため、複雑なデータ、たとえば多次元配列を表現するにも連想配列で代用したり、配列を文字列に変換するといった工夫が必要でした。ところが、無名の配列とリファレンスを組み合わせることで、複雑なデータ構造でも簡単に扱うことができるようになります。

-- note --------
[*9] C言語では、標準ライブラリ関数 malloc() を使って実現することができます。Perl 5 では配列のほかにも、「無名のハッシュ」や「無名の関数」を作ることができます

●クロスリファレンスの改良

最後に簡単な例題を示しましょう。正規表現 (前編) でクロスリファレンスを作成しました。このプログラムでは、行番号を文字列として連結し、再度それを行番号に分解するので、それだけ余分に時間がかかりました。また、ひとつの行に同じキーワードが複数個あると、その個数だけ行番号を出力してしまう、という不具合がありました。これをリファレンスを使って改良してみましょう。

まず、元のプログラムリストを示します。

リスト : クロスリファレンスの作成 (cref.pl)

use strict;
use warnings;

my $pattern = shift;
my $filename = shift;
my %word_table = ();
open my $in, $filename or die "Can't open file: $filename\n";

# 探索処理
while (<$in>) {
    while (/$pattern/o) {
        if($word_table{$&}){
            $word_table{$&} .= " $.";   # 文字列として連結する
        } else {
            $word_table{$&} = "$.";
        }
        $_ = $';                        # 残りの文字列をセットする
    }
}
close $in;

# 表示処理
foreach my $word (sort(keys(%word_table))) {
    print "$word\n";
    my $count = 0;
    foreach my $num (split(/ /, $word_table{$word})) {
        printf("%8d", $num);
        if (++$count >= 8) {
            print "\n";
            $count = 0;
        }
    }
    print "\n";
}

改良するところは行番号をセットする処理と、行番号を出力する処理です。行番号は無名の配列に格納し、そのリファレンスをハッシュに格納します。プログラムは次のようになります。

リスト : クロスリファレンスの作成 (cref1.pl)

use strict;
use warnings;

my $pattern = shift;
my $filename = shift;
my %word_table = ();
open my $in, "<", $filename or die "Can't open file: $filename\n";
while (<$in>) {
    while (/$pattern/o) {
        my $ra = $word_table{$&};
        if ($ra) {
            if ($ra->[$#$ra] != $.) {
                push @$ra, $.;
            }
        } else {
            $word_table{$&} = [$.];
        }
        $_ = $';                        # 残りの文字列をセットする
    }
}
close $in;
  
foreach my $word (sort(keys(%word_table))) {
    print "$word\n";
    my $count = 0;
    my $ra = $word_table{$word};
    foreach my $num (@$ra) {
        printf("%8d", $num);
        if (++$count >= 8) {
            print "\n";
            $count = 0;
        }
    }
    print "\n";
}

$word_table{$&} の値を変数 $ra にセットします。$ra が偽であれば、そこに行番号をセットします。行番号は特殊変数 $. で求めることができました。[ $. ] で無名の配列を生成して行番号 $. で初期化し、そのリファレンスを $word_table{$&} にセットします。

$ra が真であれば、すでに行番号がセットされています。配列のいちばん最後に格納されている行番号と $. を比較し、異なる番号であれば配列に格納します。$#$ra は、変数 $ra に見慣れない記号がついていますね。配列名の前に $# をつけると、配列の最後の添字を表す特殊変数となります。簡単な例を示しましょう。

my @a = (10, 20, 30);
print $#a;           # 2
print $a[$#a];       # 30

配列 @a には 3 つの要素が格納されているので、$#a の値は 2 となります。そして、添字に $#a を使うことで、最後の要素 30 にアクセスすることができます。この特殊変数は参照するだけではなく、値を代入することで配列の長さを変更することができます。配列が短くなった場合、その部分の値は破棄されることになります。

$# は配列へのリファレンスにも適用できます。$ra が配列へのリファレンスであれば、$#$ra で最後の要素の添字を参照できます。したがって、$ra->[$#$ra] は最後の要素を参照することになります。この行番号と $. が異なっていれば、$. を配列に格納します。これは push を使えば簡単です。このように、リファレンスを使えばハッシュの中にも配列を格納できるので、同一の行番号のチェックも簡単に行えます。

行番号を表示するときは、%word_table からリファレンスを取り出して変数 $ra にセットします。あとは、foreach で配列から要素を取り出して出力するだけです。デリファレンスには -> を使っています。これで修正は終了です。興味のある方は実際にプログラムを実行して、動作を確認してみてください。

今回はここまでです。次回はリファレンスの続きで、「無名のハッシュ」や「関数へのリファレンス」など、高度な使い方を説明します。


初版 2015 年 4 月 26 日
改訂 2023 年 3 月 19 日

Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | Perl | NextPage ]