LiSt 38 キャストで失敗した例 っています。しかも , goto と違って , 飛び [ 備考 ] 先が明確に書かれていないだけ , よけいに List 37 のプログラムは筆者がよく見る奇 妙な技巧に凝った例を簡略化したものです。 タチが悪いのです。 ところどころの if 文で , どのように処理が 飛んでいくのかわかりにくくなっています。 break,continue の悪用 break , continue のやっかいなのは , goto と 深刻度☆☆☆ ( 重度 ) 同様に処理が追いにくくなる危険をはらん [ 症状 ] でいることに加えて , プログラムの変更で 「無節操な goto の使用」の場合と同様 , プ ループ文が一段増えただけで , とたんに飛 ログラムの解読や改造が困難になります。 び先が変わる点です。また , break, contin また , バグが発生しても退治しにくくなり ue と同様に setjmp, lon mp を悪用した場合 も処理が追いにくくなりプログラムの解読 ます。 が困難になるので注意が必要です。 プログラマの怠慢が原因です。適当にプ ログラムを入力する習慣が付いていると思 キャストの悪用 われます。 深刻度☆☆☆ ( 重度 ) [ 対策 / 予防 ] 実はどう対策 / 予防していいのか筆者に [ 症状 ] も思いつきません。何しろ , このようなプ 期待したとおりにプログラムが動いてく ログラムを書いている人たちには悪いこと れないはずです。 をしているという自覚がないのです。 [ 原因 ] [ 例外 ] プログラマがキャストの意味を理解しな 試作段階 , 試行錯誤している場合は許さ いで乱用することが原因です。 れるかもしれません。 [ 対策 / 予防 ] 実例です。 b 「 eak と continue で流れのわかりに キャストとはコンパイラに対する黙認指 strs 仕という関数は 1 番目の引数で指定し くいプログラム 示にすぎず , どのようにコードが生成され た文字列から 2 番目の引数で指定した文字 るかはプログラマの責任になるということ 列を探す関数です。 List 38 では , "TEST" void (void) をしつかり認識し , できるだけ使用しない 土地 ( x ) { という文字列のなかから ' S ' という文字を探 while(Y)( ように心掛けることです。 そうとしているようです。おそらく , 土 f ( 幻 break; [ 例外 ] theCp = strstr("TEST ” , 'S') ; if(A) contxnue ; 一 キャストをせざるをえない , あるいはキ と書いてコンパイラに型が一致しないと怒 while(B)( ャストを前提とした関数を利用する場合は られたのでしよう。「キャスト」で無理やり if(c)( やむをえないでしよう。しかし , なるべく ごまかしています。ところが , s 仕 s 仕で要求 引 ) 引 if(D){ なら避けるべきです。 している引数は「 const char * 」 , つまり [ 備考 ] char 型ポインタなのに , 実際に与えている ( 幻い C 言語のダークサイドであり , 「グロ ' S ' はⅲ t 型です。ここでキャストを使って , ーーノヾ k( ル変数」と並んで禁じ手の上級クラスに位 「ⅲ t 型だけど char ポインタとして扱ってほ 置付けられるのが「キャスト」です。 しい」とごまかしても残念ながら解決には なりませんし , 思ったとおりには動いてく なにしろキャストというのは悪いいい方 をすれば「型のごまかし」であり , 「コンパ れません。 イラに対する詐欺行為」にすぎませんから , このプログラムは正しくは , thecp = strstr("TEST","S") ; コンパイラのチェックを通ったものの , や はりうまくいかないというケースは珍しく とすべきです。 ありません。 List 38 のプログラムは , その 舌は変わりますが , 無意味なキャストを void f(Y 曲 a て *thecp; ecp = 3 st て ( 舛 ES , ( cons セ char 幻 ts' p て土北 f は s ] 跏 % ecp ) を List 39 void * の使用例 void W て土セ e を D ( cong し void *inData,size—t S 土 土 n セ thel; char theTx 128k 鶩驀第 W て土 te 印 ( & the 工 , g 土 0 モ ( 遍 t ) WriteFD(theTxt,strIen(theTxt)) ・こを邑を [ 原因 ] List 40 void * の間違った使用例 日 ) WriteFD ( ( cons し void * )&thel , sizeof(int) ) ・ WriteFD( ( 00n VO 坦 *)theTxt, strlen(theTxt) List continue; break; if(F){ if(G)( ℃ on 土れ ue breaki 一三ロ 42 C MAGAZINE 2000 4
真紀俊男の ローテク講座 プログラミングレッスン Ja a 言語 ? ログラミング Java 言語を始めよう B5 変 376 ページ ISDN 4-7973-0803-6 本体価格 2,400 円 洋 a 言語 プロクラミング、を をレッスン 0 、第 , 、いをし ( 。第 pvsprintf void pvsprintf(char *outBuf,size—t inSize,const char *inFormat,va—list inV) char *theLimit = outBuf 十 inSize - whiIe(*inFormat & & outBuf く theLimit){ if(*inFormat = = switch(*(inFormat + 1) ) ( outBuf = p—Char—Buf(outBuf,va—arg(inV,int) inFormat 十 = break; case 'd' ・ outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(inV'int) , 10 inFormat 十 = break; case 8 outBuf = P2—Text—Buf(outBuf,theLimit,va—arg(inV,char * ) inFormat 十 = break; case X outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(inV,int) , 16 inFormat 十 = break; a 言語を始めよう ava 言語プロ ? ラ当ングレッスス上 ) 結城 めの語ス門書。体書 ( よ巻 ) : プログラを冫の知識や経験のない初心斉 を料象にア av case ・ *outBuf 十十 = ' 宅ー inFormat 十 = break; default: *outBuf 十十 = *inFormat 十十一 break ー )else{ *outBuf 十十 = *inFormat 十十一 *outBuf = 0 後に残しておいたわけであるが。単に P2sp ⅱ n を考え , 次のような引数を考える。 void P2sprintf(char * outBuf, va list th eVA , を消し , 外部からパラメータを得るように Size t inSize, するだけで , ほとんど P2sp ⅱ n げと差はない。 const Char * inFormat, List 10 のようになる。 ⅲ Size は最大格納サイズで , これを超え [ 注 ] て文字列の格納を行わないものとする。 [ 1 ] スボコンが嫌い 制限を付けるには , あらかじめ制限量を 整数の変数で持たせる考えもあるが , C 言 亡くなった手塚治虫氏という有名な漫画 語ならではの考えで , ポインタの比較によ 家もスボコンが嫌いで , なぜこういうのが って制限を付けようと思う。というのも実 ウケるのか理解できず , 周りの人に聞いて 際にやってみればわかるが制限量の変数を 回ったというエピソードが残されている。 持たせた場合 , 文字列へのポインタとは別 その話を聞いて , 筆者は手塚治虫氏が大好 個に制限量の判断を同時にやらねばならず きになった。 プログラムがややこしくなるからである。 [2]ctype. h あれこれ説明するより実際のコードを見せ ただしいまどきのコンパイラは C + + 兼用 よう (List9)0 山 eLimit がミソである。おわ が多く , 新しい規格に従っている場合 , cty かりいただけるであろうか ? pe. h ではなく cc 甲 e にある。これにかぎら va-list 対応 ず . h というファイルを調べると内部で , #include <CXXX> 残った課題は実に簡単である。だから最 となっていることが多い。 オ ? ジ ; 暴指向を始めよう Ja 言語プロ ? ラミングレッスン ( 下 ) 結城浩著 結城浩・ = よプ 0 ~ 崧ーツ初者のための の知識を踏まえ , クラス、クラスの継承、例外。 以レッド、バッケー ~ など、オブジェクト指。 の概念を、多ぐのサノブルプログラムを使っ。て、 明快に解説している。を三イをえイ 虹好評発売中 ! SOFT ソウトバンク穴ブシング株式会 BANK " http://books.s Oftba nk 上 0. / ublishing EL : 04g6 2- ・加 0 示価格は税別 ) B5 変 344 ページ ISDN 4-7973-1010-3 本体価格 2400 円 真紀俊男のローテク講座 137
五ロ = 編 の 0 ◆ 個人が趣味でやっている場合もこれらの 調べると「禁じ手パターンの宝庫」と化して 問題は起こりますが , ある程度以上になる います。この手のソフトのソースが公開さ ひとつの関数の行数が多すき、る と勝手に自滅したり , 「飽きてしまいまし れることはあまりありませんが。そういえ 深刻度☆☆☆ ( 重度 ) た。終了です」と降参して , それ以上は増え ば某 Web プラウザがオープンソースになっ ません。しかし , そういう自滅や「飽きてし [ 症状 ] たときに少しだけソースを見たのですが , まいました」が許されないプロは深刻です。 やはり「分量に関する禁じ手パターン」の宝 プログラムの解読や改造が困難になりま プロだから , どんなにイヤでも仕事はや 庫でした。オープンソースのメリットを生 す。また , バグが発生しても退治しにくく らなくてはならないのです。そのため , ソ かすどころか , 全然生かせない実例となり なります。 ースがどんなに読みにくくなっても , 際限 つつあります。それはオープンソースとい [ 原因 ] なく分量が増えていくという運命が待って 多くの場合 , 怠慢が原因です。すなわち , うスタイルのよし悪しという問題ではなく , います。しかも , このような肥大化したプ 分量に関する禁じ手パターンにより , もは 適当にプログラムを入力する習慣が付いて ログラムは自分ひとりで開発した場合でも や誰にも手を付けられないような惨状を呈 いるものと思われます。 わかりにくいことが多いのに , まったく赤 [ 対策 / 予防 ] しているわけです。なかなか新しいバージ の他人が作ったプログラムとなると , わけ ョンが現れなかったのも当たり前で , あれ 実はどう対策 / 予防していいのか筆者に がわかりません。そこで , そのソースを受 に手を付けられる人がいるなら , そうとう も思いつきません。職業プログラマの場合 け継いだ人はなるべく既存のソースをいじ 勇気があるか , かなりのパワーがある人で 1 行増やすたびに , いくらか給料をカット らないで「接ぎ木」をし , ますます分量が増 しよう。それだけの勇気とパワーがあれば , するというのはどうでしようか ? 新規にプログラムを作り直すほうが手つと [ 例外 ] えていくわけです。 最近の巨大ソフトが , 「機能は豊富だが り早いような気もします。 試作段階や試行錯誤している場合は許さ [ 注 3 ] ひとりで開発するのではなく , 複数の バグだらけ」という現実は , すでにみなさ れるかもしれません。しかし , 本番ではキ プログラマが共同してプログラムを開 んがご存じのとおりです。実際に , なかを チンと関数を分けて整理しましよう。 発するスタイル ひとつの関数の行数が多い例 VOid psprintf(char *outBuf,const Char *inFormat,. va—list theVA; int thel; char *theCp; char theCb[161; static char theN[ ] = ” 0123456789ABCDEF 第 va—start(theVA,inFormat); while(*inFormat) { if ( * inFormat switch(*(inFormat 十 1) ) ( case C thel = va—arg(theVA,int); if(isprint(thel) ) { *outBuf 十十 = (char)thel; }else{ *outBuf 十十 = inFormat 十 = break; thel = va—arg(theVA,int); if(thel く 0 ) ( *outBuf 十十 = thel * = -1 ・ theCp = theCb; while(thel > = 10 ) { *thecp 十十 = theN[theI 10 thel / = 1 の *theCp 十十 = theN[theIl; while(--thecp > = theCb){ *outBuf 十十 = *theCp; inFormat 十 = 2 ー break; case S thecp = va—arg(theVA,char * List while(*thecp){ *outBuf 十十 = *theCp 十十一 inFormat 十 = break; case X thel = va—arg(theVA,int) : if(thel く 0 ) { *outBuf 十十 ' 、 thel * = ” 1 ー theCp = theCb; while(thel > 16 ) { *theCp 十十 = theN[theI 宅 16 thel / = 1 研 *theCp 十十 = theN[theI]; while(--thecp >= theCb){ *outBuf 十十 = *theCp; inFormat 十 = 2 ー break; *outBuf 十十 inFormat 十 = 2 ・ break; default: *outBuf 十十 * i れ FO て ma に十十一 break; )else{ *outBuf 十十 = *inFormat 十十一 *outBuf = ' 0 ' va—end(theVA); プログラミングの禁じ手 35 特集 1
で , 大規模なチームプログラムをやってい 構造化が進んで , 保守性の点でもプログラ [ 備考 ] ムの質が向上します。 switch 文を使った場合 , case ラベルが大 るところでは , たいして珍しくなかったり 量にあると , いとも簡単に行数が多くなり します。 共通部分をまとめない こういう巨大関数ができあがる理由はい ます。困ったことに最近のウインドウもの ろいろ考えられますが , ひとつの単純な理 のプログラムは巨大な switch 文を簡単に作 ☆☆ ( 中程度 ) 深刻度状況によっては☆☆☆ ( 重度 ) 由ではなく , 複数の理由が絡み合い , 積み らせるような仕様になっています。 [ 症状 ] 重なって , できてくる場合がほとんどです。 たとえば , List 25 に示した Pspr ⅱⅲ関数は たまに , こういう巨大関数を作るプログラ プログラムの解読や改造が困難になりま 85 行あります。これを見て , 「まだこの程 度なら根性を出せば理解できる」という人 マのなかに「これだけの巨大関数を作れる す。また , バグが発生しても退治しにくく もいれば , 「 50 行を超えるのは勘弁してく のは技術的にすぐれているからだ」と自己 なります。 満足をしている場合もありますが , 見る人 [ 原因 ] れ」という人もいます。よく聞かれる目安 が見れば単なる無能である証拠だと見破っ 不勉強と怠慢が原因です。 として , 「 1 画面のなかで収まる程度の行数」 [ 対策 / 予防 ] とか , 「プリンタ用紙の 1 ページ以内に収ま てしまいます。一刻も早く認識を改めるべ まとまりのない , 読みにくいプログラム るのが限度」などがあります。 きでしよう。 ちなみに先ほどの List25 を複数の関数に を書くプログラマには「もっと勉強してく ところが筆者が実際に目にする例は , そ ださい」といいたいところですが , この手 分割して書き直すと , List 26 のようになり んな「かわいい」話ではなく , 2000 行とか ます。 List 25 に比べると , ある程度「構造」 の人たちは , 絶対といっていいほど勉強し 3000 行に及ぶ「最長不倒関数」とでもいいた てくれません。事実上 , 効果的な [ 対策 / 予 が見えやすくなっていることがわかるでし くなる凶悪な例です。ただし , それがめっ 防 ] はないのかもしれません ( 苦笑 ) 。 たに現れない特殊な例だと思うと大間違い このようにすることで , より よう。また List 25 を複数の関数に分割して書き直した例 void Psprintf(char *outBuf,const char *inFormat, ... ) va—list theVA; va-start(theVA,inFormat); 血 e ( * 土 nFo て t ) ( if(*inFormat = = s 響 ch ( * ( 土 0 て t + 1 ) ) { case C outBuf = P—Char—Buf(outBuf,va—arg(theVA,int)); inFormat 十 = 2 ー break; outBuf = P—IntN—Buf(outBuf,va—arg(theVA,int),10); 十 = inFormat break; P—Text—Buf(outBuf,va—arg(theVA,char 料 outBuf inFormat 十 = 2 を break; List 26 s 0 char *P—Char—Buf(char *i0Buf,int inC) 土 f ( 土 sp て土北 ( 土 nc ) ) ( * 土 OBuf 十十 (char)inc; )else{ 土 OBuf 十十 = ' ? ' return 土 oBu static char *P—Text—Buf(char *i0Buf,char *inT) whiIe(*inT){ 社 oBuf + 十 = * 土 nT 十 て e し u てれ ioBuf; static char *P—IntN—Buf(char *i0Buf,int i 心 , 加と inRadix) ch 矼 theBuft16J; char *thePtr; 土 f ( i 心く 0 ) ( * 土 oBuf 十十 = ' ー ' 土心 * = -1 theptr = P—IntN-Buf-Sub ( theBuf ,inNum , inRadix whiIe(--thePtr 〉 = theBuf ) ( * 土 OBuf 十十 *thePtr ー return ioBuf ー st 北土 0 char *P—IntN-Buf-Sub(char *ioBuf,int 土心Ⅷ , 土北 inRadix) static char theN[ ] = ” 0123456789ABCD を F ・ while(inNum > まånRadix) { *ioBuf 十十 = theN[inNum き inRadix]; ま心 / = 土れ Rad 土 *i0Buf 十十 = th は心島 return iOBuf ー case X P—IntN—Buf(outBuf,va—arg(theVA,int),16Yi outBuf inFormat 十 2 ・ break; case ー *outBuf 十十 = inFormat 十第 break; default: *outBuf 十十 = *inFormat 十 break; }else{ *outBuf 十十 = *inFormat 十 *outBuf va—end(theVA); 36 C MAGAZINE 2000 4
ぎに割算をし , 1 桁ずつを切り出してバッ フアに書き込んでいる。 1 桁の数値を 1 文 字に変換するのに市 eN という配列を使って いるが , こうするほうがプログラムコード がスッキリし , しかも速くなる。高速なデ ータ変換をしたいときに , あらかじめ固定 配列を用意するのは , よく愛用される手法 である。これを知らないで , List 7 のような 感じでプログラムを作る人もいる。もちろ ん間違いではないが , ほかにも方法がある ことを知ってほしいと願う。 「ほかにも方法が・・・・・・」といえば , d ⅳとい う系列の関数を使う方法もある ( List8 ) 。 れは割算の商と剰余を同時に求めて市 v ー t と いう構造体に答えを返すものである。 機械語レベルの話で恐縮だが , 割算命令 で同時に商と剰余が 1 命令で求められる cp U があるが , C 言語のプログラムでは同じ 割算になるのに , 商と剰余を求めるために 2 回同じ割算をするのがもったいない ( ちょ っと , みみっちいかもしれないが ) のを気 にして , この div が作られたのではないかと 想像する ( 真相はどうかは知らない ) 。 c = div(a,b) とした場合 , c. quot に a+b の商 , c. rem に剰 余が得られる。ただ , 実際問題 , ここで示 したような例では本当に速度アップが期待 できるものかどうか疑問ではあるが。 P-IntN-Buf-Sub の別解答例 制限を付ける処理 こで , やり残しているのはふたつあり , あり , ー vsnp ⅱ n げの代用品が最終目標である。 こまでのプログラムは , まだ試作品で ②制限を付ける ( 1 )va_list 対応 136 C MAGAZINE 20 4 ここで Psprintf の制限付きバージョンで からである。 いと思う。というのも ( 1 ) は案外 , 簡単だ た変形パターンで , 先に ( 2 ) から片付けた いが , いつものローテク講座のチマチマし となる。いっぺんにふたつを片付けてもい 0 P 」 ntN_Buf_Sub の別解答 return ioBuf ・ *ioBuf 十十 = *thePtr ・ while(--theptr 》 = theBuf & & ioBuf く inLimit){ thePtr = P-IntN-Buf-Sub(theBuf,inNum,inRadix); inNum * = - 場 *ioBuf 十十 = ' - ' if(inNum く 0 ) ( char 社 he て・ char theBuf[161; static char *P2-IntN-Buf(char *ioBuf,char *inLimit,int inNum,int inRadix) return ioBuf ・ *iOBuf 十十 = * 土 nT 十 whiIe(*inT & & ioBuf く inLimit){ static char *P2—Text—Buf(char *ioBuf,char *inLimit,char *inT) va—end(theVA); *outBuf = 0 ' *outBuf 十十 = *inFormat 十十一 )else( break; *outBuf 十十 = *inFormat 十 default: break; inFormat 十 = 2 ー *outBuf 十十 = ' ' case ' inFormat 十 = 2 ・ outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(theVA,int) , 16 case X break; inFormat 十 = 2 ・ outBuf = P2-Text—Buf(outBuf,theLimit,va—arg(theVA,char * ) case S inFormat 十 = outBuf = P2—IntN—Buf(outBuf,theLimit,va—arg(theVA,int) , 10 break; inFormat 十 = outBuf = P—Char—Buf(outBuf,va—arg(theVA,int) case C “ ch ( * ( i 0 て m 北十 1 ) ) { if ( * 土禛 orm 北 = = ' ) { whiIe(*inFormat & & outBuf く theLimit) ( va—start(theVA,inFormat); char *theLimit = outBuf 十 inSize - va 」に theVA; void P2sprintf(char *outBuf,size—t inSize,congt char *inFormat ) P2sp 「 intf return 土 oBu *ioBuf 十十 = theN[inNuml; inNum = theD. quot; *ioBuf 十十 = theN[theD. reml; theD = div(inNum,inRadix); whiIe(inNum > = inRadix) ( div-t theD; static char theN[ ] = 第 0123456789ABCDEF を static char *P-IntN—Buf—Sub(char *ioBuf,int inNum,int inRadix)
たタイマを自動的に破棄するようになって います。 ソースコードの詳細 ケーションのソースコードから , ポイント それでは , 今回作成したサンプルアプリ の部分を解説します。 LIB を利用する場合 , SCRNSAVE.H に次の ばならないことです。これは SCRNSAVE. ッポックスはその ID を 2003 で作成しなけれ 注意が必要なのは , この設定ダイアログ を表示しているだけです。 述した内容を取得してメッセージボックス リックした場合にエディットボックスに記 このダイアログボックスで国ボタンをク 3 ) の動作をコーディングします。ここでは , ときに表示されるダイアログボックス : Fig. ックスの一ボタンをクリックした ックス ( 「画面のプロバティ」ダイアログボ スクリーンセーバを設定するダイアログボ ScreenSaverConfigureDialog 関数では ScreenSaverConfigureDiaIog 関数 st 1- ① ) 。 単純に TRUE を返すだけでかまいません ( Li ムコントロールなどを利用しない場合は , するものです。今回のようにとくにカスタ コントロールなどの登録を行うときに利用 RegisterDialogClasses 関数は , カスタム RegisterDialogCIasses 関数 List サンプルプログラムのソース ( 抜粋 ) スクリーンセーバ作成 sc て nsave. は b をリンク #define STRICT #include <windows . h> #include <windowsx. h> #include <commctrl.h> $include <scrnsave. h> #include "test.h" #include ”て eso ce. h ″ #define ID—TIMER 1 static HWND hWndmain; static HINSTANCE hlnst; static char szAppName[ ] char szMsg [ 256 SYSTEMTIME st; static BØL HICON hIcon=NULL; 0 CMAGAZINE" ・ 記述があるためです。 #define APP 100 #define DLG SCRNSAVECONFIGURE 2003 そのため 2003 以外の値でダイアログボック スを作成すると , 「画面のプロバティ」ダイ アログボックスで一ボタンをクリ ックしても何も起こりません。 こで定義されている ID APP はスクリ * function : ScreenSaverProc LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT msg, hlnst 哥 (HINSTANCE)GetWindowLong(hWnd, GWL—HINSTANCE); 0 e AI 訂 . SetTimer(hWnd, ID—TIMER, 1000 を NULL); case WM-CREATE: switch ( msg ) { int x,y; HINSTANCE hlnst ー PAINTSTRUCT ps; BITMAP bmp; HBITMAP hBitmap; HI.x: hdc, hdc-m; WPARAM wparam, LPARAM lParam) else { GetObject(hBitmap, size0f(BITMAP) , &bmp); hBitmap = LoadBitmap(hInst, *IDB—BITMAP2"); = TRUE; hBitmap LoadBitmap(hInst, "IDB—BITMAPI" = FALSE; ifftm = TRUE){ hdc = BeginPaint(hWnd, &ps)i y = ( GetSystemMetrics(SM—CYSCREEN)— ( (int)bmp. bmHeight)*5 ) / 2 x = ( GetSystemMetrics(SM-CXSCREEN) ー ( (int)bmp. bmwidth)*S, ) / (int)bmp. bmWidth,(int)bmp. bnHeight, SRCCOPY); ( (int)bmp. bmHeight)*5, hdc—m, 0 , 0 , StretchBIt(hdc, x, y, ( (int)bmp.bmWidth)*5, SeIectObject(hdc—m, hBitmap); hdc-m = CreateCompatibIeDC(hdc); break; default: return 0 ー PostQuitMessage ( 0 KillTimer(hWnd, ID—TIMER); case WM—DESTROY: break• InvalidateRect(hWnd, ル叫 TRUE); case WM—TIMER: break EndPaint(hWnd, &ps); DeIeteObject(hBitmap); return DefScreenSaverProc(hWnd, msg, wparam, lParam 126 C MAGAZINE 2000 4
List 5 候補語の既存状態をチェックし既存でなければユーザの判断を仰ぐ 1 : 0 土 d checkAndRegister( String candidate, Fi1e cu て P 土 1e ′ int lnum ) { hand1eNewword( candidate, curPi1e, lnum ) ー }else{ / / 既存語ではなかったのでユーザに見せて判断を仰ぐ return; addposition( candidate, cu てを土 le ′ lnum / / 位置情報を加える = = セてⅡ e ) { }else if ( index. containsKey( candidate ) ↓拾う語の HashtabIe 内にある return; / / なにもしない if( noindex. 00れta土n日( candidate ) = = true ) { / / 捨てる語の Hash 日 et にある 9 : 1 : void hand1eNewword( String WO て d. Pi1e f 土 1e. int lnum ) { 初めて拾った語に対する処理の入口 List 7 idxval. addLocInfo( f 土 1 lnum / / メソッドの返し値は使わない 2 : JIndexVa1ue idxval = (JIndexVa1ue) (index. get ( idxWord ) ) ー 1 : VOid addPosition( String idxWord, Fi1e f 土 1e ′ int lnum ) { 語が見つかった位置をその語の位置情報に新たに加える List 6 10 : } switch( policy ) { 土 policy = getpolicy( word / / ユーザに問う「この語をどうする ? 」 10 : 11 : 12 : 14 : 15 : 16 : 17 : 18 : 19 : / / 捨てるのなら case DI SCARD : noindex. add( word / / 捨てる語の Ha 日日 e セに加える break ー case PICKUP: / / 索引に取り上げるのなら各種の処理へ進む string mword = modifyword( 曾。て d / / 語の形をユーザが変更する if( ! ( word. equals ( mword ) ) & & index. containsKey( mword ) ) { addPosition( mword, file, lnum / / 変更後の語が Has table にあった て e 加て町 / / ↑その場合は位置情報だけを記録する string reader = getReader( mword / / 平仮名による読みを入力させる index. put( 等 0 て d. new JIndexVa1ue( reader. f 土 1e. lnum ) ) ー break; / / ↑そして日 as れセ le に新たなエントリとして登録 / / end 日曾土 tc れ sys セ 5. ex 土セ ( 0 / / 質問ダイアログがクローズされた default: / / そのほかの場合 List 8 語に対するユーザの方針を問う 1 : 土 n セ getpolicy( String word ) { 土れセ retval = JOptionpane. showConfirmDia10g( セ h 土 8. word, ”この語を索引に入れますか ? ” 3 : JOptionpane . YES_NO_OPTION, JOptionPane . QUESTION—MESSAGE ) ー if ( retval = = こ Op セ io れ pane. Y S ー OP で工 ON ) return else if ( return 10 : elge PICRUP ー retval = = JOptionpane . CLOSED_OPTION ) BREAK ー DISCARD; 11 : 12 : Fig. 3 return 』 ndex の初期ダイアログ この語を索引に入れますか ? データ構造 の集まり〉とく索引に取り上げる語の集ま り〉にすでに含まれているかどうかを調べ 0 C 言語フォーラム 141 ば行番号イコール , パラグラフ番号となり の原稿のファイルがプレーンテキストなら 「ファイル名と行番号」を記録します。本 えないので , こでは語の位置情報として の私としては本の最終決定ノンプルを知り に編集者ではない単なる執筆者という立場 ル ) であるべきですが , 前号で述べたよう 位置情報は本来ならばページ数 ( ノンプ 情報をオプジェクトに加えます ( 登録しま ドです。このメソッドにより , 新たな位置 行目で呼び出している add clnfo ( ) メソッ の中で最も重要なのが , addPosition() の 3 少々のメソッドです。その少々のメソッド 「出現位置情報」を記録するデータ構造十 は , 語の「読み」一一平仮名の読みーーと 見た JIndexValue オプジェクトです。それ を取り出しています。その値部分は前号で する値 , すなわち [ キー : 値 ] ペアの値部分 index) から , 候補語 idxWord をキーに対応 索引に取り上げる語の Hashtab1e ( 名前は この addPosition ( ) メソッドは 2 行目で , おきましよう (List 6 ) 。 えるメソッド addPosition() をここで見て という作業をします。②の位置情報を加 を行う 見せて判断を仰ぎ必要な各種の処理 新しい語である・・・・・・ならばユーザに ( 3 ) どちらの集まりにも含まれていない table に記録する らばその語の新たな位置情報を Hash 集まりにすでに含まれている・・・・・・な ( 2 ) 索引に取り上げると判断された語の ならば何もしない の集まりにすでに含まれている・・ ( 1 ) 索引に取り上げないと判断された語 ぞれ , 今拾った語が , else if, else の計三つの条件節の中でそれ この checkAndRegister ( ) メソッドは if, s 断 ( ) メソッドです (List5)0 ます。それを行うのが , 次に示す checkAnd
ロの List ハッシュを使う ( out ⅱ ne2. pl) List 同じ動作をする謎のプログラム ( out ⅱ ne3. pl) $file = yourfile. txt' 1 : 2 : 3 : 4 : 5 : 6 : 8 : open(FILE, $file) 0 て die; 9 : while ( く FILE>) { foreach $regex (keys %prefix) { 10 : if ( /$regex/) ( 11 : print $prefix{$regex) , $ 12 : 13 : last ー 14 : 15 : 16 : ) 17 : close(FlLE); $file = yourfile. txt' 1 : 2 : 8prefix = ( 3 : ' ◆ 5 : 6 : . join( ' ) Ⅱ ' , keys(%prefix)) 8 : $pattern = ( ( ' 9 : open(FILE, $file) or die; 10 : while (<FILE>) { print($prefix{$ 十 ), (-) if ( /*$pattern/); 11 : 12 : ) 13 : close(FILE); ハッシュの添え字のことを「キー」と呼び , 短く書けばよい , というものではないです ・シンプルに それに対応する要素を「値」と呼びます。 ね。 List 14(outline0. (l) は正しく動作するプ List 16(outline2. (l) では , ハッシュ %pre 同じ動作をするもっとよいプログラムが ログラムですが , 特殊な変数 $ ーを使うと f ⅸのキーは「 ^ ◆」のような文字列で値は「」 ありましたら , ぜひ結城浩くhyuki@hyuki.c もっとシンプルに書くことができます。 Li のような空白文字の列です。ハッシュ % om > までご連絡ください。 st 15 ( outl ⅲ el. (l) はいちいち $ ⅱ ne に代入せ prefix を初期化するときにはコンマ区切り 次回は「ネットワーキング」 ず , 暗黙のうちに仮定される変数 $ ーを活 の代わりに = > が使え , 対応関係をわかり 用しています。これだとムダな記述がない やすく書くことができます。またこれだと , ぶん , どういうパターンにマッチさせてい インデントを空白ではなく「 > 」にしたり 今回はテキスト処理のいくつかの例を示 るかが読みやすくなりますが , その反面 「→」にしたりするときでも修正が楽になり しました。いかがでしたか。次回は「ネッ Perl に慣れていない人には何をやっている ます。 トワーキングの楽しみ」と題して , Perl で List 16 (outline2. (l) では foreach を使って かわかりにくいと思われる危険性もありま ネットワークプログラミングを行ってみま しよう。どうぞご期待ください。 す。 ハッシュ %prefix のキ—$regex に関するル ープを作り , 現在対象になっている行 $ ー もしも , ご意見やご質問がありましたら , ・ハッシュ ( 連想配列 ) を使う が $ regex にマッチするかどうかを調べて 本誌綴じ込みの編集部へのハガキでお知ら せくだされば幸いです。また , ご遠慮なく List 15(outline1. (l) は慣れてくればわか います。マッチした場合には , $prefixl$r egex Ⅱすなわちその正規表現に対応した 結城浩くhyuki@hyuki.com/ へメールをお送 りやすいプログラムですが , 記号とインデ りください。本連載に関するメールには表 ントの関係をさらにはっきりと表すために インデント ) を現在行の前に置いて表示し 書き換えてみましよう。 perl のハッシュ 題に [MP] という文字を含めてくださると ます。 last というのは C 言語の break に似て ( 連想配列 ) と呼ばれるデータ構造を使いま こでは foreach を中断する 助かります。本連載に関する U 糺は , いるもので , す。 のに使います。 http://www hyuki.com/mp/ List 16 (outline2. (l) は , 「 ^ ◆」のような正 foreach をなくして List 17 (outline3. (l) の です。 規表現と , インデントの対応を % pre ⅱ x と ようなプログラムを書くこともできます。 参考文献等 いうハッシュで表現したものです。 これも不思議なことに List 16(outIine2. pl) ハッシュというデータ構造は , ひとこと と同じ動作をします。 [ 1 ] 『 Effective Perl 』 , Joseph N. HaII , RandaI でいえば「文字列が添え字になっているよ 変数 $ pa れ ern は「 ^ ( ( ◎月 ( ・月 ( ◆月 ( ◎ ) ) 」 L. Schwarts 著 , 吉川邦夫訳 , アスキー うな配列」です。通常の配列は , 数字が添 という正規表現になり , これとマッチさせ 出版局 ると特殊な変数 $ + に「最後にマッチしたカ え字になっており , 配列@array の 0 番目の http://www effectiveperl.com/ 要素は $ array [ 0 ] で表されます。一方ハッ ッコの内容」が格納されます。そのため , [ 2 ] 『詳説正規表現』 , Jeffrey E. F. Friedl 著 , $prefix 月で目的のインデントが取り出 シュは文字列が添え字の役目を果たし , ハ 歌代和正監訳 , オライリー・ジャパン ッシュ %hash の 'key' という文字列に対応 [ 3 ] メールマガジン「 perl クイズ』 せるのです。 こまでやると読みにくく感 する要素は $ hash ド key Ⅱで表されます。 じる人は多くなるでしよう。プログラムは http://www.hyuki.com/pq/ Pe 日プログラミングの楽しみ 97
プログラミン % 禁じ手 特集 1 LiSt 36 例 し 隠 を 0 0 で ロ ク マ 常に「近似値」が出るということを覚悟すべ きです。たとえば , List 34 のようなコーデ イングは一見正しいように思えますが , 期 メモー ここでは c 十 + の例外処理機構すなわち t , cat 曲洋肚 ow を 待したとおりの挙動にならないことがあり ある程度、模倣するマクロを定義している。使い方は以下の通り 加し thevar; / / c 曲で t 0 ⅵ直を受け止める変数 ます。というのも浮動小数の演算には「丸 、ノ / t プロックの開始 ptry( め誤差」と呼ばれる誤差が付きものだから 。 w ( 値 / / t 0 曾の実行 です。人間の感覚で 10 進数で考えたときに 割り切れていると思われる式でも , コンピ pcatch(thevar)( 〃 cat 曲プロックの開始 ュータの内部の 2 進数の演算では割り切れ 0 瓧 ch で受け止める変数新 eva てはいあらかじめ土型変数として定義するこど pt 社 ow が実行されなかった場合、 theva てには 0 が無条件に入っている。 ずに誤差が発生している場合があります。 pt 社 ow で投げる値は 0 以外であるこど。 0 である場合、 pcatch プロック内は 実行されない。わざと 0 を投げるテクニックもイ吏えるかもしれないが、 そのため , ある値と等しいかどうかを判断 c 十 + 本来の t 社 0 Ⅳの仕様通りにはいかなくなる。 するには , いったん浮動小数を整数値に変 extern 加 gPTHROW—VALUE——i / * p に h て OW レベル * / 換してから判断するか ( ただし変換すると #define ptry きに桁落としをしないよう注意すること ) , gPTHROW—VALtjE——三 #define ゑセ h て 0 曾 ( V ) その値との差の絶対値が , ある程度以下な do ー gPTHROW—VALUE== VAL; 第 ら等しいとみなすロジックを採用するのが goto PCATCH—ENTRY——;= 第 ) ( 0 ト 常套手段です (List 35 ) 。 #define pcatch(VAR) PCATCH—ENTRY— VAR ま gPTHROW—VALUE——;- 第 ← gPTHROW—VALUE---e = 無節操な goto の使用 ( V ) 深刻度☆☆☆ ( 重度 ) チームプログラムの場合は , [ 症状 ] [ 原因 ] のですから , プログラマが , 「 goto 」の持つ強力な性格 なおのこと goto は禁じ手扱いです。 プログラムの解読や改造が困難になりま しかし , goto は , グローバル変数と同様 を理解していないことが原因です。 す。また , バグが発生しても退治しにくく [ 対策 / 予防 ] に強力な効能を持っています。また , 工ラ なります。 ー処理や例外処理に限定して「例外」的に いっさい , goto を使わないか , 例外や工 誤った浮動小数の比較処理 goto を使う方針もあります。ところが , 開 ラーに関する処理にならマクロや C + + の例 外処理に切り替えるとよいでしよう。 発プロジェクトにかかわっているすべての プログラマにそこいらへんがうまく伝わら [ 例外 ] 試作段階や試行錯誤している場合は許さ ないと「あ , goto 使ってら一 , バッカでえ」 と揶揄の対象になったり , あるいは「そう れるかもしれません。 か , g 。 to を使ってもいいんだ」となって軽率 [ 備考 ] どういうわけか「 goto 文はよくない」とい で怠慢なプログラマに都合のいい口実を与 うのは C 言語でプログラムをしている人は えることになりかねません。 たいてい知っています。わざわざ「禁じ手 ちなみに , List 36 のマクロは実際には パターン」としてピックアップするほどでも goto を使っているものの , それをマクロの なかに隠すことで安全に「例外処理」を実現 ありません。しかし , なぜ「よくない」かを した例です。 きちんと説明できるでしようか ? 理由が しかし , 本当に恐いのは明確に「 goto 」と わかっていないのに「よくない」といってい 表現しているのではなく「隠れた goto 文」に る人も多いような気がしてなりません。 なっている場合です。たとえば , break や 簡単にいえば , goto を乱用すると処理の con ⅱ nue というのは , goto と書かれてはい 流れが追い切れなくなり , わけがわからな ないものの , そこから一気に別の場所に飛 くなるからです。個人でプログラムする場 ぶという点で , まさしく goto 的な性格を持 合でも , わけがわからなくなる危険がある 特集 1 プログラミングの禁じ手 41 ノ * 凵 st 34 void f ( ) ödouble„ a; if(a 0 ) ( / * ←この部分がまずい * / LiSt 浮動小数の比較処理 #include <math. void f( ) double if(IsNearIySameDouble(a,0) ) { 土 n し IsNearlySameDouble(doubIe inA,double 土 ) return fabs(inA - inB) く 0.000 場