本連載は第一線のPerlハッカーが回替わりで執筆していきます。今回は『初めてのPerl』(通称リャマ本)(注1)、『プログラミングPerl』(通称ラクダ本)(注2)の訳者である近藤嘉雪さんで、テーマはリファレンスです。
リファレンスとは?
今回は、Perlを学ぶ際に避けて通ることができないリファレンス(reference)について説明しましょう。
リファレンスとはどんなものでしょうか? リファレンスはスカラ値の一種で、ほかのデータのことを指しているデータです(C言語のポインタに相当します)。リファレンスを利用すれば、連結リスト、木構造、グラフのような高度なデータ構造を表現できます。
Perlでは、変数の前に逆スラッシュを置くことによって、その変数を指すリファレンスを得ることができます。たとえば次のコードによって、変数$srefには、スカラ変数$sを指すリファレンスが代入されます。
$sref = \$s; # スカラを指すリファレンス
同様にして、配列、ハッシュ、サブルーチンへのリファレンスを取得できます。
$aref = \@a; # 配列を指すリファレンス
$href = \%h; # ハッシュを指すリファレンス
$fref = \&my_subroutine;
# サブルーチンを指すリファレンス
これらのコードからわかるように、リファレンス自体はスカラ値の一種なので、数値や文字列と同様にスカラとして扱うことができます。ここでは、取得したリファレンスをスカラ変数に代入しています。
デリファレンス
リファレンスをたどって指しているデータにアクセスすることをデリファレンス(dereference)と言います。デリファレンスを行うには、変数の種別を表すプレフィックス文字$、@、%、&など[3]を使います。
スカラのデリファレンス
スカラへのリファレンスに対してデリファレンスを行うには、リファレンスを表す式(ここではスカラ変数$sref)の直前に「$」を付けて、$$srefと書きます。また、スカラとしてデリファレンスした結果は、スカラ変数として扱うことができます。
$sref = \$x; # スカラへのリファレンスを取得
$y = $$sref; # スカラ値にアクセス
リスト1をもとに説明しましょう。まず1行目で変数$xに100を代入しています。2行目では、変数$xへのリファレンスを取得して、変数$srefに代入しています。4~6行目では「$$sref」という部分でデリファレンスを行っています。
リスト1 スカラをデリファレンスする
1:my $x = 100;
2:my $sref = \$x; # スカラリファレンスを$sref に代入
3:
4:print "$$sref $x\n"; # 100 100 と表示する
5:$$sref += 5;
6:print "$$sref $x\n"; # 105 105 と表示する
「$$sref」は、$srefに入っているリファレンスに対してデリファレンスを行います。$srefには2行目で変数$xへのリファレンスを代入しているので、$$srefは変数$xを表すことになります。
4行目と6行目のprint文では、$$srefと$xの値を表示します。4行目では、$xの値は100なので「100 100」と表示します。次に5行目の「$$sref += 5」では、$srefをデリファレンスしたもの(つまり$x)に対して5を加えます。その結果、6行目では「105 105」と表示します。ここで、$$srefに5を加えると、変数$xの値も100から105に変わることに注目してください。
配列のデリファレンス
配列としてデリファレンスする場合には、リファレンスを表す式(ここでは$aref)の直前に「@」を付けて、@$arefと書きます。配列を書ける場所ならどこにでも@$arefを置くことができます。
$aref = \@array; # 配列へのリファレンスを取得
@s = @$aref; # 配列全体にアクセス
$$aref[10] = "ten"; # 配列の要素にアクセス
上記の2行目では配列代入を行っています。実際には@arrayの全要素が@sにコピーされます。3行目は配列の要素にアクセスする例です。ここでは配列@arrayの要素$array[10]に文字列"ten"を代入しています。
また、次のように、foreach文で配列の要素を順に処理するという使い方もできます。
foreach my $elem (@$array_ref) {
# $elem を使って処理を行う
}
リスト2では、配列リファレンスを受け取って、要素の和を返す関数compute_sumを定義しています。この関数は引数として、配列ではなく配列へのリファレンスを受け取ることに注意してください。
リスト2 関数compute_sum:配列リファレンスを受け取って、要素の和を返す
1:my @tmp_array = (1, 3, 5, 7, 9);
2:print "Total: ", compute_sum(\@tmp_array), "\n";
3:
4:sub compute_sum {
5: my ($aref) = @_; # リファレンスを$aref へ代入
6: my $sum = 0;
7: foreach (@$aref) { # 配列をデリファレンス
8: $sum += $_;
9: }
10: return $sum;
11:}
5行目では、引数として渡された配列リファレンスを変数$arefに代入しています。6行目では、和を計算していくための変数$sumを宣言しています。7~9行目のforeachループで、配列のすべて要素の和を求めます。7行目のforeachの括弧の中にある「@$aref」という部分で、デリファレンスを行っています。
1行目で配列@tmp_arrayを初期化してから、2行目でcompute_sum 関数を呼び出します。配列@tmp_arrayを関数compute_sumに渡す際にはリファレンスを渡す必要があるので、先頭にバックスラッシュを付けて「\@tmp_array」としていることに注意しましょう。バックスラッシュを忘れて単に「@tmp_array」と書くと、配列@tmp_arrayの全要素が渡されてしまいます。
ハッシュのデリファレンス
ハッシュとしてデリファレンスする場合には、リファレンスを表す式(ここでは$href)の直前に「%」を付けて、%$hrefと書きます。ハッシュを書ける場所ならどこにでも%$hrefを置くことができます。
$href = \%h; # ハッシュへのリファレンスを取得
$x = $$href{'a'}; # ハッシュの要素にアクセス
上記の2行目では、ハッシュの要素$h{'a'}の値を変数$xに代入しています。
また、次の例は、keys関数によってハッシュのすべてのキーを取り出して順に処理します。
foreach (keys %$href) {
....
}
サブルーチン(関数)のデリファレンス
サブルーチン(関数)としてデリファレンスする場合には、「&」を付けて「&$fref」と書きます。引数を渡す場合、後ろに括弧で囲んで引数リストを指定します。
# サブルーチンへのリファレンスを取得
$fref = \&my_func;
&$fref($param); # サブルーチンを呼び出す
サブルーチンへのリファレンスのことをコードレフ(coderef)と呼ぶこともあります。
デリファレンスの失敗
リファレンスを本来の型以外にデリファレンスしようとすると、エラーになります。次の例では、1行目で配列へのリファレンスを変数$arefに代入してから、2行目で「$」を付けてスカラとしてデリファレンスしようとしているので、エラーになります。
$aref = \@array;
$$aref = 10;
このようなケースでは、「Not a SCALAR reference at- line XX.」(スカラリファレンスではない)といったエラーメッセージが表示されます。
リファレンスの基本操作
ref関数──リファレンスかどうかの判定
スカラ値がリファレンスかどうかを判定するには、ref関数を使います。この関数は、引数がリファレンスなら真を、そうでなければ偽を返します。真として返される値は、リファレンスが指しているものの種類を表す文字列になっています(表1)。
表1 ref関数の結果が真のときに返される主な値
リファレンス | REF |
スカラー | SCALAR |
配列 | ARRAY |
ハッシュ | HASH |
サブルーチン | CODE |
型グロブ | GLOB |
オブジェクト | オブジェクトが所属するクラス名 |
リファレンス同士の比較
2つのリファレンスが同じものを指してれば値は等しくなります。ですから、リファレンスが入っている変数$xと$yがあったとき、==演算子で値を比べれば同じものを指しているかどうかがわかります。
# $x と$y にはリファレンスが入っている
if ($x == $y) {
# $x と$y は同じものを指している
} else {
# $x と$y は別のものを指している
}
リファレンスから文字列への変換
リファレンスは、必要に応じて自動的に文字列に変換されます。たとえばリファレンスをprint文の引数として使うと、文字列に変換されたものが表示されます。具体的には「タイプ(16進表記のアドレス)」という文字列になります。たとえばハッシュへのリファレンスならHASH(0xbb59f8)といった感じになります。
なお、逆方向の変換、つまり文字列をリファレンスに変換することはできません。
<続きの(2)はこちらです。>