◆ はじめに
ごく普通の言語(COBOL,C,Java,VB等)を知っている人が、Perlをやり始めるといろいろと悩むことがあります。
それは、以下の感じです。
- 暗号のような記号のオンパレード
- 文脈(コンテキスト)によって意味の違う関数
- 構造体がないデータ構造
- 同名で意味が違う変数
- 比較先がない比較
- 代入文がないのに代入される
- 正規表現の拡張
- return文がないリターン値
- foreach文がいつのまにかfor文に
- サブルーチンなのに関数に
本ページは、上記ようなハードルを一つ一つクリアにしていけたらと思います。
また、全く初めてのPerlプログラミングの人にも、無理なく理解ができるように心がけました。
分かってくると、Perlほど面白く、ラクチンな言語は、たぶんありません。
なので、この際にマスターしてしまいましょう!
尚、ご感想、ご意見、誤字、脱字、間違い等がありましたら遠慮なくPerlノート掲示板へご指摘ください。
◆ サイト最新情報
◆ インデックス
- 【その0】プログラミングの基礎の基礎
- 【その1】変数等の頭文字って何を意味するの?
- 【その2】特殊変数 $_ とは
- 【その3】コンテキストで意味が変わる関数
- 【その4】構造体がないデータ構造
- 【その5】foreach文がfor文に
- 【その6】Perlでの文字列
- 【その7】サブルーチンの呼び出し方
- 【その8】サブルーチンのデフォルトのリターン値
- 【その9】単独shiftとは何か?
- 【その10】マッチ演算子の区切り文字
- 【その12】正規表現
- 【その13】リファレンスについて
- 【その14】use宣言について
- 【その15】blessについて
- 【その16】Perlでのオブジェクトについて
- 【その17】Perlでのオブジェクト指向について
◆ コンテンツ
【その0】プログラミングの基礎の基礎
◆ はじめに
今ここを読んでいるということは、プログラミングに興味があり、Perlでもやってみようかなぁ・・・なんて思っているのだと思います。
そのプログラミングは、基礎を知らないで突っ走っても、なかなか上達しないですね。
なので、とりあえず本コンテンツ『Perlノート講座』をひと通り読んでいただければ、プログラミングとPerlの基礎がマスター出来るようになっているのです(たぶん)。
そもそも、プログラミングは、かなり昔、メモリ上に直接命令コードを書いて、それをCPUに読み込ませて、計算したり、プリンタ出力したり・・・、していた時代がありました。
メモリ上に直接命令コードを書くのは、凄く大変なことでした。
そこで徐々に、機械語ができ、アセンブラができ、フォートランやコボルやC言語ができて、今の時代になってPerlやJavaなどが主流になってきたのです。
特にPerlは、私が知る限り、プログラミングが非常にラクチンに出来る言語なのです。
なので、この際、Perlをマスターしてしまいましょう!
まずは基礎の基礎からで、プログラムの6つの基本要素を理解し、そして以下の拡張要素や補助要素をからめて徐々に完成度のあるプログラミングをマスターして参りましょう!
では、スタートです!
◆ プログラムの基本要素
プログラムの基本要素は、以下の6つです(たぶん)。
プログラムを組むときは、この6つの要素を組み合わせます。
- 変数
- 演算子
- 代入文
- 判断文
- 繰り返し文
- リテラル(数値・文字列等)
・変数
変数は以下の3種類があります。
- スカラー変数
- 配列変数
- ハッシュ変数
- 参考:http://d.hatena.ne.jp/chaichanPaPa/20090617/1245243589
・演算子
以下以外にもまだまだ沢山あります。
- 算術演算子(+,-,*,/等)
- 比較演算子(==,>,eq,lt等)
- 代入演算子(=,+=,*=,.=等)等です。
・代入文
代入文は、代入演算子を使って、以下のように変数へ値を代入します。
my $value = 10;
・判断文
判断文は、if文等で、以下のような感じで使用します。
if ($value == 10) { $value += 1; } else { $value += 2; }
・繰り返し文
繰り返し文は、for文やforeach文等で、以下のような感じで使用します。
my @value; for (my $i = 0; $i < 10; $i++) { $value[$i] = $i * 2; }
・リテラル
リテラルとは、プログラムのソースコード中に使用される数値や文字列のことです。
- 文字列リテラルは、ソースコードに書かれた文字列のことを意味します。
- 数値リテラルは、ソースコードに書かれた数値のことを意味します。
◆ プログラムの拡張要素
プログラムは、基本要素の組み合わせで、大体できてしまいます。
しかし、実際は、上記5つの基本要素以外に、以下の拡張要素も組み合わせます。
- 組み込み関数(use宣言なしで使えるシステム関数)
- 標準モジュール関数(use宣言ありで使える標準関数)
- 標準オブジェクト委譲(use宣言ありで使える標準オブジェクト)
- 標準以外(自作やCPAN)モジュール関数(use宣言ありで使える関数)
- 標準以外(自作やCPAN)オブジェクト委譲(use宣言ありで使えるオブジェクト)
◆ プログラムの補助要素
プログラムは未来の自分も含めて、誰にでも分かりやすい、プログラム作りを心がけないとなりません。
そのために以下を熟慮します。
- わかりやすい変数名の命名(小文字とアンダーバーで、$user_name $search_word等)
- わかりやすい関数名の命名(小文字とアンダーバーで動詞_名詞、parse_data() create_table()等)
- わかりやすいコメント
- わかりやすいデータ構造
- わかりやすいアルゴリズム
- 統一されたコーディングスタイルを貫く
◆ プログラミングのはじめの一歩
最後にPerlでのプログラミングのはじめの一歩『Hello World』です。
本当は『print "Hello World";』だけなんですが、上記の要素を入れてみました。
use strict; use warnings; my $strmsg = 'Hello World'; if ($strmsg eq 'Hello World') { for (my $i = 0; $i < length($strmsg); $i++) { print substr($strmsg, $i, 1); } }
【その1】変数等の頭文字って何を意味するの?
他言語、特にオーソドックスなcobol,c,java,vb等の人がPerlをやり始めると謎だらけで悩むことになりますね。
そんな人のために、僭越ながら私が謎を解き明かしていきますよ! Perlerの方は温かく見守ってくださいね(^o^)
◆変数等の頭文字って何を意味するの?
Perlをやり始めると変数等の頭文字($,@,%,&,*,何も無し)が気になります。
『$』が付くシンボルは、スカラー変数で、数値や文字列など何でも入る・・・VBで言えば、variant型のような変数です。
『$』はScalarの『S』に似ているので、スカラー変数を表します。
『@』が付くシンボルは、配列変数で、配列の要素はスカラー変数と同じで、何でも入る。
『@』はarrayの『a』に似ているので、配列変数を表します。
『%』が付くシンボルは、ハッシュ変数で、ハッシュはkeyとvalueを対で持っていて、valueはスカラー変数と同じで、何でも入る。
『%』はkeyとvalueの対を表していて、ハッシュ変数を表します。
『&』が付くシンボルは、サブルーチンで、実は『&』を頭に付けるより、尾に『()』をつけてサブルーチンを明示する方が多いです。
『&』はSubの『S』に似ているので、サブルーチン(これは自信がない)を表します。
(モダンPerlでは、『&』は付けないとのことです。右を参照のこと『第124回 サブルーチンの頭に&は普通つけない』)
『*』が付くシンボルは、型グロブで、頭文字以外同名のシンボルすべてを表します。
『*』と言えば、ワイルドカードですね。
何もつかないシンボルは、ベアワードで、ファイルハンドル、引数のない組み込み関数、ヒアドキュメントの終端値、ラベル、単なる文字列などなど・・・。
◆ファニー文字
ちなみに、これらの頭文字のことをPerl では「ファニー文字(funny character)」とよんでいます。
実はファニー文字を使うことにより、将来使われるであろう予約語やキーワードとかぶることがありません。
他の言語では、かぶる可能性があり、かぶった時に過去の資産が使えなくなるのです。
つまり、Perlでは、ファニー文字を使うことにより、過去の資産が使えなくなることが無いのです。
◆サンプル
$hoge = 123; # スカラー変数 @hoge = (1, 2, 3); # 配列変数 %hoge = (a => 1, b => 2, c => 3); # ハッシュ変数 print "-----&hoge------------\n"; &hoge; # サブルーチン print "-----&foox(*hoge)------\n"; &foox(*hoge); sub hoge { print "$hoge", "\n"; # スカラー変数 print "$hoge[0]$hoge[1]$hoge[2]", "\n";# 配列変数 print "$hoge{a}$hoge{b}$hoge{c}", "\n";# ハッシュ変数 } sub foox { (*foo) = @_; print "$foo", "\n"; # スカラー変数 print "$foo[0]$foo[1]$foo[2]", "\n";# 配列変数 print "$foo{a}$foo{b}$foo{c}", "\n";# ハッシュ変数 print "-----&foo--------------\n"; &foo; # サブルーチン }
◆実行結果
C:\perltest>perl sysbol.pl -----&hoge------------ 123 123 123 -----&foo(*hoge)------ 123 123 123 -----&foo-------------- 123 123 123
上記サンプルは、型グロブと各変数の関係を示すため、すべてグローバル変数になっています。
しかし、モダンPerlではなるべくグローバル変数は使わず、myでローカル変数にして処理するのが推奨です。
また、サブルーチンの『&foo;』も『foo();』の記述の方が推奨です。
【その2】特殊変数 $_ とは
まず、以下のソースを見てください。
use strict; use warnings; while (<DATA>) { if (/^h.*e$/) { print; # hogeを表示する } } __END__ hoge foo
Perl初心者や他言語から来た人が・・・実はかつての自分が、このソースを見た時に???のオンパレードでした。
これを以下のようにすると・・・。
use strict; use warnings; while ($_ = <DATA>) { if ($_ =~ /^h.*e$/) { print $_; # hogeを表示する } } __END__ hoge foo
かなり分かりやすくなりますね。
このように、Perlでは、デフォルトで『$_』という特殊変数を用いるようになっているのです。
そして、『$_』は省略できるのです。
そして、ちょっとベテランになると、省略の方が良くなるのです。
ちなみに、ソースを簡単に解説しときます。
- 『while ($_ = <DATA>) {』 は、__END__の下のテキストをデータとして読み込み、すべて読む込むとゼロが返り、ゼロは偽なのでwhileループを抜ける。
- 『if ($_ =~ /^h.*e$/) {』 は、$_の中身が、hから始まりeで終わる文字列とマッチした場合に真となる。
補足
$_は、普通はブロック内でローカル化されのですが、例外的にwhile文ではローカル化されません。
なので、while文での$_の使用は、注意が必要です。
$_ = 100;
print $_, "\n";
while (<DATA>) {
print $_;
}
print $_, "\n"; # 100の値でなくなる
__END__
aaa
bbb
ccc
【その3】コンテキストで意味が変わる関数
今回は、他言語経験者にとって、Perlでの最大の謎『コンテキスト』です。
まず、Perlでは、スカラー変数と配列変数とハッシュ変数があります。
スカラー変数には、文字列や数値、リファレンス等のスカラー値を代入します。
配列変数やハッシュ変数には、リスト値を一気に代入します。
Perlでは、スカラー変数にスカラー値を代入することをスカラーコンテキストといいます。
また、配列変数とハッシュ変数にリスト値を一気に代入することをリストコンテキストといいます。
つぎに、関数は、引数を入力*1として結果をリターン値として返します。
このリターン値が、代入先の変数のスカラー変数か配列変数またはハッシュ変数かによって、スカラー値を返したり、リスト値を返したりするのです。
つまり、コンテキストによって同じ関数でもリターン値が変わるのです。
まぁ、すべての関数がそうなるわけではないのですが、リスト値を処理する関数は大体そうなります。
◆サンプル(localtime.pl)
use strict; use warnings; my $localtime = localtime(time); # スカラーコンテキスト print "$localtime \n"; my @localtime = localtime(time); # リストコンテキスト print "@localtime \n"; #以下もリストコンテキスト my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); local ($, , $\) = (' ', "\n"); print($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
◆サンプル実行結果
C:\perltest>perl localtime.pl Sun Jun 28 07:31:27 2009 27 31 7 28 5 109 0 178 0 27 31 7 28 5 109 0 178 0
◆補足1
実は、関数だけでなく、以下のような単純な代入でもコンテキストがあります。
use strict; use warnings; my $localtime = localtime(time); # スカラーコンテキスト my @localtime = localtime(time); # リストコンテキスト my $x = @localtime; # スカラーコンテキスト print $x, "\n"; # 配列の要素数(9)が表示 my @x = $localtime; # リストコンテキスト print @x, "\n"; # スペースをデリミタにしたリストで取り込み、配列にて表示(Sun Jun 28 07:42:39 2009)
◆補足2
コンテキストでリターン値が変わる主な関数達
- gmtime
- localtime
- unpack
- grep
- map
- reverse
- sort
- splice
- split
- keys
- values
- glob
こうやって見ると、リストコンテキストではリスト値を返す便利な関数が、スカラーコンテキストでは、その要素数を返すケースが多いようです。
◆補足3
上記は、スカラーコンテキストとリストコンテキストの2つでしたが、実は、まだ違う種類のコンテキストがあります。
大きく分けて、スカラーコンテキストとリストコンテキストとボイドコンテキストの3つあり、さらにスカラーコンテキストにはブール値、文字列、数値、不定の4つがあります。
- スカラーコンテキスト
- ブール値コンテキスト
- 文字列コンテキスト
- 数値コンテキスト
- 不定コンテキスト(文字列、数値どちらでも構わない場所や状況)
- リストコンテキスト
- ボイドコンテキスト(戻り値を必要としない場所や状況)
*1:もちろん、出力のときもある
【その4】構造体がないデータ構造
普通の言語、たとえば、C言語とかVB等では、データ構造を定義できる構造体があります。
もっと昔だと、構造体がなく、すべて配列で処理する言語もありました(昔のコボルとかベーシック)。
で、perlのデータ構造なのですが、ご存知の通り、スカラー、配列、ハッシュの3種類しかありません。
そう、構造体がないのです。
では、他言語で構造体処理のクセを身に付けてしまった人は、どうすればよいのでしょうか・・・。
実は、構造体は、ハッシュで表現ができるのです。
構造体の配列も、同じく、ハッシュの配列で表現ができるのです。
以下にC言語の構造体とPerlのハッシュ、そして、構造体配列とハッシュの配列のサンプルを書いてみました。
たしかに、C言語の構造体は、Perlのハッシュで表現ができていますね!
◆サンプル:C言語の構造体
struct db_login { char svnm[7]; char svnm2[9]; char loginid[31]; char password[31]; char db_string[45]; }; struct db_login shisha={"svr00X", "X支社", "id", "passwd", "svr00Xs"]; printf("hostname=%s\n", &shisha.svnm[0]); printf("hostname2=%s\n", &shisha.svnm2[0]); printf("loginid=%s\n", &shisha.loginid[0]); printf("password=%s\n", &shisha.password[0]); printf("db_string=%s\n", &shisha.db_string[0]);
◆サンプル:Perlのハッシュ
my %shisha = ( svnm => 'svr00X', svnm2 => 'X支社', loginid => 'id', password => 'passwd', db_string => 'svr00Xs' ); printf("hostname=%s\n", $shisha{svnm}); printf("hostname2=%s\n", $shisha{svnm2}); printf("loginid=%s\n", $shisha{loginid}); printf("password=%s\n", $shisha{password}); printf("db_string=%s\n", $shisha{db_string});
◆サンプル:C言語の構造体配列
#define SHISHA_CNT 3 struct db_login { char svnm[7]; char svnm2[9]; char loginid[31]; char password[31]; char db_string[45]; }; struct db_login shishas[SHISHA_CNT]={ "svr001", "A支社", "id", "passwd", "svr001s", "svr002", "B支社", "id", "passwd", "svr002s", "svr003", "C支社", "id", "passwd", "svr003s", }; int i; for (i =0; i < SHISHA_CNT; i++) { printf("hostname=%s\n", &shisha[i].svnm[0]); printf("hostname2=%s\n", &shisha[i].svnm2[0]); printf("loginid=%s\n", &shisha[i].loginid[0]); printf("password=%s\n", &shisha[i].password[0]); printf("db_string=%s\n", &shisha[i].db_string[0]); }
◆Perlのハッシュの配列
my @shisha = ({ svnm => 'svr001', svnm2 => 'A支社', loginid => 'id', password => 'passwd', db_string => 'svr001s' }, { svnm => 'svr002', svnm2 => 'B支社', loginid => 'id', password => 'passwd', db_string => 'svr002s' }, { svnm => 'svr003', svnm3 => 'C支社', loginid => 'id', password => 'passwd', db_string => 'svr003s' }, ); for (my $i = 0; $i < @shisha; $i++) { printf("hostname=%s\n", $shisha[$i]{svnm}); printf("hostname2=%s\n", $shisha[$i]{svnm2}); printf("loginid=%s\n", $shisha[$i]{loginid}); printf("password=%s\n", $shisha[$i]{password}); printf("db_string=%s\n", $shisha[$i]{db_string}); }
Perlでは、リファレンスと無名配列と無名ハッシュが理解できると、データ構造は自由自在なのです!
たとえば
- 配列の配列 $hoge[0][0]
- ハッシュの配列 $hoge[0]{foo}
- 配列のハッシュ $hoge{foo}[0]
- ハッシュのハッシュ $hoge{foo1}{foo2}
- ハッシュの配列のハッシュの配列とか・・・$hoge[0]{foo1}[0]{foo2}
◆関連リンク
【その5】foreach文がfor文に
結論から言うと、Perlでは、for文とforeach文は同じ物なのです。
というか、for文のforは、foreachの省略形とのことです(ホントかな)。
と同じとはいいながら、for文とforeach文ではイメージが違います。
- for文は、インデックスで回すイメージ
- foreach文は、データ構造の各要素に対して各々回すイメージ
◆for文の基本
for (my $i = 1; $i < 10; $i++) { 処理 }
◆foreach文の基本
foreach my $scalar (@array) { 処理 }
◆for文とforeach文の可換
冒頭で述べたように、for文とforeach文は同じで可換なのです。
foreach (my $i = 1; $i < 10; $i++) { 処理 } for my $scalar (@array) { 処理 }
で結論は、イメージを重んじる人はイメージ通りに、簡素にしたい人はfor文オンリーに、たぶん、foreach文オンリーは無しでしょう・・・たぶん。
【その6】Perlでの文字列
VB系とかDOSコマンド系での文字列はダブルクォーテーションです。
また、SQL系での文字列は、シングルクォーテーションです。
Perlでの文字列は、上記両方です。
違いは、ダブルクォーテーションでは変数が展開され、シングルクォーテーションでは展開されません。
また、qクォート演算子とqqクォート演算子も同じ関係で文字列を表します。
my $strdata1 = '文字列1'; my $strdata2 = "文字列2 $strdata1"; my $strdata3 = q{文字列3}; my $strdata4 = qq{文字列4 $strdata3}; print $strdata1, "\n"; # 文字列1 print $strdata2, "\n"; # 文字列2 文字列1 print $strdata3, "\n"; # 文字列3 print $strdata4, "\n"; # 文字列4 文字列3
あと、qwクォート演算子を使うと文字列のリストの入力が楽になりますね。
my @strlist1 = ('文字列1', '文字列2', '文字列3');
my @strlist2 = qw{文字列1 文字列2 文字列3}; # 上と等価(ただし、スペースが文字列中にあるときはNG)
print "@strlist1", "\n"; # 文字列1 文字列2 文字列3
print "@strlist2", "\n"; # 文字列1 文字列2 文字列3
◆補足1:ダブルクォート文字列での特殊文字のエスケープ
たとえば、ダブルクォート文字列にダブルクォートを記述する場合は、その前に \ を置いてエスケープします。
- \" ダブルクォーテーション( " )のエスケープ
- \$ ダラー( $ )のエスケープ
- \\ エスケープのエスケープ
- \@ アットマーク( @ )のエスケープ
- \% パーセント( % )のエスケープ
- \; セミコロン( ; )のエスケープ
尚、シングルクォート文字列の中でのシングルクォート( ' )のエスケープは( \' )とします。
◆補足2:ダブルクォート文字列で展開される \ による文字エスケープ
以下は、ダブルクォート文字列の中で各々の意味(制御コード)に展開されます。
- \t タブ
- \n 改行
- \r リターン
- \f 改ページ
- \b バックスペース
- \a アラーム
- \e エスケープ
- \003 8進数
- \x1b 16進数
- \cC CTRL-C
- \l 次の文字を小文字
- \u 次の文字を大文字
- \L \E が来るまで小文字
- \U \E が来るまで大文字
- \Q \E が来るまで非英数字の前に \ を追加
- \E \L、\U、\Qの効果を終了する
【その7】サブルーチンの呼び出し方
◆ イントロ
まず、Perlでのサブルーチンは関数と等価です。
そして、関数の呼び出し方には、『&』のファニー文字を付けたり、付けなかったり・・・。
また、関数名の後に『()』小カッコを付けたり、付けなかったり・・・。
またまた、『&』も『()』も付けなく、ベアワードになったりと・・・。
これらは、『組み込み関数やインポートしたモジュール関数』と『ユーザ定義関数』で意味が違ってきます。
なので、各々について対比して解説をしてみました。
尚、以下は、あくまでも私のコーディング方針で、強制的なものではありません。
◆ 『&』ファニー文字について
組み込み関数やインポートしたモジュール関数は、『&』ファニー文字は付けない。
組み込み関数等に『&』を付けるとエラーになる。(ユーザ定義関数を探しに行ってエラーとなる)
ユーザが定義できる関数(サブルーチン)は、いろいろな理由で『&』を付けず、ユーザ定義関数は関数名の後に『()』小カッコを付ける方がよい。
しかし、万が一、組み込み関数とユーザ定義関数が同名になった時は、『&』でユーザ定義関数を呼び出すことができます。
use strict; use warnings; my $hoge1 = time; # 組み込み関数 my $hoge2 = &time; # ユーザ定義関数 print $hoge1 , "\n"; print $hoge2 , "\n"; sub time { return 'hoge'; }
◆ 関数名の後の『()』小カッコについて
組み込み関数やインポートしたモジュール関数は、『()』小カッコを付けない方がよい。
カッコを付けてもOKですが、ユーザ定義関数以外であることを明示するために『()』を付けない方がよい。
ユーザが定義できる関数(サブルーチン)は、『()』小カッコを付ける。
関数をロジックの前置きに定義すると、カッコなしでもOKですが、そうしない。『()』小カッコを付けてユーザ定義関数を明示する。
use strict; use warnings; my $hoge1 = localtime time; # 組み込み関数 my $hoge2 = &localtime(time); # ユーザ定義関数 print $hoge1 , "\n"; print $hoge2 , "\n"; sub localtime { return $_[0]; }
◆ 引数のない関数について
引数のない場合の組み込み関数やインポートしたモジュール関数は、ベアワードにする。
引数のない場合のユーザが定義できる関数(サブルーチン)は、『()』を付ける方がよい。
◆ ユーザ定義関数は、『&』をつけない?
『&』は、基本的につけません。これに関しては以下のリンク先を参照願います。
- & はいつ使うべきか http://blog.livedoor.jp/dankogai/archives/51237271.html
- 関数と演算子 http://d.hatena.ne.jp/deq/20061213/1165991932
また、id:miyagawa氏から、以下のようなコメントも頂きました。
将来組み込み関数が増えた場合でも、それらの関数は use feature しないと有効にならない仕組みになっている(5.10のsay や givenなど) ので、何もなしに動かなくなる、ということはないはず。
◆ 結論
- 組み込み関数をコールする時は、小カッコ『()』を付けない。
- ユーザ定義関数をコールする時は、小カッコ『()』を付ける。しかし、『&』は付けない。
【その8】サブルーチンのデフォルトのリターン値
Perlでのサブルーチンは、実は関数とイコールです。
そもそも、サブルーチンと関数の違いは、リターン値を返すか返さないかの違いです。
しかし、Perlでは、サブルーチンでもリターン値を返すことができるので、事実上、関数とイコールなのです。
Perlのサブルーチンはsubキーワードから書き始めます。
リターン値はreturnキーワードで記述します。
しかし、returnキーワードをあまり見かけないのです。
またしかし、それでもリターン値が返って来ているみたいなのです。
そう、実は、returnキーワードは省略可能なのです。
Perlでは、サブルーチンの最後に評価した式の値が、returnキーワードがあってもなくても、リターン値になるのです。
use strict; use warnings; sub closure { my $value = shift; sub { $value++ }; # return sub { $value++ }; と等価 }
Perlをやり始めると、subだし、returnキーワードはないケースが多いし、本当にリターン値が返っているのだろうか・・・。
なんて、悩む時期があるのです。
とにかく、サブルーチンの最後に評価した式の値が、returnキーワードがあってもなくても、リターン値になるのです!
【その9】単独shiftとは何か?
Perlでのshift関数は、普通、配列変数を引数にとり、配列変数から一番若いインデクスの要素を取り出します。
しかし、配列変数の引数がない単独shiftを結構見かけます。
これは、mainモジュールでは「@ARGV」がデフォルトの引数で、sub内では、「@_」がデフォルトの引数になるのです。
ちなみに、「@ARGV」は、コマンドラインの引数の配列で、「@_」は、サブルーチンの引数の配列です。
◆サンプル
use strict; use warnings; my $arg = shift || 2000; print $arg, "\n"; subx(); subx(4000); sub subx { my $para = shift || 3000; print $para, "\n"; }
◆サンプル実行結果
C:\perltest>perl shift.pl 1000 1000 3000 4000 C:\perltest>perl shift.pl 2000 3000 4000
【その10】マッチ演算子の区切り文字
マッチ演算子はスカラー変数に格納されている文字列を正規表現を使ってマッチするか判断する演算子です。
通常、区切り文字『/』を使って以下のようになります。
if ($_ =~ m/abc/) { print "マッチした! \n"; } else { print "マッチしない!\n"; }
しかし、以下のようにも書けるのです。
if (m@abc@) { print "マッチした! \n"; } else { print "マッチしない!\n"; }
そう区切り文字は、『/』でなくとも、任意の非英数字(空白文字は除く)でも大丈夫なのです。
またしかし、非英数字でもカッコ系は、開き/閉じのペアでないとなりません。
またまたしかし、以下の3つに関しては、注意が必要です。
- 『#』を使う場合は、マッチ演算子との間にスペースを入れないこと(入れるとコメント認識される)。
- 『'』を使う場合は、パターン内で変数が展開されなくなる。
- 『?』を使う場合は、??演算子になる(1回しかマッチしない)
ただし、区切り文字『/』は、他の非英数字と違い、mを省略できます。
if (/abc/) { print "マッチした! \n"; } else { print "マッチしない!\n"; }
◆サンプル(m.pl)
use strict; use warnings; my $s = "abcxxxxabcyyyyabczzzz"; if ($s =~ m/abc/) { print "マッチした! \n"; } else { print "マッチしない!\n"; } if ($s =~ m@abc@) { print "マッチした! \n"; } else { print "マッチしない!\n"; } if ($s =~ m<abc>) { print "マッチした! \n"; } else { print "マッチしない!\n"; } my $a = 0; while($s =~ m/abc/g) { $a++; } print $a, "\n"; $a = 0; while($s =~ m?abc?) { $a++; } print $a, "\n";
◆実行結果
C:\perltest>perl m.pl マッチした! マッチした! マッチした! 3 1
【その11】正規表現
今回の謎は、正規表現です。
はじめての人のために解説をしてみました。
他言語からの人は、正規表現を使う機会がなかったかもしれませんが・・・。
Perlでは、正規表現を使うための言語と言っても過言ではありません。
正規表現を使うことによりテキスト(文字列)編集処理が飛躍的に楽になるのです。
そもそも正規表現は、人体の神経回路網を数学的に説明するための方法として開発されました。その後、UNIXのケン・トンプソンによってコンピュータでの検索アルゴリズムに引き継がれました。ちなみに、正規表現を初めて使用したアプリケーションは、UNIX のエディタedやexの祖となったQEDというエディタのようです。QED、ed、grep等を経てPerlに引き継がれたようです。 (そもそもの正規表現は、参考リンクのリンク先を参照しました)
では、まずはじめにテストデータ、family.txt を用意します。
このテストデータを元にしてサンプルプログラムを踏まえて解説をしていきますね。
・family.txtファイルの内容
macha koike yachu koike chaichan koike hiro koike mama koike papa koike koike 6 ni-bo- horie yasashii nee-nee horie chottokowai 60 horie ma-kun murai sachi murai kazumasa murai hisa
◆マッチング
まず始めに、苗字が『koike』の人をマッチさせてみましょう。(サンプル01 sample01.pl)
while($_ = <STDIN>){ if($_ =~ /koike/){ print $_; } }
上記は、標準入力からデータを読み込んで一行づつループし、その行データに『koike』がマッチするか調べ、マッチした時に行データを標準出力しています。
『$_ =~ /koike/』の式はマッチした時は、真を返し、マッチしない時は、偽を返します。
では、実行してみましよう。
% sample01.pl < family.txt macha koike yachu koike chaichan koike hiro koike mama koike papa koike koike 6
・補足1
逆にマッチしない時は真を返し、マッチした時は偽を返すには『$_ !~ /koike/』とします。
◆アンカー
まずいことに、名前以外の koike 6 もマッチてしまいました。
そこで、koikeという文字列がデータの末尾に一致する場合のみに限定します。(サンプル02 sample02.pl)
while($_ = <STDIN>){ if($_ =~ /koike$/){ print $_; } }
/koike$/の『$』は、文字列の末尾にマッチする記号で、アンカーと言います。
アンカーには、他に文字列の先頭にマッチする『^』と単語の境界(正確には、単語の先頭あるいは末尾)にマッチする『\b』、それ以外の部分にマッチする『\B』があります。
では、実行してみましよう。
% sample02.pl < family.txt macha koike yachu koike chaichan koike hiro koike mama koike papa koike
今度は上手く行きました。
・アンカー『^』の検索(サンプル03 sample03.pl)
つぎに、アンカー『^』を使って『k』で始まる人を検索してみましょう
while(<STDIN>){ if(/^k/){ print; } }
尚、『$_』は省略可能です。
% sample03.pl < family.txt koike 6 kazumasa murai
・補足2
また、アンカーには、 文字列の先頭にマッチする 『\A』 と文字列の末尾にマッチする 『\Z』 があります。
『\A』 と 『^』 、『\Z』 と 『$』 は機能がよく似ていますが、複数の行がある文字列を対象に正規表現を行った場合、『^』 は改行の直後にもマッチしますが、『\A』 は文字列の先頭にしかマッチしません。
また、『$』は改行の直前にもマッチしますが、『\Z』は文字列の末尾にしかマッチングしません。
◆文字クラス
また、よけいな koike 6 がマッチしてしまいました。
そこで、koike 6 には数字が入っていますので、数字があるとNGにすればいいわけです。
ですが、発想を変えて、数字以外の文字が連続するとしても同じです。
・数字以外の文字が連続の検索(サンプル04 sample04.pl)
while(<STDIN>){ if(/^k[^0-9]+$/){ print; } }
% sample04.pl < family.txt kazumasa murai
今度は上手く行きました。
まず、[〜]は文字クラスといって、[0-9]の場合は 0から9までの1文字にマッチします。
[^0-9]は文字クラスの中に ^が付くことによって文字クラスの否定を意味します。
文字列のアンカーの『^』とは別物です。
[^0-9]+の+は1回以上の『繰り返し』という意味です。
つまり、0-9以外の一文字の繰り返しになります。
$は末尾にマッチのアンカーの$です。
・1語の名前を検索(サンプル05 sample05.pl)
while(<STDIN>){ if(/^\S+$/){ print; } }
% sample05.pl < family.txt hisa
\Sも文字クラスで、空白文字以外を表します。
つまり、^\S+$ は先頭が空白文字以外で、これがかつ一回以上の繰り返しで、末尾までになります。
・3語の名前を検索(サンプル06 sample06.pl)
while(<STDIN>){ if(/^\S+\s+\S+\s+\S+$/){ print; } }
% sample06.pl < family.txt ni-bo- horie yasashii nee-nee horie chottokowai
\sも文字クラスで、空白文字(空白,タブ,改行)を表します。\Sの反対です。
つまり、^\S+\s+\S+\s+\S+$ は先頭が空白文字以外で、これがかつ一回以上の繰り返した後、空白文字を一回以上の繰り返し、空白文字以外一回以上の繰り返し、空白文字を一回以上の繰り返し、空白文字以外一回以上の繰り返しで、末尾までになります。
・文字クラス一覧
[ABC] A,B,Cのいずれか1文字
[A-Z] A〜Zまでのいずれか1文字
[A-Za-z0-9] A〜Z, a〜z, 0-9までのいずれか1文字
[^ABC] A,B.C以外の文字
[^A-Z] A〜Z以外の文字
\w 英数文字。[a-zA-Z0-9]と同様
\W \w以外の文字
\d 数値文字。[0-9]と同等
\D \d以外の文字
\s 空白文字(空白,タブ,改行)
\S \s以外の文字
\b 単語の区切り
. 任意の一文字
・補足3
/koike/ だと koikesでもマッチしてしまいますが、/\bkoike\b/だと回避できます。
・補足4
\wと\Wは、アスキー系以外では機能しない場合があるらしい。
◆メタキャラクタとエスケープ
正規表現において特殊な意味を持つ文字をメタキャラクタと呼びます。
+ * ? . ( ) [ ] { } | \ $ ^
\は、メタキャラクタの本来の作用をエスケープし、
メタキャラクタ以外では書かないのと同じになります。
たとえば、
^\^ は、^という文字で始まる行にマッチ
\\ は、\自体にマッチ
また、\Qと\Eの間に挿入された文字列は全部の文字の前に \ が挿入されたものと同じです。
/\o\)\+\>/ と /\Qo)+<\E/ は同じ。
◆繰り返し
A+ 1個以上連続したA(A, AA, AAA, ...)
A* 0個以上連続したA( , A, AA, AAA, ...)
A? 0または1つの任意文字( , A, B, C, ...)
A{5} 5回繰り返し。 AAAAAと同じ
A{3,} 3回以上繰り返し。 AAA+と同じ
A{3,5} 3回以上5回以下繰り返し。 AAAA?A?と同じ
◆ 最大マッチと最小マッチ
たとえば、以下のようなデータがあったとします。
<h1>Koike family</h1> <h2>Home Page</h2> <h3>By chaichanpapa</h3>
このデータからタグのみ除去してみましょう。
タグは < ではじまり、 > で終わる文字列です。
その文字列の中を「.」の任意の一文字で「*」で0回以上の繰り返しとして指定します。
ですので、以下のようなプログラムになります。
・サンプル(sample10.pl)
while(<DATA>){ s/<.*>//g; print; } __END__ <h1>Koike family</h1> <h2>Home Page</h2> <h3>By chaichanpapa</h3>
では、実行してみましょう。
% sample10.pl %
改行のみの表示だけになってしまいました。
実は、perlでは、パターンマッチがもっとも長い文字列とマッチする最大マッチなのです。
ですから、<h1>Koike から chaichanpapa</h3> まで削除されてしまいました。
では、タグのみ削除するにはどうしたらいいでしょう?
それには最小マッチの ? を指定します。
・サンプル(sample11.pl)
while(<DATA>){ s/<.*?>//g; print; } __END__ <h1>Koike family</h1> <h2>Home Page</h2> <h3>By chaichanpapa</h3>
では、実行してみましょう。
% sample11.pl Koike family Home Page By chaichanpapa
今度は、タグが除去され、上手くいきました。
・補足5
最大マッチと最小マッチは、英語(perlretut.pod)での"maximal match"と"minimal match"の訳です。
しかし、訳者によって、最長マッチと最短マッチや最多マッチと最少マッチになっていますね。
ちなみに、私は、すP派です。
◆グループ化と選択
文字列を繰り返すときは()を使ってグループ化します。
koi(ke)+ koike, koikeke, koikekekeなどにマッチします。
いくつかのパターンのどれかにマッチさせるときは | を使います。
(選択はパフォーマンスがあまり良くないとのことです。)
macha|yachu machaかyachuにマッチします。
koike(X|Y) koikeXかkoikeYにマッチします。
◆カッコを使った記憶
また、グループ化した部分は後から参照することができます。
これを後方参照といい、 同じ正規表現内で後方参照を行うには、\1, \2... を用います。
/(koike)\1/ koikeが2回連続する文字列にマッチします。
また、正規表現外で後方参照を行うには、マッチ特殊変数 $1, $2... を用います。
$str =~ /^(k.+) (c.*)$/i; print $1, "\n"; # koikeと表示される print $2, "\n"; # chaichanと表示される
◆マッチ特殊変数
マッチ特殊変数には、$1,$2,・・・の他に、$` , $& , $'等があります。
これらは、パターンマッチしたときに「マッチする前までの文字列」、「マッチした文字列」、「マッチした後の文字列」が各々、$` , $& , $' に格納されます。では、プログラムで確かめてみましょう。
サンプル sample14.pl
$n = 0; while(<STDIN>){ chomp; / \S+ | /; ++$n; print "*** $n ***\n"; print "AAA: $`\n"; if($& ne " "){ print "BBB:" . substr($&, 1, length($&) -2) . "\n"; } print "CCC: $'\n"; print "\n"; }
% cat family3.txt macha koike sayuri koike chaichan % % % sample14.pl < family3.txt *** 1 *** AAA:macha CCC:koike *** 2 *** AAA:sayuri BBB:koike CCC:chaichan
まず、chompは $_ の末尾の改行コードを切り落とします。
/ \S+ | /; は「左右に空白を持つ非空白1文字以上か、または、1文字の空白」のマッチです。
つまり、今回の場合は、 *** 1 ***は「1文字の空白」のマッチで、 *** 2 ***は「左右に空白を持つ非空白1文字以上か」にマッチします。
そして、$` と $& と $' に各々の値が格納されます。尚、$&には前後にスペースが入っていますので、substr($&, 1, length($&) -2)で削除します。
◆置換処理
置換演算子(s///)を用いると、文字列の置換を行うことができます。
my $str = 'koike sayuri'; $str =~ s/(koike) sayuri/$1 chaichan/; # $strは 'koike chaichan' になる print $str, "\n";
・修飾子
以下の修飾子を指定することにより、パターンマッチの振る舞いを変えることができます。
- i 大文字小文字の同一視 (case-insensitive)
- s 「.」が改行にもマッチするようにする (single line)
- m 複数行として扱う (multi-line)
- x 拡張正規表現を使う (extended)
- e Perlのコードとして評価する (evaluation)(並べてた数だけevalします)
- g 連続して何回もマッチ (global)
- o 一度だけコンパイルする (only once)
・i修飾子
i修飾子は、正規表現がアルファベットの大文字小文字を区別せずにマッチするようにするために指定します。
m/^chaichan$/i; # chaichanにもChaichanにもCHAICHANにもChAichanにもマッチする。
・s修飾子
#「.」が改行にもマッチするようになる my $str = "aaa bbb\nccc ddd"; print "No Match1\n" if ($str !~ /bbb.*ccc/); print "Match1\n" if ($str =~ /bbb.*ccc/s);
・m修飾子
my $str = "aaa bbb\nccc ddd"; print "No Match2\n" if ($str !~ /^ccc/); print "Match2\n" if ($str =~ /^ccc/m);
・x修飾子
正規表現内の空白や改行が無視され、「#」以降はコメントとして扱われます。
# 1と出力 print 1 if 'Chaichan' =~ / C h a i # コメント c h a n /x;
・e修飾子
以下はあまり意味がないプログラムですが、3回『e』しています。
my $str = "hello world"; $str =~ s/\w+/subx()/eeeg; print $str; sub subx { return 'suby()'; } sub suby { return 'subz($&)'; } sub subz { ucfirst($_[0]); }
C:\perltest>perl reg_e.pl Hello World
・g修飾子
連続して何回もマッチの gですが、上記で『s/\w+/subx()/eeeg』を『s/\w+/subx()/eee』にすると、表示は『Hello world』となり、worldはそのままで、Helloだけ『h』が『H』になり、連続しなくなります。
逆にいうと、g修飾子を指定すると連続して何回もマッチするわけです。
・o修飾子
変数展開が最初の1回だけ行われます。
逆にいうと、o修飾子を指定しない場合は、毎回変数展開される。
my $str = "aaa bbb\nccc ddd"; my $regex = 'aaa'; for (1..10) { print "Match3\n" if ($str =~ /$regex/o); }
◆拡張構文
Perlには、以下のような拡張構文があります。
拡張構文は、キャプチャ(カッコを使った記憶)を行わないため、マッチした部分を正規表現の中で\1、\2のように参照したり、後から$1、$2のような変数で参照したりすることができません。
- (?# コメント) 正規表現内にコメントを入れます。
- (?=式) 前方一致検索です。
- (?!式) 後続の文字列が式に一致しなければマッチします。
- (?ismx) 正規表現内にパターンマッチ修飾子を埋めみます。
- (?<=式) 後方一致検索で先行する文字列が式に一致すればマッチします。
- (?<!式) 後方一致検索の否定形で先行する文字列が式に一致しなければマッチします。
以下は、拡張構文のサンプルです。すべてマッチします。
$bbb = '東京都庁'; $ccc = 'とうきょう都庁'; print "Match1", "\n" if ($bbb =~ /東京(?=都庁)/); # 東京の後に都庁が続いている東京にマッチ print "Match2", "\n" if ($bbb =~ /東京(?!議会)/); # 東京の後に議会が続いていない東京にマッチ print "Match3", "\n" if ($bbb =~ /(?<=東京)都庁/); # 東京の後に都庁が続いている都庁にマッチ print "Match4", "\n" if ($ccc =~ /(?<!東京)都庁/); # 東京でないの後に都庁が続いている都庁にマッチ
ちなみに、拡張構文の「東京(?=都庁)」と普通構文の「東京(都庁)」は同じ結果になりますが、すべての拡張構文には後方参照がありません。
また、拡張構文では検索文字にパターンマッチ修飾子(imsx)が埋め込めます。
# 普通構文
$ddd = 'Tokyo';
if (/$ddd/i) {
print "$ddd\n";
}
# 拡張構文
$ddd = "(?i)Tokyo";
if (/$ddd/) {
print "$ddd\n";
}
◆ 正規表現初心者卒業
上記がマスター出来たら、とりあえず、正規表現初心者卒業です。
しかし、正規表現は奥が深く、また、Perlが独自に拡張しています。
ですので、まだまだ、正規表現を極めようという人は、とりあえず以下で勉強をしてみてください。
再帰的な正規表現や正規表現の中にPerlコードを記述できたりと、高度なテクニックが書いてありますよ。
◆ 参考リンク
【その13】リファレンスについて
普通、リファレンスといえば、一覧の機能参照のことですが、Perlでリファレンスといえば、言語仕様のアドレス参照のことなのです。
まぁ、PerlでのリファレンスをC言語でいえば、アドレス演算ができないポインタのようなものなのです。
リファレンスの利用場面は、私が思いつくところでは以下のケースです。
- 多次元配列を処理するケース
- 関数をリファレンスでコールするケース
- 関数の引数へ配列を渡すケース
- 関数のリターン値にするケース
◆多次元配列を処理するケース
my $array_ref1 = \@array;
配列変数の@の前に、\をつけると、配列のリファレンスが取得できます。
上記は、配列@arrayのリファレンスを$array_ref1へ格納しています。
また、無名配列のリファレンスを格納することもできます。
my $array_ref2 = [ 1, 2, 3, 4 ];
リストを [ ] でくくると、無名配列になり、そのリファレンスを$array_ref2へ格納しています。
つぎに、リファレンスから元の値を取得することをデリファレンスといいます。
my $array_ref2 = [ 1, 2, 3, 4 ]; print $array_ref2->[3]; #デリファレンス
ちなみに、『->』は矢印演算子といい、リファレンスをデリファレンスします。
補足:リファレンスの元をリファレントいいます。
つまり、$array_ref1がリファレンスで、\@arrayがリファレントです。
この機能を利用すると多次元配列の処理が可能です。
@a = ( [ qw(00 01 02) ], [ qw(10 11 12) ], [ qw(20 21 22) ]); for $i (0 .. $#a) { for $j (0 .. $#{$a[$i]}) { print '$a[' . $i . '][' . $j . ']=' . $a[$i][$j] . ' '; } print "\n"; }
尚、上記では『->』矢印演算子がありませんが、大括弧(ブラケット)や中括弧(ブレース)の間にはさまれた矢印は省略可能なのです。
◆関数をリファレンスでコールするケース
関数のリファレンスも配列と同様にできます。
sub01("aaa"); # 普通にコール $sub = \&sub01; $sub->("aaa"); # リファレンスでコール sub sub01 { print "--- sub01 $_[0]---\n"; }
『$sub = \&sub01;』で関数のリファレンスを格納して、『$sub->("aaa")』でコールしています。
◆関数の引数へ配列を渡すケース
Perlでの関数の引数はリストです。
引数に配列変数を渡すとリストに展開されてしまい、普通は上手く配列変数を渡せません。
そこで登場してくるのが配列変数のリファレンスです。
リファレンスなら、上手く配列変数を渡せるのです。
my @bbb = ("bbb", "ccc", "ddd"); my @xxx = ("xxx", "yyy", "zzz"); sub02("aaa", \@bbb, \@xxx); sub sub02 { print "--- sub02 $_[0]---\n"; print "--- sub02 $_[1]->[0]---\n"; print "--- sub02 ${$_[1]}[1]---\n"; print "--- sub02 $_[1][2]---\n"; print "--- sub02 $_[2][0]---\n"; print "--- sub02 $_[2][1]---\n"; print "--- sub02 $_[2][2]---\n"; }
◆ 関数のリターン値にするケース
関数のリターン値でblessしたリファレンスが返された場合、それは、他言語でいうオブジェクトと同じ感じになるのです。
my $date = DateString->new(); print $date->to_string, "\n"; package DateString; sub new { my $class = shift; # 第一引数は、クラス名が渡される。 my $time = shift || time(); return bless { time => $time }, $class; # blessしたリファレンスを返す。 } sub to_string { my $self = shift; return scalar localtime $self->{time}; }
【その14】use宣言について
Perlをやり始めると、requireはすぐ分かるんだけれど、useって謎だなぁって思いますね!
今回は、そんなuseの謎に迫ります。
はじめに、requireは、C言語でいう%includeと同じで別ファイルにあるソースを実行時にあたかも自ソースとして読み込みます。
つぎに、useは、実行前(コンパイル時)にあたかも自ソースとして読み込みます。
実行前というのは、Perlではコマンド起動時にコンパイルしてから実行するという2段階方式になっているのです。
つまり、useはコンパイル時に実行され、requireはコンパイル後に実行されるのです。
そして、読み込んだソース(モジュール)にpackage宣言がない場合、useとrequireでは、これ以外の違いはありません。
しかし、モジュールにpackage宣言があると、ちょっとややっこしくなるのです。
まず、モジュールでなく、メインソースの方には、package宣言がありませんが、実は、デフォルトでmainというpackage名になっています。
一方、モジュールの方は、package宣言でpackage名をmain以外で指定すると、当然ですがメインソースとは別のpackage名になりますね。
しかし、別のpackage名になっても、useには他モジュールを、以下ようにあたかも自モジュール(mainモジュール)にあるようにする仕組みがあるのです。
- それは、Moduleモジュールのimportメソッドです。
- 『use Module』は、実は『BEGIN { require Module; Module->import; }』とイコールなのです。
- 『BEGIN』は、コンパイル時に実行されるブロックなのです。
- 『Module->import;』は、Moduleモジュールにあるものをmainモジュールにインポートするものなのです。
いきなり、importが出てきますが、これは、Moduleモジュール内でExporterモジュールを継承していて、Exporterモジュール内のメソッドなのです。
◆サンプル
・mainモジュール
#use Module; BEGIN { require Module; Module->import;} # use Module; と等価 print sumx(5,3),"\n"; # Moduleモジュールのsumx関数があたかも自関数のように使える
・Moduleモジュール
package Module; use base Exporter; # use Exporter; our @ISA = qw(Exporter); と等価 our @EXPORT = qw(&sumx); sub sumx { return $_[0]+$_[1]; # 渡された引数2つを加えて返す処理 } 1;
◆補足
Moduleモジュールでは、『use base Exporter』でExporterを継承して、『our @EXPORT = qw(&sumx);』でsumx関数をエクスポートしています。
mainモジュールでは、『Module->import;』でModuleモジュールでエクスポートしたsumx関数をインポートしています。
【その15】blessについて
useやモジュールとリファレンスが理解出来てくると、blessが気になってきますね!
今回は、そんなblessの謎に迫ります。
blessを一言でいうと、リファレンスとパッケージを紐付けます。
たとえば、以下を見てください。
use strict; use warnings; my %test_HASH = (aaa => 'XXX', bbb =>'YYY'); my $test = bless \%test_HASH, 'package01'; # リファレンスとパッケージを紐付け print '-->', $test->{aaa}, '<--', "\n"; $test->test01(3); print '-->', $test->{aaa}, '<--', "\n"; package package01; sub test01 { print "第1引数は$_[0], 第2引数は$_[1]です。", "\n"; my $obj_self = shift; $obj_self->{aaa} = 'ZZZ'; }
『my $test = bless \%test_HASH, 'package01';』で、%test_HASHのリファレンスとpackage01のパッケージを紐付けています。
そして、blessのりターン値が%test_HASHのリファレンスなのですが、だだのリファレンスでなく、blessで紐付けられたpackage01パッケージも含まれるのです。
なので、『$test->test01(3);』のようにpackage01パッケージのtest01サブをコールすることが出来るのです。
コールする時、『->』矢印演算子を使うとtest01サブでは、自動的に第1引数が自リファレンスになるのです。
なので、『$obj_self->{aaa} = 'ZZZ';』でリファレンスからもとのハッシュ変数にアクセスして書き換えが可能なのです。
結果的にリファレンスが、ハッシュ変数(プロパティ)とサブルーチン(メソッド)を持つことになるのです。
プロパティとメソッドと言えば、そう、オブジェクトになるのですね…これが!!
【その16】Perlでのオブジェクトについて
えーと、前回ご説明しましたが、Perlでは一つのパッケージが一つのクラスになります。
つまり、パッケージ名がクラス名になるのです。
また普通、クラスには、オブジェクトを構築するコンストラクタがあります。
そして、コンストラクタでblessしたリファレンスにクラス名を紐付けてリターン値にして、それを変数に格納するとその変数がインスタンスになります。
って、文章で説明しても分かり辛いので、以下のソースを見て感じとってください。
- ポイント1は、クラス名に矢印演算子で関数をコールすると第1引数にクラス名が自動的に入ります。
- ポイント2は、インスタンスに矢印演算子で関数をコールすると第1引数にオブジェクトが自動的に入ります。
- ポイント3は、ポイント1とポイント2を合体させたクラスになっており、委譲でオブジェクトを使っています。
use strict; use warnings; print "\n", '◆ポイント1の例(パッケージ名(クラス名)が第1引数)', "\n"; package00->test00(3); package package00; sub test00 { print "第1引数は$_[0], 第2引数は$_[1]です。", "\n"; my $class = shift; print "第1引数は $class でクラス名になります。\n" } print "\n", '◆ポイント2の例(blessリファリンス(オブジェクト)が第1引数---', "\n"; my %test_HASH = (aaa => 'XXX', bbb =>'YYY'); my $test = bless \%test_HASH, 'package01'; print '-->', $test->{aaa}, '<--', "\n"; $test->test01(3); print '-->', $test->{aaa}, '<--', "\n"; package package01; sub test01 { print "第1引数は$_[0], 第2引数は$_[1]です。", "\n"; my $obj_self = shift; $obj_self->{aaa} = 'ZZZ'; print "第1引数は $obj_self でオブジェクトになります。\n" } print "\n", '◆ポイント3の例 オブジェクト例(委譲)---', "\n"; my $obj_package02 = package02->new(); # newはオブジェクトコンストラクタ # $obj_package02はインスタンス print '-->', $obj_package02->{bbb}, '<--', "\n"; $obj_package02->test02(4); # オブジェクトのメソッド起動 print '-->', $obj_package02->{bbb}, '<--', "\n"; package package02; # パッケージは、Perlではクラスになる sub new { print "第1引数は$_[0]でクラス名です。", "\n"; my $class = shift; my %test_HASH = (aaa => 'XXX', bbb =>'YYY'); return bless \%test_HASH, $class; } sub test02 { print "第1引数は$_[0], 第2引数は$_[1]です。", "\n"; my $obj_self = shift; $obj_self->{bbb} = 'ZZZ'; print "第1引数は $obj_self でオブジェクトになります。\n" }
【その17】Perlでのオブジェクト指向について
Perlは元々は、オブジェクト指向言語ではありません。
しかし、柔軟な言語仕様のPerlでは、オブジェクト指向言語のように振る舞うことも可能なのです。
普通、オブジェクト指向プログラミングでは、以下の要素が必要です。
- クラス(Perlではパッケージで対応)
- インターフェース(Perlでは不完全だけどパッケージで対応)
- コンストラクタ(Perlではblessリファレンスで対応)
- オーバーライド(Perlでは普通に対応)
- オーバーロード(Perlでは複数の同名subは無理。しかし、一つのsubでオーバーロードもどきは可能)
- カプセル化(Perlでは言語仕様的に無理)
- 継承(Perlでは普通に対応)
- 委譲(Perlでは普通に対応)
- 多態性(Perlでは普通に対応)
ということで、カプセル化とオーバーロード以外は、大丈夫なのです。
以下に、クラス、コンストラクタ、継承、多態性、オーバーライドを使用したサンプルを書いてみました。
- クラスは、Person、Otokonoko、Onnanokoのパッケージを使用する。
- コンストラクタは、『return bless $self, $class;』でblessリファレンスをコンストラクトする。
- 継承は、『use base 'Person';』でbaseプラグマを使用する。
- 多態性は、動的な条件によって、予めコーディングしてあるオブジェクトのメソッドの振る舞いが替わる。
- オーバーライドは、スーパークラスでの抽象getNameメソッドをサブクラスの具象getNameメソッドで置き換える。
◆サンプル
use strict; use warnings; my $obj; my $hiki = shift; if ($hiki eq '1') { $obj = Otokonoko->new(); } else { $obj = Onnanoko->new(); } print $obj->getName(), "\n"; $obj->shumi(); package Person; sub new { my $class = shift; my $self = { Name => $class, }; return bless $self, $class; } sub getName {}; package Otokonoko; use base 'Person'; sub getName { my $self = shift; return $self->{Name}; } sub shumi { print "バスケ\n"; } package Onnanoko; use base 'Person'; sub getName { my $self = shift; return $self->{Name}; } sub shumi { print "ケーキ作り\n"; }
◆実行結果
C:\perltest>perl tatai.pl 1 Otokonoko バスケ C:\perltest>perl tatai.pl 2 Onnanoko ケーキ作り