┌──────────────────┐
│Perl入門 ~構造化プログラミング編~ │
│(4)サブルーチン:引数/戻り値 │
│2005/10/19 │
└──────────────────┘
[▲Perl Home▲]
●サブルーチンへの引数/戻り値(1)
◆リスト「@_」
・前回は変数の有効範囲について話をしましたが、今回はそれらの値を関数間でや
り取りする方法について考えて行きたいと思います。
・サブルーチン呼び出しの際、サブルーチン名の横に記述された値/変数は特殊な
リスト変数@_によって、サブルーチンのブロックへ引き渡されます。
┌──────────────────────────────────┐
#!/usr/local/bin/perl -w
#注意!! このコードは実用的ではありません!!
use strict;
{ # クロージャ : $a,$bの有効範囲を制限するために使いました
my $a = 10;
my $b = 20;
print "実行前 : \$a = $a, \$b = $b\n";
&SubTwice($a, $b);
print "実行後 : \$a = $a, \$b = $b\n";
}
sub SubTwice {
for (my $i=0; $i<@_; $i++) { # @_リストは引数の値を持つ
print "サブルーチン内_引数値 [$i] = $_[$i]\n";
$_[$i] *= 2; # 2倍に
print "サブルーチン内_処理後 [$i] = $_[$i]\n";
}
}
──────────────────────────────────
[実行結果]
実行前 : $a = 10, $b = 20
サブルーチン内_引数値 [0] = 10
サブルーチン内_処理後 [0] = 20
サブルーチン内_引数値 [1] = 20
サブルーチン内_処理後 [1] = 40
実行後 : $a = 20, $b = 40 # 値が変わってる!!
└──────────────────────────────────┘
・上記のコードは、引数$a,$bの値を、サブルーチンSubTwice内で受けた後、2倍に
する処理をしています。ここで注意して欲しいのは、サブルーチン実行後、その
値を呼び出し側へ返してもいないのに、値が変化しているということです。
・つまり「@_」リストの重要な性質として、「引数のリファレンスを渡す」という
ものがあります。このままだと、呼び出し側とサブルーチン側で処理と変数の分
離ができないので、構造化記述が苦しいですね。
・そこで変数をサブルーチン内で独立に扱うため、前回話をした「my関数」を使う
ことになります。以下に、その場合の例を示します。
┌──────────────────────────────────┐
#!/usr/local/bin/perl -w
use strict;
{ # クロージャ : $a,$bの有効範囲を制限するために使いました
my $a = 10;
my $b = 20;
print "実行前 : \$a = $a, \$b = $b\n";
&SubTwice($a, $b);
print "実行後 : \$a = $a, \$b = $b\n";
}
sub SubTwice {
my ($a, $b) = @_;
print "サブルーチン内_処理前 \$a = $a\n";
$a *= 2;
print "サブルーチン内_処理後 \$a = $a\n";
print "サブルーチン内_処理前 \$b = $b\n";
$b *= 2;
print "サブルーチン内_処理後 \$b = $b\n";
}
──────────────────────────────────
[実行結果]
実行前 : $a = 10, $b = 20
サブルーチン内_処理前 $a = 10
サブルーチン内_処理後 $a = 20
サブルーチン内_処理前 $b = 20
サブルーチン内_処理後 $b = 40
実行後 : $a = 10, $b = 20
└──────────────────────────────────┘
◆サブルーチンから値を戻す
・サブルーチン内の変数を呼び出し側と独立化できるようになりました。すると、
今度はサブルーチンの処理結果を戻す方法について考えなければなりません。こ
れには組み込み関数の「return()」を使用します。
・return()は引数を以下のように戻すことができます。
return($a); # スカラー値 $a を戻します
return(@a); # リスト @a を戻します
return(%a); # ハッシュ %a を戻します
return(); # undefを戻します
return($a, $b); # ($a, $b)をリストとして戻します
return(@a, @b); # (@a, @b)が平坦化された単一のリストを戻します
return(%a, %b); # (%a, %b)が平坦化された単一のハッシュを戻します
・いくつかの記述例を示してみます。
┌──────────────────────────────────┐
#!/usr/local/bin/perl -w
use strict;
{
my $sc1 = &SubRetSc1();
print "\$sc1:$sc1\n";
my ($sc20, $sc21) = &SubRetSc2(); # リストで受ける
print "\$sc20:$sc20, \$sc21:$sc21\n";
my %hash2 = &SubRetHash2(); # 平坦化ハッシュ
foreach my $name (sort(keys(%hash2))) {
print "key:$name, val:$hash2{$name}\n";
}
}
sub SubRetSc1 {
my $ret_sc1 = 'sc1_val';
return($ret_sc1);
}
sub SubRetSc2 {
my $ret_sc20 = 'sc20_val';
my $ret_sc21 = 'sc21_val';
return($ret_sc20, $ret_sc21);
}
sub SubRetHash2 {
my %a = ('a0'=>'val_a0', 'a1'=>'val_a1');
my %b = ('b0'=>'val_b0', 'b1'=>'val_b1');
return(%a,%b);
}
──────────────────────────────────
[実行結果]
$sc1:sc1_val
$sc20:sc20_val, $sc21:sc21_val
key:a0, val:val_a0
key:a1, val:val_a1
key:b0, val:val_b0
key:b1, val:val_b1
└──────────────────────────────────┘
・カンの良い方はすでに気付かれていると思いますが、複数のリスト(@)やハッシュ
(%)を戻した場合、それは「平坦化」されます。このままでは別々のリスト/ハッ
シュとして値を戻すことができません。
・このことはサブルーチンが引数を受け取る場合も同様です。このようなときには
以前話をしたリファレンスを使用します。
●サブルーチンへの引数/戻り値(2)
◆リファレンスの使用
・さて、リストやハッシュ等の構造化データを、サブルーチンと複数個やり取りす
るにはリファレンスを使用します。リストがサブルーチンはリストを経由するこ
とは変わらないので、リストの各要素を構造化データのリファレンスとする方法
を取ります。
・下記例を見て頂くのが手っ取り早いでしょう。
┌──────────────────────────────────┐
#!/usr/local/bin/perl -w
use strict;
{
my @lista = ('a0', 'a1', 'a2');
my @listb = ('b0', 'b1', 'b2');
my ($rac, $rbc) = &ChangeList(\@lista, \@listb);
# |~~~~~~~~~ |~~~~~~~~~~~~~~~
# リファレンス受け リファレンス渡し
for (my $i=0; $i<@{$rac}; $i++) {
print "$i : $lista[$i] : ";
print $rac->[$i],"\n";
}
for (my $i=0; $i<@{$rbc}; $i++) {
print "$i : $listb[$i] : ";
print $rbc->[$i],"\n";
}
}
sub ChangeList {
my ($ra, $rb) = @_; # リファレンス受け
# デリファレンスしてローカル処理
my @la = @{$ra};
my @lb = @{$rb};
for (my $i=0; $i<@la; $i++) {
$la[$i] .= '_a_change';
}
for (my $i=0; $i<@lb; $i++) {
$lb[$i] .= '_b_change';
}
return(\@la, \@lb); # リファレンス戻し
}
──────────────────────────────────
[実行結果]
0 : a0 : a0_a_change
1 : a1 : a1_a_change
2 : a2 : a2_a_change
0 : b0 : b0_b_change
1 : b1 : b1_b_change
2 : b2 : b2_b_change
└──────────────────────────────────┘
・上記の例では、サブルーチン内でデリファレンスし、実質的には「値渡し」とし
ていますが、リファレンスのまま処理を続ければ、元のデータ処理が加えられる
いわゆる「参照渡し」になります。
◆デフォルト値有りの名前付き引数
・特別な機能ではありませんが、サブルーチンの引数にハッシュを適用することで
「名前付きの引数」を使うことができます。
&SubTest (name=>'Taro', hobby=>'ski');
:
sub SubTest {
my %hash = @_;
:
}
・これに加え、後から指定した「key+value」が有効になるハッシュの性質と、平
坦化を利用すれば、デフォルト値有りの名前付き引数を実現できます。
┌──────────────────────────────────┐
#!/usr/local/bin/perl -w
use strict;
{
&SubParaHash(
name => 'taro', # 名前付き引数記述
job => 'musician'
);
}
sub SubParaHash {
my %defval = ( # デフォルト値定義
name => 'null',
job => 'office worker',
hobby => 'ski', # デフォルト趣味がスキー(笑)
sex => 'man'
);
my %para = (%defval, @_); # 平坦化してハッシュ受け
# 後指定(引数)のKey+Valueが有効になる
foreach my $buf (sort(keys(%para))) {
print "$buf\t: $para{$buf}\n";
}
}
──────────────────────────────────
[実行結果]
hobby : ski
job : musician
name : taro
sex : man
└──────────────────────────────────┘
●リファレンスカウント
◆メモリデータの解放
・プログラミング一般の話ですが、プログラムは動作中に何らかのメモリ領域を確
保します。例えば今回のようにサブルーチンを使用すれば、そのサブルーチンが
呼び出されるた度に必要なメモリが確保されていきます。
・しかし、サブルーチンは呼び出し回数に制限が設けられている訳では無いので、
ある回数以上サブルーチンを実行すると、メモリ資源を使い果たしてプログラム
が落ちてしまいます(*1)。
・通常はこれを避けるために「有効範囲が終了した変数は解放する」という仕組み
が入っています。これを一般にガベージコレクション(garbage collection)と呼
びます(*2)。
・しかし、サブルーチンから抜けた後も必要なデータが存在します。C言語で言う
ところのヒープ領域から確保するダイナミックなデータ等がそれに相当しますが
この種データ解放の仕組みは言語によりかなり異なります(*3)。
・Perlでは幸運なことに、動的に確保したメモリでも、自動的に解放されます。つ
まりプログラマは、メモリの解放を意識しなくても大丈夫です。
◆リファレンスカウント:ダイナミックなデータ確保
・ならば、サブルーチン間で必要とされる間はずっと有効になるダイナミックなデ
ータを、Perlではどのように「確保」するのでしょうか。それにはリファレンス
を用います。
・Perlでは有効範囲の終わった変数は自動的に解放されますが、そのデータがリフ
ァレンスを介して文字通り参照されている間は、解放の対象から外されます。こ
れをリファレンスカウントと呼びます。つまり、内部でデータがリファレンスさ
れている回数をカウントしていて、それが0より大きいうちは消さないという仕
組みです。
・説明が長くなってしまいましたが、下記の例を見ればわかると思います。
┌──────────────────────────────────┐
#!/usr/local/bin/perl -w
use strict;
{
my $ref0 = &SubRefCount(); # 1回呼び出してリファレンス受け
my $ref1 = &SubRefCount(); # もう1回呼び出してリファレンス受け
# ただし$ref0とは別のデータになる
for (my $i=0; $i<@{$ref0}; $i++) {
$ref0->[$i] =~ s/data/DATA/; # $ref0側のみ変更
print "$i ref0 : ",$ref0->[$i],' <=> ';
print "$i ref1 : ",$ref1->[$i],"\n";
}
}
sub SubRefCount {
# @datas は my でサブルーチン内のスコープ
# 普通は消えてしまうが...
my @datas = ('data0', 'data1', 'data2');
# @datasのリファレンスを返している
return(\@datas); # リファレンスを戻す
}
──────────────────────────────────
[実行結果]
0 ref0 : DATA0 <=> 0 ref1 : data0
1 ref0 : DATA1 <=> 1 ref1 : data1
2 ref0 : DATA2 <=> 2 ref1 : data2
└──────────────────────────────────┘
・上記コードの実行結果より
+ SubRefCount内で生成された @datas のデータは、サブルーチン終了後も消
えていない。
+ 更に複数回呼び出した場合は、別のデータが生成される。
ことがわかったと思います。
・この機能を使えば、データ読み込み/確保部についても容易にコードを構造化で
きますし、リファレンスの使用範囲を工夫すれば、メモリ確保/解放の効率も良
くなります。
・Perlの場合、このリファレンスカウントを用いてダイナミックに確保したデータ
を容易に管理できるとともに、データ自身に対する要素の追加/削除すらもでき
てしまうので、C++等に比べると(*4)このあたりのフレキシビリティは飛び抜け
て高いということが言えるでしょう。
・どうでしょうか。そろそろプログラミング言語としてのPerlの良さを感じ始めて
頂けているのではないかと思います。次回からは、他のPerlコードを利用するor
利用させるための仕組みとして「package」に話を移して行きたいと思います。
(*1)これをプログラミングにおいて一般にメモリリークと呼びます。デバイス屋さんだ
と「リーク電流」とか「refresh time」とかが頭に浮かんじゃうかもしれませんが
...(^_^;
(*2)ここでは、解放することを一般にgarbage collectionと書いてしまいましたが、サ
ブルーチン/関数内の自動変数解放はgarbage collectionとは異なります。サブル
ーチン/関数のスタック解放と同時に消えるので。
(*3)例えば懐かしのN88-BASICではERASE文で配列を解放していましたね。C++ではnewで
確保したデータをdeleteで解放します。Perlでは、これが自動的に行われることに
なります。
(*4)C++でもauto_prやSTLを使用することで、自由度は上がっていますが、その意味を
理解するのに敷居が高い傾向にあると感じています。
[▲Perl Home▲]
Copyright(c)2005 Monpe
All Rights Reserved