に比べたら for 文て、コマンドを探す時間など けて、すから , もちろんたいした時間がかか ューザからの入力を待って動作するプログ は無視て、きる手間といえます。 ラムて、すから , ューザからの入力スヒ。ード るわけて、はありません。とくにこの例題は しかし , もし探す個数が非常に多い場合 , あるいは相手が人間て、はなくコンヒ。ュータ 配列を使ったコマンド選択メニュー のプログラムだったりした場合は話が違い ます。そんな場合は , テープルの中から目 1 : #include く stdio. h 〉 2 : #include く ctype. h> 的のものを探し出す時間が無視て、きない長 3 : #include く stdlib. h> 4 : #include く process. h> さになったりします。だからこそ探索アル 5 : 6 : typedef struct { ゴリズムと呼ばれるものの意義があるわけ 7 : Char cmd; 8 : VOid 第 て、すが , それについては本連載て、はこれ以 9 : Char *help; 第 10 : } ITEM; 上は触れません。 鷲 12 : void main(void); 13 : void cmd—curdir(void) ; コドンタブリタ 第 14 : void cmd_dir(void); 15 : void cmd—exit(void); 17 : ITEM menu ロ 例題 1 て、はコマンド選択は数字を使い , 例 { 'C', cmd_curdir, " カレントディレクトリを表示する " } , ”ディレクトリを表示する” } , { ' D' , cmd_dir, 19 : 題 2 て、は英文字 1 文字を使いました。それて、 { 'Q', cmd-exit, ”終了する " } , は , コマンド選択に文字列を使おうとした 22 : 23 : #define MAX ITEM (size0f(menu)/sizeof(ITEM)) らどうなるて、しようか。ューザが入力した 24 : 25 : char buf[BUFSIZ] : 3 文字列によって何かコマンドが選択される 26 : char cmdl ine[BUFSlZ] ; 27 : int exit—flag = 0 ; これはいわゆるコマンドインタブリタ 29 : void main(void) と呼ばれるプログラムになります。インタ 31 : プリタは英語て、 interpreter と書き , 通訳と 32 : 33 : いう意味を持っています。すなわちコマン 35 : ドインタブリタとは入力されたコマンドを 36 : 通訳して , その文字列に対応した処理を実 38 : 39 : 行してくれるプログラムのことてす。 物 40 : 41 : たとえば MS-DOS の COMMAND.COM 42 : はコマンドインタブリタて、す。あなたが M 、 44 : S-DOS<COPY という文字列を入力する 45 : 46 : と , COMMAND.COM というインタブリタ 47 : 48 : はそれを通訳して , その文字列に封応した 49 : 50 : 処理を実行してくれるわけてす。 MS-DOS 51 : 52 : 上て動作するコマンドインタブリタは COM MAND.COM だけて、はありません。ユーザ 3 55 : void cmd curdir(void) の入力したコマンドを解釈・実行してくれ system( ” DIR /W") ; ればいいのて、すから , SYMDEB などのデバ 59 : 60 : void cmd_dir(void) ッガや EDLIN などのエデイタもコマンドイ pr intf(" ディレクトリ名を入力してください・ 62 : ンタブリタといっていいて、しよう。 gets(buf) ; 63 : sprintf(cmdline, "DIR /W Xs ” , buf) : 64 : コマンドインタブリタを作ろうというと system(cmdline) ; き , そのプログラムの内部にはテープルを ー 68 : void cmd_exit(void) 持っことになると思います。そのテープル printf(" 終了します。 *n"); とは , そのコマンドインタブリタが解釈て 71 : exit—flag = 1 : きるコマンドのテープルてす。 COMMAN LiSt int i; ChO i ce ; Char while (!exit—flag) { printf( ” for (i = 0 : i く MAX ITEM; i + + ) { printf("Xc. xs*n", menu[i]. cmd, menu[i]. help) ; printf( ” pr intf ( " コマンドを入力してください・ gets(buf) ; choice = (islower(bufC0]) ? toupper(bufC0]) : bufC0]) ; fo 「 (i = 0 ; i く MAX ITEM; i + + ) { if (menu[i]. cmd = choice) { (*menu[i]. func)(); break; if (i = MAX-ITEM) { printf ( ”入力が誤っています。” ); プログラミングの工ッセンス 121
配列を使った番号選択メニュー List という形は多方向分岐を思わせます。つま り , choice という変数の値に応じていくつも の処理に分岐していくのて、す。もともとメ ニューから選択するというのはまさにそう いう処理なのて、すから , このような多方向 分岐が登場するのは当然のことといえまし よう。て、 , C 言語にはもともと多方向分岐の ための構文が用意されているのて、 , それを 使ってみようというわけて、す。て、は List 2 を 見てください if 文を使った List 1 と , switch 文を使った List 2 を比べてみると , その対応がよくわか ると思います。 List 1 から List 2 への変形は よくあるパターンて、すね。対応もすぐわか るし , 難しくありません。注意するのは sw itch 文て、 break を忘れないことぐらいて、す。 多方向分岐て、あることをはっきり示すため に if 文て、はなく switch 文を使ってみました。 普段はスニーカて、外出するけれど , 今日は デートだから革靴を履いていこう。 List 1 か ら List 2 への変形はそんな感じかもしれませ ん。 1 : #include く stdio. h 〉 2 : #include く stdlib. h 〉 3 : #include く process. h> 4 : 5 : typedef struct { 6 : VOid 7 : Char *help; 8 : } ITEM; 9 : 10 : void main(void); 11 : void cmd-curdir(void) : 12 : void cmd-dir(void); 13 : void cmd—exit(void) ; 14 : menu ロ ー 15 : ITEM { cmd_curdir, { cmd-dir, { cmd_exit, ー 20 : ー 21 : #define MAX_ITEM : 23 : char bufCBUFSIZ] : 24 : char cmdIine[BUFSIZ] ; 25 : int exit—flag = 0 ; 26 : 27 : void main(void) 28 : { int i, choice; 29 : 30 : while (!exit-flag) { printf( ” = 32 : for (i = 0 : i く MAX-ITEM; i + + ) { 33 : printf( ” Xd. Xs*n", i + 1 , menu[i]. help); 34 : 35 : printf( ” 36 : printf ( ”番号を入力してください・ gets(buf) ; 38 : choice = at0i (buf) 39 : if ( 0 く = choice & & choice く MAX ITEM) { 40 : (*menu[choice]. func) ( ) ; 41 : 42 : else { 43 : printf(" 入力が誤っています。” ); 44 : 45 : 46 : 47 : } 48 : 49 : void ー 52 : } 53 : ー 54 : void 55 : { 56 : 57 : 58 : 59 : 60 : } 62 : void cmd—exit(void) 63 : { printf( ”終了します。” ); 64 : exit—flag = 1 ; る す 示 } 表” をる ト示 ク表 レを デト” トクる ンレす レイ了 カデ終 (sizeof(menu)/sizeof(ITEM)) 項目か多くなると メ さて , 例題 1 て、は選択する項目が 1 , 2 , 3 の三つだけて、した。けれどもプログラムは 改良を加えたり機能を拡張したりしている うちにだんだん大きくなってくるものて、す。 4. 現在時刻を表示する 5. 工デイタを起動する 6. ファイルを削除する とメニューの内容が増えていくとすると , List 1 や List 2 はどうなるて、しようか。これ はすぐ想像がっきます。メニューの選択部 分が増えると , List 1 て、は if ・・ elseif ・ の連鎖の数が増えていき ,List 2 て、は case の 数が増えていくことになります。つまりひ る」 , これがメニューてす。 C 言語て、それを どこまて、がひとつの if 文 , あるいは switch 文 とつの if 文や switch 文がだんだん巨大になっ 実現する場合 , 普通に考えたら List 1 のよう なのかがはっきりしなくなるからて、す。 てくるのて、す。これはあまりうれしくあり に if 文を使うか ,List 2 のように switch 文を ちょっと整理しましよう。「多くの選択肢 ません。プログラムを見たときにどこから 使うことになります oif 文の連鎖や switch 文 の中から指定されたものをひとつ実行す cmd-curdir(void) system("DIR /W") ; cmd-dir(void) pr intf ( ”ディレクトリ名を入力してください・ gets(buf) : sprintf(cmdl ine, "DIR /W XS", buf) ; system(cmdline) ; 118 C MAGAZINE 1 的 3 1
X 68 k 活用講座 ラスを継承したすべてのクラスて、オプジェ クトが生成されるたびに呼び出されるのて、 , 確認を忘れていると何度も static メンバの初 期化が行われてしまいます。継承クラスが 生成されるたびに呼び出されるのて、 , オー バーヘッドがありますが目をつむってしま x68ksprite クラスのコンストラクタて、はメ ンバの初期化と同時に ードウェアの初 期化も行っています。このために このク ラスを継承したオプジェクトが生成されれ ば必す画面モードの切り換えが行われます。 基本クラスの初期化が終われば , 基本ク ラスの各メンバ関数を順次呼び出してスプ ライトを生成します。 List 3 のコンストラク タをみていただけたらわかるように , 非常 にシンプルにすっきりとまとまっています。 渡された引数以外て、意識している変数は , x68kpcg クラスて、割り当てされる PCG の番 号だけて、す。これは x68ksprite クラスに通知 しないとならない番号なのて、 , これはおも てに現れます。変数 sp is ready, time は , main() { } だけて、動かすという目標のため だけに存在しているのて、 , 普通に使う場合 には必要のない処理て、す。 今回のクラスには , デフォルトの動きを 指定の動きにすげかえたり , 具体的に座標 を指定したりするメンバ関数がないのて、 , このままて、は実用性は乏しいて、しようが , これらの処理を追加するのは簡単てしよう。 クラス Test のオプジェクト root が生成さ れたら , 後の処理は割り込みが勝手にやっ てくれます。同様に順次 next0 , nextl と生 成されて main ( ) にエントリします。これは 何もしない関数て、すからすぐに復帰してき ます。 次にオプジェクトの消去が行われます。 クラス Test: : -Test は , 何もしないディス トラクタなのて、次に基本クラスの消去にな ります。 x68kpcg クラスはディストラクタを 持っていないのて ( これは明らかに手抜きて す ) , 残りは x68ksprite クラスてす。ここて List List 1 , 2 で見えないしかけ 1 : #include ” Cppsprite. h ” 2 : #include く interupt. h 〉 3 : 5 : 〃 C 言語ライブラリをつかうための処理 6 : 〃 7 : 8 : struct FILE; "C" void screen (int, int, int, int); 9 : extern "C" FI LE *fopen (char * , char * ) : 10 : extern ” C ” int fclose (FILE の : 11 : extern "C" int fseek (FILE 事 , int, int) : 12 : extern "C" int fread (void * , int, int, FILE の : 13 : extern "C ” volatile void abort ( ) ; 14 : extern ” C ” int VDISPST (void * , int, int); 15 : extern ” C" void SP_INIT (void) ; 16 : extern ” C" void SP_ON (void) ; 17 : extern ” C" void B_CUROFF (void); 18 : extern ” C ” void B_CURON (void) : 19 : extern 20 : 21 : x68kpcg: :x68kpcg (void) if ( ! inited_pcg) 23 : 24 : ョ 26 : 28 : 29 : 32 : 33 : VOid 34 : x68kpcg: :setfilename (char *P) 36 : int len = 0 : for (char *x = p; *x; x + + ) 十十 ; x = new char[len + 1 ] : 39 : 40 : data base_nane = x ; 42 : no 44 : 45 : 46 : VOid 47 : x68kpcg : : getpcgdata ( int id) 48 : ( fO 「 (int i 49 : = 0 : i く g ー 00 = ! i 十十 ) if ((pcg-ok[i] & 0x7fff) = id) 51 : 52 : 55 : 56 : 60 : 63 : / / データベースから PCG データをリードする 64 : / / if (!data—thse—nane) 65 : abort ( ) : 66 : FILE *fp = fo Ⅱ (data—base れ a , if (!fp) 68 : abort ( ) : fseek (fp, id * sizeof (PCG—REG). の : 71 : PCG_REG *buf = (PCG REG の new PCG_REG; if (fread (buf, 1 , sizeof (PCG-REG), (p) ! = sizeof (PCG-REG)) abort ( ) : fclose (fp); 75 : pcg—max 十 + : 76 : pcg—regs[i] = buf; 77 : no = 1 : 80 : x68ksprite: :x68ksprite (void) ″デフォルト移動は x が増える方向に 1 ドット 83 : static move defauItN)ve = { 1 , 0 } ; static MOVEDEF default mve—def = { 1 , 0 , &default—nove); 84 : 85 : 86 : if ( ! inited-sp) 〃割り当ては行われない / / 配列の初期化 for (int i 十十 ) = 0 : i く 256 : pcg-ok[i] inited—pcg ニ 1 : / / strlen ( ) と同じ / / 処理を行う 〃文字バッフアを確保 / / メンバにセット 〃 strcpy ( ) と同じ / / ダミーで初期化 / / 既に登録済みかを / / 調べる / / 登録されているなら 〃その番号を返す / / 空きをさがす。 / / 見付かったら / / ID を登録 no = 1 : return ; i 十十 ) fo 「 (i = 0 : i く 128 ; if (pcg-ok[i] = pcg-ok[i] = break : X68k 活用講座 141
List 1 いのて、 , これらの処理はすべてメンバ関数 にしておき , コンストラクタて、はフラグを 設けて static メンバの初期化とついて、に画面 の設定を行うようにしておきました。 目標に到達 List 1 が当面の目標て、ある main ( ) { } だ けて、スプライトを動かすことを実現するた めに作成した PCG クラスとスプライトスク ロールレジスタクラスて、す。 PCG クラスが class x68pcg, スクロールレジスタクラス が class x68sprite という名称て、作ってあり ます。このふたつの基本クラスは , 時間経 過を計る x68sprite : : vsync time ( ) を除 いてすべて private と protected メンノヾなの て、 , 直接この基本クラスを利用することは て、きません。実際にスプライトを表示させ るには , このふたつのクラスを継承したク ラスを設計して , それを利用します。 List 2 がこのふたつのクラスを継承させた Test クラスを持っメインルーチンて、す。本 当に main ( ) { } になっていますが , ディス クに収録されているプログラム ( MAIN. X ) は , 実際にこれをコンパイルしたものて、四 つのスプライトを動かして終了します。 タネを明かせば簡単て、 , C 十十の言語仕様 て、あるグローバルなオプジェクトは関数 ma ⅲ ( ) が呼ばれる前に初期化される , という シカケを利用して , 本来ならば main ( ) から 呼び出されて実行されるて、あろう処理を全 部そのオプジェクトにつめこんてあるだけ のことて、す。 今回掲載したプログラムを改造改良すれ ば , ゲームプログラムて、「このキャラクタを この動きのデータて、動かせ」という処理のク ラスをキャラクタ〃とて、もすれば キャラクタ f00 ( キャラ指定 , 動く方 法 ) ; とだけオプジェクトを生成しておけばそれ だけて、済む , 当たり判定や移動処理は全部 見えないシカケがやってくれる , というプ static short static SP_REG static short static short static MOVEDEF *sp_moves[256] : static short protected: static char *data base—name; 46 : class x68kpcg 44 : } MOVEDEF; move *moves ; short count ; Short size; 41 : 42 : 45 : 48 : 49 : 51 : 52 : 53 : 54 : 56 : 59 : 64 : 65 : 66 : 67 : 68 : 69 : 73 : 74 : 76 : 83 : 86 : 88 : 89 : 90 : 92 : 93 : 94 : static PCG_REG *pcg_regsC256] : static volatile short pcg ー ok [ 256 ] : static short pcg_max; PCG_REG *volati le pcg_req; short static req_no ; static short inited_pcg; no ; fr i end VO i d v_sync_funct ion (void) ; 60 : protected : x68kpcg (void) ; x68kpcg (PCG—REG * ) ; void setfilename (char * ) ; VOid getpcgdata (int) : void set—pcg (void) if ( ! (pcg-okCno] & 0X800 の ) while (pcg—req) pcg—req = pcg—regs Cno] ; req_no = no; int pcg_set—OK ( ) return pcg—0kCno] & 0X8000 ; 81 : class x68ksprite pro tected : sp—regs[256] ; sp—displayC256]; sp—assign[256]; sp—activeC256] : static int no 響 : static short inited_sp; static volatile short sp_is_ static volatile short vsync_ int get—x—pos (void) void assign_sp_reg (int pcg_ x68ksprite (void) : x68ksprite (void) : friend VOid v_sync function short sp—no; ready; counter ; ー 100 : ー 101 : ー 102 : ー 103 : ! 104 : 105 : 106 : ー 107 : ー 108 : ー 109 : ( return sp_regs[sp—no]. sp_x int get—y—pos (VOid) { return sp_regs[sp—no]. sp_y ・ VOid activate (VOid) { sp—active[this- 〉 sp—no] X 68 k 活用講座 X68k 活用講座 〃移動処理を行わせる / / 現在の Y 座標を得る関数 / / 現在の X 座標を得る関数 / / スクロールレジスタ割り当て / / 割り込み処理関数 / / 割り当て番号 / / 経過時間カウンタ / / 割り込み関数フラグ / / クラスイニシャライズフラグ / / 使用されているスプライトの数 / / 割り込みで動かすためのフラグ / / デフォルト動き定義へのポインタ / / ディスプレイ管理配列 〃ディスプレイフラグ 〃スプライトレジスタイメージ / / をテストする関数 / / ハードに登録されているか / / 割り込みに登録を依頼 / / PCG パッフアセット / / 残っていれば待つ / / 前の要求が残っていないか ? / / 登録ビットをチェック / / ハードに登録を要求する関数 / / 割り込み処理関数 / / 割り当てられた pcg no / / クラスイニシャライズフラグ / / 登録待ちの no / / ハード登録待ちの pcg / / 現在の PCG 登録 max 番号 / / 管理配列 / / PCG データイメージ 139
リ The + 抽象クラスの継承 List 純粋仮想関数の定義 ちょっと深入りしすぎかもしれませんが , 純粋仮想関数についてもうひとつだけ。 実は , 純粋仮想関数の実体を定義をする ことは可能なのです。 ただし , 見えない関数ポインタテープル には登録されませんから , 普通の仮想関数 のように呼び出すことはできません。 :foo() Abs . のようにクラスを指定して , ポインタを通 さない直接の呼び出し方法を取れば , コン ストラクタからでもなんとか呼び出すこと は可能です。 けれども , 純粋仮想関数の実体を定義す ることは文法上許されてはいますが , 実際 のプログラムでは減多に使われないでしよ う。 #include く iost 「 eam. h > 1 : / / abst. cpp 2 : 3 : class Abstract { / / 純粋仮想関数を含むので抽象クラス virtual void f00 ( ) 4 : virtual void ・ bar() 5 : 7 : 8 : class StillAbstract : public Abstract { / / f00 ( ) はオーパーライドしたが、 r ( ) はそのまま 9 : / / 純粋仮想関数として残るので抽象クラス void f00 ( ) ; 12 : } ; 14 : class NotAbstract : publ ic Stil lAbstract { / / 全ての純粋仮想関数をオーバーライドしたので、 15 : / / もはや抽象クラスではない 16 : void bar(); 17 : 抽象クラスのコンストラクタ LiSt 1 : / / abscon. cpp 2 : 3 : class Abstract { 4 : public: virtual void f00 ( ) Abstract() foo(); : public Abstract ( 12 : class NotAbstract 13 : public: vo id foo ( ) { } / / オーバーライド 14 : NotAbstract() { } 18 : void main() 19 : { NotAbstract not; 20 : / / 工ラー : インスタンスは作成途中 class Abs { public Abs ( ) Abs 、 :foo() : virtualvoid f00 ( ) = 0 ; Fig. 5 純粋仮想関数呼び出しのランタイムエラー : f00 ( ) void Abs cout くく "Abs . : fOO"< く endl, Pu 「 e ⅵ u 引 function called イトの文字によって , SortElem クラスが抽 ないということが直感的にわかるようにな 象クラスとして蘇ったのてす。 っているともいえます。純粋仮想関数は , いうまてもなく仮想関数なのて , 必ず "virt 抽象クラスの継承 純粋仮想関数の宣言を List 6 に示します。 ual" をつけます。 宣言方法は至って簡単て , 仮想関数の宣言 実際に SortEIem クラスに純粋仮想関数を の後ろ ( セミコロンの前 ) に 抽象クラスを継承するときには , すべて 0 」をつけ 使ってみたプログラムを List 7 に示します。 の純粋仮想関数をオーバライドする必要は るだけてす。これは , NULL ポインタのメ ご覧のように , List 2 との違いは , 7 行目の タフアとしてとらえることもてき , 実体が 「 = 0 」だけてす。てすが , このたったの 3 バ ありません。純粋仮想関数は , 純粋仮想関 純粋仮想関数 C + + 入門講座 109
ニ - - ロ 学 五ロ 一三ロ Macintosh のサンプルプログラム ( samp 厄 1. c) List 1 List 1 91 : void handleEvent(Boolean wne) 92 : { 93 : char c; 94 : if (wne) { 95 : Wa i tNex tEvent (everyEvent , &TheEvent , OL, NULL) : 96 : } else { SystemTask() ; 98 : GetNextEvent (everyEvent, &TheEvent) ; 99 : 100 : 101 : 102 : 103 : 104 : 105 : 106 : 107 : 108 : 109 : 110 : 111 : 112 : 113 : 114 : 115 : 116 : Boolean InitMac() 117 / * initialize * / 118 : ln i tGraf (&thePort) ; 119 : InitFonts() ; 120 : FlushEvents(everyEvent, の ; 121 : InitWindows() ; 122 : InitMenus() ; 123 : TEInit(); 124 : InitDialogs (NULL) ; 125 : InitCursor() ; 126 : 127 : return NGetTrapAddress(WNE_TRAP, ToolTrap) ! = 128 : NGetTrapAddress(UNIMPL_TRAP, T001Trap) ; 129 : 130 : } 131 : 132 : 133 : void setupMenu() 134 135 : Handle menubar; 136 : menubar ニ GetNewMBar(MenuBarID) ; 137 : SetMenuBar ( menubar ) ; 138 : AppleMenuH = GetMHandIe(AppIeMenuID) : 139 : AddResMenu(AppleMenuH, ' DRVR' ) ; 140 : FiIeMenuH = GetMHandle()i leMenuID) ; 141 : DrawMenuBar() ; 142 : 143 : ) 144 : 145 : 146 : void main() 147 : 148 : B001 ean wne : 149 : wne = InitMac(); 150 : setupMenu() : 151 : 152 : 153 : Done = false; while (!Done) 154 : hand leEvent (wne) ; 155 : 156 2 : 4 : 5 : #def i ne MenuBar I D 128 6 : 7 : #define AppIeMenuID 128 8 : #define AboutItemID 1 9 : #define AboutAIertID 128 10 : 11 : #define FileMenuID 129 12 : #define QuitItemID 1 13 : 14 : 15 : #define WNE_TRAP 0X60 16 : #define UNIMPL_TRAP Ox9F 18 : 19 : Boolean Done : 20 : MenuHandIe AppIeMenuH; 21 : MenuHandle FileMenuH; 22 : EventRecord TheEvent ; 23 : 24 : 25 : void hu1dleAppleCh0ice(short i tem) Str255 accName; 27 : 28 : i nt accNum : 29 : switch (item) { 30 : 31 : case AboutI tem I D : NULL) ; 32 : 33 : break ; default: 34 : Get I tem (AppIeMenuH, i tem, accName) : 35 : accNum = 0penDeskAcc(accName) ; 36 : 37 : break ; 38 : 39 : ) 40 : 41 : 42 : void handleFileChoice(short item) 43 : { switch (item) { 44 : 45 : case QuitItemID: 46 : Done = true ; break : 48 : 49 : } 50 : 51 : 52 : void handIeMenuCh0ice(long choice) 53 : { 54 : int menu, item; 55 : if (choice ! = の 56 : menu = H iWord cho ice item = LOWord(ch0ice 58 : switch (menu) { 59 : 60 : case AppleMenuID: handleAppIeCh0ice(item) ; break ; 62 : case FileMenuID: 63 : handIeFiIeChoice(item) : 64 : break ; 65 : 66 : 68 : 69 : } 70 : 71 : 72 : void handIeMouseDown() 73 : { 74 : WindowPtr 町 75 : short part : long choice; 77 : part = FindWindow(TheEvent. where, switch (part) { case i nMenuBar : 80 : choice = MenuSelect(TheEvent. where) ; handIeMenuCh0ice(ch0ice) : 82 : 83 : break ; case i nSysW i ndow : 84 : SystemCIick(&TheEvent, の : 86 : break ; 88 : ) 89 : 90 : samplel. c switch (TheEvent. what) { case mouseDown: hand 1 eMous eDown ( ) ; break ; case keyDown : case autoKey : c = TheEvent. message & charCodeMask; if (TheEvent. modifiers & cmdKey) hand 1 eMenuCho i ce (MenuKey (c) ) ; break ; HiIiteMenu( の : 使用するリソースの記述 ( samp 厄 1 . 「 ) 1 : resource ' MENU' ( 128 ) { 2 : 128 , 3 : textMenuProc, 4 : 0x7FFFFFFD, 5 : enabled, 6 : apple, { / * array: 2 elements * / 7 : 8 : ” about s plel. noKey, noMark, plain, 9 : nol con, no lcon, noKey, noMark, plain 11 : 12 : 13 : } ; C 言語雑学講座 135
ワイント プログラミング List 1 167 : 168 : 169 : 170 : 171 : 172 : 173 : 174 : 175 : 176 : 177 : 178 : 179 : 180 : 181 : 182 : 183 : 184 : 185 : 186 : 187 : 188 : 189 : 190 : 191 : } 192 : 194 : ( 195 : 196 : 197 : 198 : 199 : 200 : 201 WO = (long)work / 0X10000 + ((long)work & 0xFFFF) / 16 + 1 : 239 : ) (long)ucw / 0X10000 : mask; for (i = tate; i > 0 ; i--) { continue; if ((byte for (i = yoko; i > 0 : i--) { / * if (k & ptnmask) { dotptn [n] ト mask 〉 > = 1 ; if (mask mask = 0X80 ; / 事 横方向 y 。 k 。倍 ドットがある場合、 パターンに登録 マスクをずらす マスクがアンダーフロー マスクを初期化 配列をインクリメント 2 パイト文字の場合、 3 2 ビット * / になるまで 1 行分の編集継続 縦方向 / * マスクの初期化 250 : 251 : 252 : 1 s->yl = yl; s->x2 = x2; s->y2 = y2 : s->cl = cl; s->c2 = c2; LiSt 253 : 254 : 255 : 256 : } 257 : 258 : 259 : 260 : { 261 : 262 : 263 : 264 : 265 : 266 : 267 : 268 : 269 : 270 : 271 : 272 : 273 : 274 : 275 : 276 : } 277 : 278 : 279 : ( 280 : 281 : 282 : 1i0 ( 0xA2 ) : / * 左上の Y 座標 / * 右下の X 座標 / * 右下の Y 座標 / * 領域色 / * 境界色 / * L 1 0 割り込み り り り void line(int xl, int yl, int x2. int Y2' char C01 , char mode, char sw, char C012 ) struct { s = PARAM; s->xl = xl; s->yl = yl; s->x2 = x2; s->y2 ニ y2; s->col= C01 : int xl, char C01 , yl, x2, mode, Y2 ; sw, C012 : put(), y 十 + , dotptn, 8 * byte * YOkO, col) ; mask = 0X80 : for (i = 0 ; i く 10 ; i + + ) { dotptn [ i ] return ( の : 193 : void lio(int liocmd) / * 1 行分横方向拡大パッフアの初期化 * / / * 正常終了 / * 作業領城のセグメント値 / * パラメータ領域アト・レスのわセ外 / * グラフィック L 1 0 割り込み s->mode = mode; s—>sw s ー > C012 = C012 ; 1i0 ( 0xA7 ) : s truct SREGS union REGS regs. x. bx = 0 : segs. ds = WORK : segs; regs : void put(int x, int y, char *Ptn, int uns igned uc 矼 400 ] : s truct SREGS union REGS segs; regs ; int86x(Iiocmd, ®s, ®s, &segs) : / * 202 : 204 ・ 205 : 206 : 207 : 208 : 209 : 210 : 211 : 212 : 213 : 214 : 215 : 216 : 217 : 218 : 219 : 220 : 221 : 222 : 223 : 224 : 225 226 : 227 : 228 : { 229 : 230 : 231 : 232 : 233 : 234 : 235 : 236 : 237 : 238 : 240 : 242. 243 : 244 : 245 : 246 : 247 : 248 : 249 : 203 : void ginit(void) PARAM = WORK 事 0X10000L : int n, i; int *S, *vect; stat ic char intC5 = 0xCF ; (i * S 十十 ; = (unsigned * ) 0Xf9900000L ; for S 十十 : S / 事 / * 0xCF ニ IRET / 事 / * ク・ラフィックスクリーンの消去 / * 設定する。 / * 割り込みペクタテーブルに / 事描画処理中断用割り込みペクタを 転記する。 / * 割り込みペクタテーブルに / * R 0 M より各 L 1 0 コマンドの / 事パラメータ領域の先頭アドレス / 本 / 事 283 : 284 : 285 : 286 : 310 : 309 : 308 : 307 : 306 : 305 : 304 : 303 : 302 : 301 : { 299 : 298 : } 297 : 296 : 295 : 294 : 293 : 292 : 291 : 290 : 289 : 288 : 287 : regs. h. ah = 0X45 : regs. h. ch = 0xb0; regs. x. bx = (long)ucw & OXPPPF; segs. es ucw[7] = ucw[6] = ucw[5] ucw[4J ucw[l] ucwC0] segs. ds = (long)ptn / 0X10000 ; (long)ptn & 0xFFFP; len; = C01 ; int86x(0x18, ®s, ®s, &segs) ; vect = (unsigned の ( ( ” + + ) 事 4L ) : / * オフセット値を読みだし、 十十 = * S 十十 ; *vect + + = 0XF990 ; 300 : void roll(int n) vect ニの ( 0xc5 事 4 ) ; *vect + + = (long) ( & intC5 ) / 0X10000 : = (long)(&intC5)&0xFPFF; •vect 十十 1i0 ( 0xa の : 1i0 ( 0xA5 ) ; void screen(int 重 Od , int int int disp) struct { int X, y; char clr; s = PARAM ・ Ii0(Oxae) ; struct { char pl, p2, P3, P4; s = PARAM s->pl = 重 : s->p2 = sw; s->p3 = act ; s->p4 = disp; 1i0 ( 0xA1 ) : / 事 / 本 / * L 1 0 割り込み / 事ディスプレイ画面 / 事アクティブ画面 / * 画面スイッチ / * 画面モード り り り り 事 / り り り り り り り り 311 : 312 313 : 314 : 315 : { 316 : 317 : 318 : 319 : 320 : 321 322 : 324 : ( 325 : 326 : } 327 : 329 : { 330 : 331 332 : void clrline(int ro 響 , int lines) int yl, y2; line(), yl, 639 , Y2, 0 , 2 , 1 , の : y2 = yl + lines 事 16 yl = 「 0 響 * 16 ; 323 : void 「 011 叩 ( int 「 0 物 s ) 241 : void view(int xl, int yl, int x2, int y2, char cl, char c2) r011 ( 16 本 rows); struct { s->xl = xl; s = PARAM ・ char cl, int xl, yl, x2, y2; c2 : / * 描画領城の設定 / * 左上の X 座標 / 事 / 事 328 : void rolldovn(int rovs) roll ( -16 本「 0 響 s ) : / * 左上の X 座標 / * 左上の Y 座標 / * 右下の X 座標 / * 右下の Y 座標 / * 線の指定色 / * 描面モード 0 : 線 1 : 矩形 2 : 矩形塗 / * スイッチ / * 塗りつぶし色 / * L 1 0 割り込み / * Unit ControI Work : 800 ハ・イト int COI) / * 描画長 / * 描画位置 : X 座標 / * 表示色 / * 10110000 セグメント * / / * 描画パターンパッフアのオフセット * / Y 座標 / * B 1 0 S コール / 事 / * L 1 0 割り込み / 事 / 本 / * 矩形表示による行消去 / * ロールアップ / 事 / 本 ロールダウン / 本 ワンポイントプログラミング講座 147
番号を入力してもらい (gets), その番号を数 1. カレントディレクトリを表示する ーを使ってタビュレイトしたものがテープ 値に直し ( at 。 i ) , if 文てその番号に応じて処 2. 指定ディレクトリを表示する ルて、ある」ということになります。ふむふ 理を分けています。ふむふむ。 3. 終了する む。 の三つの項目を表示して , キーポード入力 食卓としてのテープルにはミルクやパン sw h 文を使ってみる を待ち , 入力された番号に応じた処理を行 がきちんとそろえられています。こうして うプログラムを作りなさい。 おけば , 食卓についた人は何がどこにある ごく簡 例題 1 の答えとしては List 1 て、文句ありま のか一目て、わかりますね。表としてのテー このような番号選択メニューは , プルには数字や名前がきちんとそろえられ 単なアプリケーションやツール類を作ると せんが , これを少し手直ししてみることに します。まず手始めに , switch 文を使って きによく使います。このくらいて、したらバ ています。こうしておけば , 表を見た人は ッチファイルて、作れるかもしれませんが , みましよう。 List 1 に登場した if 文の列 , 何がどうなっているか一目て、わかりますね。 これ以上イメージを広げるのは皆さんに こては C て、作ってみましよう。思いつくま if (choice おまかせして , プログラムを作りましよう。 ま書いたのが List 1 て、す。 MS-DOS て、動作 させることを想定しています。 いつものように簡単な例題から始めます。 ディレクトリを表示するところは直接 CO 番号選ニューを作ろう MMAND.COM を呼び出してくれる便利な 関数 system ( ) を使っています。あとは , わ 例題 1 画面に , かりますね。メニューを表示して (printf) , else switch 文を使った番号選択メニュー else if (choice else if (choice if 文を使った番号選択メニュー List Li st : 1 : #include く stdio. h> 2 : #include く stdlib. h> 3 : #include く process. h> 4 : 5 : void main(void) : 6 : 7 : void main(void) 8 : char buf[BUFSIZ], cmdlineCBUFSIZ] : 9 : ー 10 : int choice; 11 : int exit—flag = 0 ; ー 12 : while (!exit—flag) { printf( ” ー 14 : printf ( ” 1. カレントディレクトリを表示する \ n ” ) ; p 「 intf("2. 指定ディレクトリを表示する \ n ” ) ; printf( ” 3. 終了する \ n ” ) : printf("= printf ( ”番号を入力してください・ gets(buf) ; 20 : choice = at0i (buf) ; sw i tch (ch0 i (e) { ー 22 : case 1 : 23 : system( ” DIR /W ” ) ; break : ー 25 : case 2 : ー 26 : printf( ”ディレクトリ名を入力してください : gets(buf) : ー 28 : sprintf(cmdl ine, ” DIR /W Xs ” , buf) ; ー 29 : system(cmdline) : ー 30 : break : ー 32 : case 3 : printf( ”終了します。 *n ” ): ー 33 : ー 34 : exit—flag = 1 ; ー 35 : break : ー 36 : default: printf( ”入力が誤っています。 *n ” ): ー 38 : break ; ー 39 : ー 40 : 3 1 : #include く stdio. h> 2 : #include く stdlib. h 〉 3 : #include く process. h 〉 ー 5 : void main(void); 6 : ー 7 : void main(void) bufCBUFSIZ], cmdlineCBUFSIZ] ; 9 : Char 1 10 : int choice; 1 11 : int exit—flag = 0 ; 12 : while (!exit—flag) ( printf( ” 1 14 : printf ( " 1. カレントディレクトリを表示する \ n " ) ; printf("2. 指定ディレクトリを表示する \ n " ) ; printf( ” 3. 終了する \ n ” ) ; printf( ”ー : 19 : printf ( ”番号を入力してください・ gets(buf) : 1 20 : choice = atoi (buf) ; if (choice = ー 22 : system( ” DIR /W ” ): ー 23 : ー 24 : else if (choice = 25 : printf(" ディレクトリ名を入力してください・ 26 : gets(buf) : sprintf(cmdline, ” DIR /W Xs ” , buf) ; 28 : system(cmdline) : 29 : else if (choice = printf(" 終了します。 *n"); ー 32 : ー 33 : exit—flag = 1 : : 34 : else { ー 35 : printf ( " 入力が誤っています。 }n"); ー 36 : ー 38 : ー 39 : プログラミングの工ッセンス 117
てあるため , それほどてもないのだが , 2 重 ループなどの複雑なループの中て、のムダな 操作は , 指数関数的に実行効率の低下につ ながってしまうのだ ! 【重要】冗長な操作を避けよ ! とくにループの中て、の冗長な操作は避け よ List 1 を修正したプログラム LiSt 1 : #include 2 : #include 3 : 4 : #define Q_NO 5 : 6 : FILE *fp; 7 : ー初期処理ー 9 : int initialize(void) 10 : { fp = fopen( ” DATA ” , 11 : return ( (fp 12 : ー終了処理 15 : / * - 16 : void ending(void) 17 : ( fclose(fp) ; 18 : 19 : } 20 : ーメイン関数ーーーーー * / 第 22 : int main(void) 1 23 : ( if (initialize()) { 24 : q, ch; int ans[20 26 : Char qus [ 20 char 28 : for (q = 0 : q く Q—NO; q + + ) { 第 30 : int ch = getc(fp); ョ 32 : if (ch = EOF) goto ending; for (i = 0 ; !(isspace(ch)); i + + ) { ansCi] 36 : ニ ch; ch ニ fgetc(fp) ; 38 : ansCi] ch = fgetc (fp) : 41 : for (i ニ 0 ; !(isspace(ch)); i + + ) { 朝 42 : qusCi] 43 : ニ ch; ch = fgetc(fp) : 44 : 45 : 0 qusCi] 46 : printf(" 問題 =Xs 解答 = % s \ n ” , ans, qus) : 48 : ヨ 49 : ヨ 50 : 52 : 53 : } く ctype. h 〉 く stdio. h> 3 / * 問題の個数 * / = NULL) ? ' 0 : さて , 西野さんがほかにも試みオ方法が , なぜ正しくなた。は , 読者 0 霧さんが 考えていただきたい く間題 > 文字列のクリアを意図する (a) strcat (ans, NULL) ; (b) strcat (ans, ' \ 0 ' ) ; は , 実際には何を意味するのて、あろうか メモリのクリ ちなみに , 何らかの理由て , すべての文 字を ' \ 0 ' とする必要があるのならば , 以下 のような方法がよいて、あろう。 #include く string. h > str [ 4 ] ; char memset (str, ' \ 0 ' , sizeof(str) ) ・ 念のため説明しておくが , memset は , 第 1 引数の指すポインタから , 第 3 引数のバイ ト数だけ第 2 引数の文字て、埋めつくす関数て、 ある。 ending: ending(); return ( の ; 整数配列を確保・解放するプログラム 第 1 : #include 2 : #include 4 : #define SIZE 6 : int main(void) 8 : int 9 : p = calloc(SIZE, ・ sizeof(int)); / * 確保 * / = NULL) if (p 11 : puts ( " メモリの確保に失敗しました " ) ; 12 : else { - の物 13 : 物 14 : int for (i = 0 ; i く SIZE; i + + ) pCiJ = 16 : for (i ニ 0 ; i く SIZE; i + + ) 17 : printf("%4d", pCi]) ; 3 18 : putchar(' }n' ) : 20 : free(p) ; return ( の ; 22 : 23 : } LLSt く問題 > 以下の関数は正しいのだろう void clear string (char * str) memset (str, ' \ 0 ' , sizeof (str) ) く stdio. h> く stdlib. h 〉 10 / * 確保する配列の大きさ * / ~ 酒列の動的確保 質問の内容 さて , 次はメモリの動的な確保に関する 一 - ~ いを」い一いを ~ いをい 1 ー - ー / * 解放 * / 128 C MAGAZINE 1993 1
List 1 List KANJl.c 84 : int code, unsigned char PtnC]) 85 : int getptn(uns igned / * 文字のドットパターンの取得 s truct SREGS segs; un i on REGS 88 : regs ; 89 : / * ファンクションコード設定 regs. h. ah = 0X14 ; 90 : / * 漢字を J I S コードで設定 regs. X. dx = code : 91 : regs. x. bx = FP—SEG (ptn) ; / * 格納工リアのセク・メント 92 : regs ・ x. cx = FP—OPF (ptn) ; / * 格納工リアのわセット 93 : return(int86x(0x18, ®s, ®s, &segs)) : 94 : / * CRT BIOS コール ( 0X18 ) 96 : 97 : int gprint(int ro 町 int C01 , char *Str, int tate, int YOkO, int c) ″文字列のグラフィック表示 99 : lnt x, y, len; 100 : if (row く 0 Ⅱ ro 響 > 24 ) 101 : / * パラメータェラー return ( の ; 102 : : 何もせずに戻る if (col く 0 Ⅱ C01 > 79 ) / * パラメータ工ラー 103 : return( の : 104 : : 何もせずに戻る 105 : len = strlen(str) ; / * 文字列長の取得 106 : / * 列より水平座標を計算 107 : x = C01 * 8 : / * 行より垂直座標を計算 y = ro 響 * 16 ; 108 : 109 : len ニ gpr intxy (), y, s tr, tate, YOkO, c) ; / * 文字列表示 110 : return ( len) ; / * 表示成功文字数を戻す 111 : 112 : } 113 : int gprintxy(int X, int y, char *Str, int tate, int YOkO, int C01 ) 114 : 115. 116 : uns igned code, byte, len, rtn ; 117 : Char *S : 118 : / * 文字列の先頭にポインタを合わせる * / 119 : str; S 120 : if (iskanji(*s)) { / * 漢字 ( 2 バイト文字 ) のチェック 121 : : 2 バイト文字の場合 122 : byte = 2 ; code = ( * s + + ) くく 8 : シフト J I S → J I S 変換 123 : 124 : COde 十 = * S 十十 : code = sjtoj(code) ; 125 : 126 : else { 127 : : 1 バイト文字の場合 128 : byte ニ 1 : code = 0X8000 + ( * s + + ) : 規約により 0X8000 を加える 129 : 事 / 130 : yoko, col); / * 漢字表示 rtn = kanjiprint(), y, code, 131 : tate, if (rtn ! = の break; / * 表示失敗時は終了 132 : / * 水平座標を再計算 x + = 8 * byte 事 yoko; 133 : 134 : return( len) ; / * 表示成功文字数を戻す 135 : 136 : } 137 : int kanjiprint(int x, int y, unsigned code, int tate, int yoko, int CO 1 ) 138 : 139 : ( uns igned i nt 140 : ptnmask, mask, 141 : byte, len; 142 : dotptn[10]; / * 1 列分の横方向拡大ドットバッファ * / char 143 : struct fontpattern { / * 文字パターン格納用構造体 144 : unsigned char c[2] ; 145 : unsigned char fontptnC32J : 146 : } font ; 147 : 148 : if (code 〉 = 0X800 の byte = 1 ; 1 バイトコード , 149 : / * 2 バイトコードの判別 150 : el byte = 2 ; / * 格納バッファ font の長さ len = 16 * byte; 151 : / 事 152 : / * 横倍角は、 5 倍まで。 if (yoko 〉 5 ) return(-l) : 153 : if (x + 8 事 byte * yoko > = 64 の / 本表示しきれないばあいは、 154 : 何もせずに戻る。 return ( ー 1 ) ; 155 : if (y + 16 事 tate > = 40 の / 事表示しきれないばあいは、 156 : 何もせずに戻る。 return(-l) ; 157 : 158 : getptn(code, (unsigned char *)&font) : / * 文字パターンの取得 159 : 160 : / * マスクの初期化 mask = 0X80 : 161 : / * 1 行分横方向拡大バッフアの初期化 * / 162 : for (i=0; i く 10 : i + + ) dotptn[i] 163 : for (j = 0 : j く len; j + + ) { / * 格納パッフアの長さ分繰り返し 164 : k = font. fontptn[j]; ドットパターン設定 165 : for (ptnmask = 0X80 ; ptnnask > 0 : ptnmask > > = 1 ) ( 166 : 1 : 6 : 7 : #include く stdio. h> 8 : #include く dos. h> り 9 : #define YES 1 10 : #define NO 0 り iskanji(c) ( ( 0X80 く ( c ) & & ( c ) く 0xA のⅡ (0xE0 ← (c)&&(c) く =0xFC)) 11 : #define 12 : sjtoj(int (j) ; int / * シフト J I S → J I S 変換 13 : getptn (uns igned int code, uns igned char *Ptn) : 14 : int gprint(int ro 響 , int COI, char *str, int tate, int yoko, int c) ; 15 : int gprintxy(int x, int y, char *str, int tate, int yoko, int C01 ) ; 16 : int kanjiprint(int x, int y, unsigned code, int tate, int yoko, int col) ; 17 : int lio(int liocmd) : / * L 1 0 割り込み 18 : VOid ginit(void) : 19 : VOid / * い 0- 開 D = AO 初期化 screen(int 田 , int sw, int act, int disp) ; 20 : VOid / * い 0 ー開 D ニ AI 画面モート・ view(int, int, int, int, char, char) ; 21 : VOid / * L10_CMD=A2 描画範囲 line(int, int, int, int, char, char, char,char) : / * L10_CMD=A7 直線 22 : VOid put(int x, int y, char *ptn, int len, int col) ; / * LIO_CMD=AC パターン 23 : VOid roll(int); 24 : VOid / * LIO_CMD=AE ロール clrline(int ro 響 , int lines); / * グラフィックの行消去 25 : VOid り rollup(int rows); / * ロールアップ 26 : VOid rolldown(int rows) : 27 : VOid / * ロールダウン 28 : 29 : char 響 ork [ 0X140 の ; / * グラフィック L 1 0 用ワークエリア * / / * LIO り一クエリアの ^ 。ラク・ラフ境界のセク・メント 30 : int WORK : / * WORK の先頭位置のアドレス 31 : VOid *PARAM ・ 32 : 33 : void main(void) 34 : { 35 : illt C, n; char dispC200] : 36 : printf("*x1b[2J ” ) ; 38 : / * テキストスクトンの消去 ginit(); 39 : / * LIO 初期化 screen(), 0 , 0 , 1 ) : 40 : / * スクリーンをト・初期化 ⅵ e 響 ( 0 , 0 , 639 , 399 , 0 , の ; / * 描画領域設定 41 : 42 : strcpy(disp, ”あいうえお " ) ; / * 表示文字列初期化 43 : gprint( 1 , 1 , 1 ) ; 44 : disp, 1 , / * 縦横 1 倍 0 , gpr int ( 2 , 2 , 2 ) ; 1 , / * 横 2 倍 45 : 0 , disp, gprint( 4 , 1 , 3 ) ; 46 : 0 , 2 , / * 繼 2 倍 disp, gprint( 6 , 2 , 4 ) ; 47 : / 事縦横 2 倍 0 , disp, 2 , gprint( 9 , 3 , 5 ) : 0 , 3 , / * 縦横 3 倍 disp, gprint(14, 4 , 6 ) ; / * 縦横 4 倍 0 , disp, 4 , 50 : 51 : getch(); clrline(l, 52 : 1 ) : / * 第 1 行目を 1 行分消去 rollup ( 1 ) ; 53 : / * ロールアップ 54 : 55 : getch() ; r011d0 響 n ( 1 ) ; / * ロールダウン 56 : gprint(), 0 , disp, 1 , 7 ) ; / * 1 行表示 1 , 58 : 59 : getch() ; 1i0 ( 0xA5 ) ; 60 : / * ク・ラフィックスクリーンの消去 printf( ” *xlb* ” ) ; / * テキストスクトンの消去 63 : 64 : int sjtoj(int sj) / * シフト J I S → J I S コード変換 * / / * 上位 , 下位バイト 66 : int jh, jl; り jh = ()j > > 8 ) & 0xFF; 68 : り 69 : = sj & OxFF; り 70 : if ()h > = 0xE の jh ー = 0X40 ; jh = ()h ー 0X81 ) * 2 + 0X21 : 72 : 73 : if ()l 〉 0x7f) jl- 74 : if ()l 〉 0x9D) ( 75 : 76 : jh + 十・ こ 0x9E 77 : ー 0X21 : 80 : / * シフト J I S コードを戻す 82 : else { = 0X40 ー 0X21 : return((jh くく 8 ) + jl); C MAGAZINE 146 1993 1