※追記(2012.07/09) この記事は完全に書き直しました (→モジュールクラス・ユージング)。そちらのほうが分かりやすいと思います……たぶん。
前回は、strtok_getnext() の最中に、新たに strtok を使用することができない、という話をしました。
この原因を、わかりにくく言うと、strtokモジュールが1つしかないからです。当たり前ですが。
しかし、この当たり前を覆すちょっと便利な機能が、このページで扱う、「モジュール変数」機能です。
まずは実際のスクリプトを見てください。
// ModuleClass strtok (string tokenizer) // 簡易版 #ifndef IG_MODULE_CLASS_STRTOK_AS #define IG_MODULE_CLASS_STRTOK_AS #module strtok mBuf, mSplitter, mIndex, mlenBuf, mlenSplitter // マクロ #define global strtok_new(%1, %2, %3) newmod %1, strtok@, %2, %3 #define global strtok_delete(%1) delmod %1 //------------------------------------------------ // strtok 構築 // // @prm sBuf : 区切られるテキスト // @prm splitter : 区切り文字列 //------------------------------------------------ #modinit str sBuf, str splitter mBuf = sBuf mSplitter = splitter mIndex = 0 mlenBuf = strlen(mBuf) mlenSplitter = strlen(mSplitter) return //------------------------------------------------ // 文字列を切り出す //------------------------------------------------ #modcfunc strtok_getnext if ( strtok_isFinished(thismod) ) { // 既に完了している return "" } // 次の区切り文字を検索 i = instr( mBuf, mIndex, mSplitter ) if ( i < 0 ) { i = mlenBuf - mIndex // 残りすべて } // 結果 sResult = strmid( mBuf, mIndex, i ) mIndex += i mIndex += mlenSplitter // 区切り文字列を飛ばす return sResult //------------------------------------------------ // すでに取り出しが完了しているか //------------------------------------------------ #modcfunc strtok_isFinished return (mlenBuf <= mIndex) #global // サンプル・スクリプト #if 1 // (0 以外ならサンプルを実行) // 商品データ (という設定) // 各行は「商品名,単価,在庫数」ということにする buf = {" リンゴ,100,30 みかん,170,12 ぶどう,1600,2 "} // buf を strtok で分解する (改行で区切る) ; newmod lineToker, strtok, buf, "\n" strtok_new lineToker, buf, "\n" // マクロを使用、newmod でも処理は同じ // すべての行について、1行ずつ処理 repeat if ( strtok_isFinished( lineToker ) ) { break } // 一行取り出す data = strtok_getnext( lineToker ) if ( data == "" ) { continue } // data を分解する ( ',' で区切る ) strtok_new dataToker, data, "," name(cnt) = strtok_getnext( dataToker ) price(cnt) = int( strtok_getnext( dataToker ) ) stock(cnt) = int( strtok_getnext( dataToker ) ) strtok_delete dataToker cntItem ++ loop strtok_delete lineToker // 必ず delete する、という必要はない // 商品リストを再構成 repeat cntItem mes strf("「%s」は単価 %d 円で、%d 個の在庫があります。", name(cnt), price(cnt), stock(cnt)) loop stop #endif
変更点としては、strtok 用のデータを持つ変数の名前が変わったり、#module の行がやたら長くなったり、細かい点では、先頭のコメントの「string tokenize」が「string tokenizer」になったり、ということです。
「文字列を分解するための道具(プログラムの部品)」のことを、strtok と呼びます。この道具の設計図は、#module の横に並ぶ変数名と、#module から #global の間に書いたものです。
ここまででは、ただ設計図を書いただけで、実際には道具は作られていません。
#module strtok mBuf, mSplitter, mIndex, mlenBuf, mlenSplitter ... #modinit str sBuf, str splitter #modcfunc strtok_getnext #modcfunc strtok_isFinished ... #global
※設計図部分を、具体的なコードは省略して、再掲。
実際に道具を作るには、newmod 命令を使います。道具を作ることを、strtok を実体化する、といいます。また、作った道具のことを、strtok のインスタンス(実体、instance)といいます。ここで、作られた道具 (= strtok インスタンス) は、上記のサンプルでは、変数 lineToker が持っています。
道具が作られると、直後にコンストラクタ (#modinit で定義される命令) が自動的に呼び出されます。
コンストラクタは、道具を初期化するのに使用します。
; newmod lineToker, strtok, buf, "\n" strtok_new lineToker, buf, "\n" // マクロを使用、newmod でも処理は同じ
// 凡例 newmod (インスタンスを格納する変数), (モジュールクラス名), (コンストラクタ引数...)
インスタンスは、内部にいくつかの変数 ( mBuf, mSplitter, ...(以下略) ) を持っています。このような、「実体化された道具」の中にある変数を、メンバ変数と呼びます。例えば、strtok を2つ実体化すると、メンバ変数 (mBuf, mSplitter, ...) を2セット作ったことになります。
ただし、インスタンスの持つメンバ変数を (外部から) 操作(代入したり、その中の値を得たり)することはできません。
下にある図のように、インスタンスとはメンバ変数の集まり(パッケージ)である、と考えてもいいでしょう。
実体化された道具を操作するには、strtok が持つ命令、関数を使います。モジュールクラスが持つ命令や関数を、メンバ関数と呼びます。例では、strtok_getnext() と strtok_isFinished() が、strtok のメンバ関数です。メンバ関数は、#modfunc, #modcfunc で定義されます (これらの使い方は、#deffunc, #defcfunc とほとんど同じ)。
メンバ関数の中では、特別に、メンバ変数を普通の変数と同じように使用できます。
例えば、メンバ関数 strtok_isFinished を:
#modcfunc strtok_isFinished return (mlenBuf <= mIndex)
次のように、lineToker に対して使用するとします。
if ( strtok_isFinished( lineToker ) ) : ...
すると、strtok_isFinished が関数として呼び出されますが、その中にある mlenBuf, mIndex などは、lineToker が持つメンバ変数として扱います。
メンバ命令・関数からは、与えられたインスタンスの中にある各変数を、外にある変数と同じように操作することができる、ということです。
以上で、いわゆる「モジュール変数機能」の説明はだいたい終了です。
大事な用語が文章の中に散乱してしまっているので、改めて語句を整理しておきます。
なお、基本的にすべて、上大が (C++という言語から持ってきて) 勝手に付けた名称です (公式では決まってないので)。ご容赦ください。
※だって、普通のモジュールとクラス的なモジュールは、異なる名称で呼び分ける必要があるんだもん。
まぁ、あとは上記のサンプルで確認してください。
なお、インスタンスの中にあるメンバ変数の値は、標準の DebugWindow からは見えません。
拙作 knowbug なら、見にくいですが、一応表示に対応しています。
+参照:knowbug
さて。
ここでの説明は、かなりわかりにくいかと思います。申し訳ない。
いつも通りですが、質問などあれば掲示板までどうぞ。
次回は、モジュールから離れて、Dll関数の使い方についてやる予定です。執筆中……φ(;_;)。
※追記 (2011.10/06)
is finished ではなく has finished の方がいいかもしれない。
(ex) This function returns 'true' if this instance has finished splitting the given (charactor) string.
それと、全体的にわかりにくいので加筆か修正を考えていますが、あまり上手くいかない……。