nptr - みる会図書館


検索対象: 月刊 C MAGAZINE 1993年5月号
11件見つかりました。

1. 月刊 C MAGAZINE 1993年5月号

技法 ム グ ロ 集プ 特 O [ 開始 ] 指数文字 ( E または e ) の前には仮数部 , ある 動小数点数の定義を行ってみる。なお , いは数字の並びがきていなければならない こて、定義した構文は ANSIC ライプラリの a したがって , 3 , 4 番目も誤りてある。最後 tof などが許す構文に制限を加えたものて、あ に指数文字の後には ( 省略可能な符号に引き る。 C がソースの中に記すことを許す「浮動 続いて ) 数字の並びがなければならない。だ 定数 (floating-constant) 」の構文とは , サフ から 5 番目も間違いて、ある。 ィックスを許さない一方て、符号を許す点な こまて、考えると , そろそろプロ どが違っているのて、注意。 さて , グラムを作れる人も出てくるかもしれない このように定義することて、 , 初めて誤解 どのような処理を思いつかれるて、あろうか。 の余地なく設計を行うことがて、きる。たと 以前の check int の構造をベースに改造を行 えば , 次のそれぞれが「正しい」のかどうか うという方針て設計を行うと , てきあがる は , このような定義に則して考えると明白 のは List 2 のようなプログラムかもしれな List 2 においては , 関数の大筋において . e10 check int と同じ構造になっている。すなわ e10 ち , 構造を疑似コードて、大ざっぱに表せば , 十 e10 次のようになるだろう。 1 . 2e [ 準備 ] これらはすべて「正しくない」 ちなみに ・数字読み込みカウントを 0 にする。 のて、ある。理由は , 次のとおりて、ある。小 ・小数点はまだ読み込んて、いないものとす 数点の前後の少なくとも一方には必ず数字 の並びがきていなければならない。したが ・指数部の読み込み中て、はないものとする。 って , 1 番目と 2 番目は間違っている。また , check doub 厄の実装 ( 第 2 案 ) ・文字列の先頭の空白類文字を読み飛ばす。 ・先頭に符号があれば , 読み飛ばす。 ・着目文字が , 数字 , 小数点 , 指数文字て、 あるあいだ , 以下の処理を行う。 数字て、あれば , 数字読み込みカウント を増やす。 小数点て、あれば , すて、に小数点を読み込んて、いれば 指数部読み込み中て、あれば工ラー さもなけば , 小数点を読み込んだ ことを記憶する。 指数文字て、あれば , すて、に指数部読み込み中て、あれば 数字読み込みカウントが 0 ならば工 フ 次の文字が符号て、あればそれを読 み飛ばす。 数字読み込みカウントを 0 にする。 指数部読み込み中て、あることを記 0 になる。 3 LiSt LiSt if ((nptr = check-digits(nptr)) return 0 : if (* 叩 tr = 十十 nptr; vhile (isdigit(*nptr)) 十十 nptr; 39 : 40 : 49 : 53 : 55 : 56 : 57 : 58 : 60 : 66 : 67 : $ifdef TEST 68 : i n t 69 : main(int argc, char **argv) if (argc く 2 ) return 0 ; ⅶⅱ e (* + + argv ! = NULL printf( ” XS = XdYn ” return 0 : 77 : $endif ニ NULL) 1 : $include く ctype. h> 2 : $include く stdio. h> 3 : * 1 個以上の数字を読み飛ばす。 5 : 6 : * 読み飛ばしに成功すれば、次の文字へのポインタを返す。 7 : * 失敗したら N 乢しを返す。 9 : static const char * 10 : check-digits(const char *nptr) if (!isdigit(*nptr)) return N 乢い do { 十 + nptr; } vhile (isdigit(* 叩 tr)); return nptr; 20 : / * 21 : * 浮動小数点数文字列のチェックを行う 23 : i n t 24 : check-double(const char *nptr) / * 空白があれば、それを読み飛ばす * / 26 : vhile (isspace(*nptr)) 27 : 十十 nptr; 29 : / * 符号があれば読み飛ばす * / 30 : if (*nptr = Ⅱ *nptr : 32 : 十十 nptr; 33 : / * 仮数部をチェックする * / 34 : if (*nptr ニ 35 : if ((nptr : check-digits( + + nptr)) 36 : 37 : return 0 : } else { 38 : / * 指数部があれば、それをチェックする * / i f (*nptr = 十十 nptr; if (*nptr : 十十 nptr, if ((nptr = check_digits(nptr)) = NULL) return 0 : / * 空白があれば、それを読み飛ばす * / vhile (isspace(*nptr)) 十十 nptr; / * 文字列の終わりに達したかチェックする * / if (*nptr = return 1 : return 0 : 'e' Ⅱ *nptr = Ⅱ *nptr = / * 最低 1 つは数字があるはず * / check-double(*argv) ) : *argv, : NULL) 特集 C プログラム設計技法 49

2. 月刊 C MAGAZINE 1993年5月号

浮動小数のチェック : 最初の試み e' Ⅱ 1 くんだ ? 」という疑問にとらわれるのてはな 抵の人が「浮動小数点数って , どうやって書 そもそも , この問題を考え始めると , 大 ないかと思う。 めいた方も , 今回は少し苦労されるのて、は 整数の場合てあれば反射的に解答がひら ローは考慮しない。 すものとする。また , 数値的なオーパフ れば 1 を返し , それ以外の場合には 0 を返 い浮動小数点数文字列をポイントしてい ンタを受け取る。もし , ポインタが正し check double は , 引数に文字へのポイ int check double (const char * nptr) [ 例題 2 ] check doub 厄関数を作成せよ。 だろうか。すなわち例題 2 を考えてみてほし 浮動小数点定数に拡張してみたらどうなる 定義はこれてよいとしよう。ては , これを さて , 整数に関する構文チェックの関数 いだろうか。 そうなのてある。整数の場合には比較的 List check doub 厄の実装 ( 第 1 案 ) 単純な構文てあるのだが , 浮動小数点数と いうのは意外に複雑な構文になっているの てある。つまり , 一見すると何てもない例 題なのだが , きちんと「要求定義」を行わず には開発に着手てきないのだ。たとえば , check double には次のような文字列が渡さ れることがあるだろう。 123 1 .234 1 .23 2 これを見ると , ・まず整数がある。 ・次に ( 小数点と , もうひとつの整数 ) がく ることがある。 ・最後に指数部がつくこともある。 というような大まかな構造を読み取ること がてきる。だが , これてコーディングを開 始するのはまだ早計というものてある。 ch eck double には次のような入力がくること だってあるからだ。 .234 1e10 これらはどれも , C 言語のソ 123. ースレ / ヾノレて、 は浮動小数点数として正しいものてあると 解釈される。てあるから , C の文化を尊重す る場合には , データとして入力したときに も , これらを正しいものとして取り扱うべ きてあろう。少なくとも C プログラマがデー タを入力したときにこのような入力を行う 可能性は高く , 許しておくほうが融通性が 高くなる。 しかし , 次のものは , check double の入 力としてはエラーを含んて、いることを指摘 しておこう。 前者ては小数点が二度登場するし , 後者 て、は小数点が指数部の中て、登場する。いず れも正しくないことは明白てある。さて , このようにデータの構文 (Syntax) が複雑な 場合には , もはや 10 進整数の場合のように 自然言語ての記述には無理がある。誰にも 誤解なくきちんと定義するためには , 形式 的定義というものを用いるのがよい 0 ゆるメタ記法というものがそれに当たる。 こて、は ANSIC が採用しているメタ記法を べースにしたものを用いて , 処理すべき浮 1.2.3 1e1.2 1 : 2 : 4 : 6 : 9 : 20 : 22 : 23 : 24 : 25 : 30 : 32 : 33 : 7 : check-double(const char * 叩 t 「 ) int * 浮動小数点数文字列のチェックを行う $include く ctype. h> Ⅱ * 叩 t 「 = 'e' Ⅱ *nptr ー vhile (isdigit(*nptr) Ⅱ *nptr = / * 数字、小数点、もしくは指数文字であるあいだ読み込む * / 十十 nptr; Ⅱ *nptr = if (*nptr = / * 符号があれば読み飛ばす * / 十十 nptr; vhile (isspace(*nptr)) / * 空白があれば、それを読み飛ばす * / int digits = 0 : / * 数字を読み込んだ回数 * / int exp = 0 : / * 指数部の読み込み中か ? * / int point = 0 : / * 小数点を読み込んだか ? * / if (isdigit(*nptr)) List 2 68 : 62 : 53 : 50 : 38 : 36 : 35 : if (digits = return 0 : if (nptr[l] = 十十 nptr; exp digits = 0 : 十十 nptr; i f (d i g i ts = return 0 : ' Ⅱ nptr[l] = 58 : $ifdef TEST 59 : $include く stdio. h> int main(int argc, char **argv) if (argc く 2 ) return 0 : vhile (* + + argv ! = NULL printf("Xs = %d%n ” , *argv, check-double(*argv)) ; return 0 : 70 : #endif / * 空白があれば、それを読み飛ばす * / vhile (isspace(*nptr)) 十十 nptr; / * 文字列の終わりに達したかチェックする * / if (*nptr = return 1 : return 0 : 48 十十 d i g i ts : else if (*nptr = if (point 〉 0 ) return 0 : if (exp) return 0 : 十十 point; } else if (*nptr = if (exp) return 0 : C MAGAZINE 1 3 5 *nptr =

3. 月刊 C MAGAZINE 1993年5月号

技法 ム グ ロ 集プ 特 O 最初に ごく簡単な例題て、も各種のアプ ローチに基づく設計がありて , 結果として 得られるプログラムはまったく異なってく るということを実感してみることにしよう。 取りあえず , まず例題 1 を考えてみてほし [ 例題 1 ] check int 関数を作成せよ。 int check int (const char * nptr) check int は , 引数に文字へのポインタ を受け取る。もし , ポインタが正しい 10 進整数文字列をポイントしていれば 1 を返 し , それ以外の場合には 0 を返すものとす る。また , 数値的なオーパフローは考慮 しない。 この問題て、は , 「正しい 10 進整数文字列」 というところが重要なポイントて、ある。 ・その後に 10 進数字 ( ' 0' ~ ' 9 ' ) の 1 個以上の ば , 問題を見ただけて、 , 10 進整数文字列の れはきちんと定義すべき問題て、ある。 並びがくる。 定義を考えることなく , 即座に解答が出て て、は次のように解釈しよう。念のためにい ・その後には任意の数の空白を含んでいて えば , これは ANSI C ライプラリの atoi など くるかもしれない List 1 の考え方を疑似コードて大ざっぱに が受け付ける構文に対して若干制限を厳し もよい。 ・その後には文字列の終わりがきているこ 表すと次のようになるだろう。 くしたものてある。また , C 言語のソースレ ・文字列の先頭の空白類文字を読み飛ばす。 ベルての「 10 進定数 (decimal-constant) 」と ※符号と数字の間 , あるいは数字と数字の ・先頭に符号があれば , 読み飛ばす。 は異なる。 C の場合 , 10 進定数は 0 て、始まっ ・次が数字であることを確認し , 数字でな 間には空白は含まない。 てはならず , かっ , 符号は定数の一部とし ければ 0 を返す。 こまて、定義を与えられると , ある程度 ては許されない ・数字がきている間読み飛ばす。 以上 C プログラミングの経験がある人て、あれ ・先頭には任意の数の空白を含んでいても ・最後に再び空白類文字を読み飛ばす。 ば , ほとんど反射的に List 1 のような解答を よい。 ・文字列の終わりであれば 1 を返す , さもな ・その後には ' 思い浮かべるのて、はないだろうか。あるい ' または ' 十 ' の符号がどち は , この種のプログラムに慣れた人て、あれ ければ 0 を返す。 らかひとつきてもよい。 check int の実装 Fig. 1 浮動小数点数文字列の形式定義 浮動小数点文字列 : 空白類。符号 浮動小数点定数 空白類 浮動小数点定数 . 仮数部指数部 仮数部 . . 数字の並び 数字の並び小数部。町 小数部 . 数字の並び。町 指数部 . 指数文字符号。 数字の並び 指数文字 : one of E e 数字の並び : 数字 数字の並び数字 数字 : one of 0 1 2 3 4 5 6 7 8 9 符号 : one of 十 ロ OPt List 1 LlSt } vhile (isdigit(*nptr)); / * 空白があれば、それを読み飛ばす * / ⅶⅱ e (isspace(*nptr) ) 十十 nptr, / * 文字列の終わりに達したかチェックする * / if (*nptr ニ return l; return 0 : 25 : 26 : 27 : 28 : 29 : 30 : 32 : 33 : 35 : 36 : $ifdef TEST 37 : # i nc lude 38 : 39 : i n t 40 : nai n( int argc, char **argv) if (argc く 2 ) return 0 : vhile (* + + argv ! = N 乢し 45 : printf("Xs = XdYn", *argv, check_int(*argv)) : return 0 ; 48 : $endif 1 : #include く ctype. h> 2 : 4 : * 10 進整数文字列のチェックを行う 6 : int 7 : check-int(const char *nptr) 9 : / * 空白があれば、それを読み飛ばす * / vhile (isspace(*nptr)) 十十 nptr, / * 符号があれば読み飛ばす * / if (*nptr : Ⅱ *nptr : = 十十 nptr; / * 数字が少なくとも 1 つあることを確認する * / if (!isdigit(*nptr)) return 0 ; / * 数字を読み飛ばす * / 22 : do { 23 : く stdio. h> 十十 nptr; 特集 C プログラム設計技法 47

4. 月刊 C MAGAZINE 1993年5月号

て、表現て、きるのて問題はない 仮に , 対象とすべきものが , 一般の数式 とか , C 言語のソースプログラムそのものな どという場合には , それらの言語は正規式 ては表現てきない。それらは , より広い分 脈自由文法というクラスに入るからて、ある。 一般に BNF に代表されるメタ記法は分脈自 由文法を表現することがて、きる。分脈自由 文法は正規文法のクラスを含んて、いる。い い換えれば , 分脈自由文法に制限をつけた ものが正規文法て、ある。 しかし , grep などのツールにおいて正規 式の表現能力は十分高いと感じることから もわかるように , 正規文法しか解釈てきな いとしても , かなり広範な用途に用いるこ とはてきる。正規文法は有限状態オートマ トンて、解釈可能て、あるが , 一般の分脈自由 Fig. 2 0 60 : SI: 84 : OK : 26 : } 28 : / * 32 : { 43 : / * 10 進数整数文字列のチェックのための状態推移図 空白類 符号 SO 数字 数字 数字 S2 空白類 S3 空白類 文字列終了 文字列 終了 OK 文法はブッシュダウンオートマトンて、ない と解釈することがて、きない 状態モデルに従ってプログラム化を行う 具体的な手順としては , まず状態推移図を 0 書くことになる。後はそれを忠実に解釈実 行するようなプログラムを作成すればよい このアプローチは慣れていないと理解し にくいのて、 , まず簡単な例題 1 に戻って技法 を紹介してみよう。例題 1 て、定義した「 10 進 整数」を解釈するための状態推移図は Fig. 2 0 List 状態推移モテルによる check int の実装 2 : 4 : 6 : 7 : 8 : 9 : 12 : 15 : 16 : 20 : 27 : 29 : 30 : 33 : 35 : 36 : 37 : 38 : 39 : 40 : 42 : 44 : 45 : 46 : 49 : 50 : 52 : 1 : 囀 i nc lude <l i 田 i ts. h> * 文字の種別を表す列挙 typedef enun charKind { Eos White, Sign, Digit, 0ther, } charKind-t; List 4 98 : 96 : 93 : 90 : 85 : 83 : 80 : 77 : 68 : 66 : 65 : 63 : 62 : 59 : 58 : 57 : 56 : 55 : 53 : / * 符号があれば読み飛ばす * / i f (CharacterK inds[*nptr] goto S2; if (CharacterKinds[*nptr + + ] goto SI; goto ERR; = Digit) = Sign) if (CharacterKinds[*nptr + + ] ! = Digit) * 各文字コードに対応する文字種別を記憶する配列 17 : charKind-t CharacterKinds[UCHA•R-MAX] : * 文字種設定用補助関数 22 : static void setKinds(charKind-t *kinds, char *s, vhile ()s ! = kinds[(unsigned char)*s + + ] = kind; * 文字種配列初期化関数 31 : static void initKinds(charKind-t *kinds) charK i nd-t kind) goto ERR; 64 : S2: / * 数字を読み飛ばす * / if (CharacterKinds[*nptr] = Digit) { 十十 nptr; goto S2; i f (CharacterKindsC*nptr] = Eos) goto OK : 73 : S3: / * 空白を読み飛ばす * / if (CharacterKinds[*nptr) 十十 nptr; goto S3 : if (CharacterKinds[*nptr] goto OK : = White) { = Eos) i nt i : kinds['%0'] = Eos; ・ i く UCHAR_HAX + 1 : + + i ) for (i kinds[i] = 0ther; setKinds(kinds, ” 0123456789 ” , Digit); 10 進整数文字列のチェックを行う 曲 i (e) : setKinds(kinds, Sign); setKinds(kinds, ”←” 81 : ERR: return 1 ; return 0 : く stdio. h> 89 : #include 88 : #ifdef TEST 100 : #endif return 0 : printf("Xs = Xd*n ” , *argv, ⅶ ile (* + + argv ! = NULL initKinds(CharacterKinds); return 0 ; if (argc く 2 ) int main(int argc, char **argv) check-int(*argv)); int check- int(const char * 叩 tr) 48 : SO: / * 空白を読み飛ばす * / if ・ (CharacterKinds[*nptr] - = White) 52 C MAGAZINE 十十 nptr; goto SO; 1993 5

5. 月刊 C MAGAZINE 1993年5月号

計一ま 設技 : 「一 特集 C プログラム 状態推移表 る。これまて、のプログラムに比べて , 少々 推移図の記法て、あるバブルチャートと呼ば 文字列 小道具が増えているのて、 , まずその解説を れる図式を用いている。各バブルに付され 終了 ている SO ~ S3 , OK などの名前は「状態」を識 しておこう。最初に enumcharKind という SO S2 列挙を宣言し , それを charKind t という名 別するためのものて、ある。名前て、なくても , S2 前て、 typedef している。これは文字の種別を 整数て、もかまわない ( 後の例て、はプログラミ 表すためのものて、ある。 10 進整数をチェッ ング上の関係から整数にする ) 。各状態にお S2 クする用途て、は , 文字は空白類 , 符号 , 数 いて , 定義された入力が与えられると , 特 字 , 文字列の終わり , そしてそれ以外とい 定の状態へと推移する。そして一般には , う 5 種類に大別して考えることがて、きる。 その「推移する」瞬間に仕事を行う・・ CharacterKinds [code] れは ctype. h をインクルードして ,isdigit() えば何らかの値を出力するのて、あるが , 今 などを利用してもかまわないが , ここて、は として参照すれば , その文字コードに対す 回のプログラムては , 最終的に「与えられた 文字列は構文に沿っているかどうか」を判断 抽象化をねらって列挙を使用している。そ る charKind t の値が得られるようにするた して , 文字コードの数と同じ要素数を持っ めのて、ある。ややメモリがもったいない感 するだけて、よいのて、 , 途中て、は仕事をする charKind t 型の配列 CharacterKinds [ ] を じもあるが , たかだか UCHAR MAX ( 普通 必要がないこともあり , 論点をばやけさせ 宀言する。これは任意の文字コードが与え は char は 8 ビットなのて、 , 全部て、 256 要素 ) ないためにそれに関しては配慮していない られたときに , その文字コード - を添字として , なのて、 , このような方式を採用した。 さて , この状態推移図を素直にプログラ 状態推移表による check int の実装 0 1 っム 3 S3 S3 OK OK 0 List 5 List 52 : 53 : 54 : static int st0 55 : stat i c i nt st 1 56 : static int st2 57 : static int st3C 58 : 59 : / * 60 : * 状態推移行列 62 : int *StateMatrix ロ 63 : st0, stl, st2, st3, 64 : } ; 65 : 66 : / * 67 : * 状態推移解釈ルーチン 68 : 69 : static int 70 : stateMachlne(charKind-t *kinds, int **matrix, const char *nptr) i n t st : 73 : for ()t = 0 : st ! = OK; + + nptr) if ((st = matrix[st)[kinds[*nptr]]) = ERR) return 0 : 76 : return 1 ; 78 : } 80 : / * 10 進整数文字列のチェックを行う 82 : int check-int(const char *nptr) 83 : 8 4 : { return stateMachine(CharacterKinds, StateMatrix, nptr) ; 85 : 86 : } 87 : 88 : #i fdef TEST 89 : #include く stdiO. h> 90 : int main(int argc. char **argv) if (argc く 2 ) return 0 : initKinds(CharacterKinds) : 95 : ⅶ ile (* + + argv ! = NULL 96 : printf("%s = %d%n", *argv, check-int(*argv)); return 0 : 100 : #end i f ・ーワ朝っ 0 っロ】 0 「】ワひワ 0 1 : # i ncl ude <l ⅳ i ts. h> 2 : 4 : * 文字の種別を表す列挙 6 : typedef enum charKind { 7 : Eos 8 : White, 9 : Sign, 10 : D i g i t, Point, Exponent, 12 : 0ther, 14 : } charKind-t; * 各文字コードに対応する文字種別を記憶する配列 19 : charK i nd-t CharacterKinds[UCHAR-MAX] : 20 : 22 : * 文字種設定用補助関数 23 : 24 : static VOid setKinds(charKind_t *kinds, char *s, charKind-t kind) 26 : kinds[(unsigned char)*s + + ] ニ kind : 27 : 29 : 30 : / * * 文字種配列初期化関数 32 : 33 : static VOid initKinds(charKind-t *kinds) 34 : { 35 : i n t i : 36 : kindsC'%0'] = Eos; i く UCHAR_MAX + 1 : + + i ) fo 「 (i 38 : kindsCi) = 0ther; 39 : " 0123456789 " , Digit); setK i nds(kinds, 40 : Sign); setK inds (kinds. ”十一 setKinds (kinds, Point); setKinds(kinds, ” eE ” . Exponent) : 43 : White); setKinds(kinds, ” }t}fYn ” 44 : 46 : 47 : #define ERR ー 2 48 : #define OK 50 : / * * 各ステートに対する状態推移データ 特集 C プログラム設計技法 53

6. 月刊 C MAGAZINE 1993年5月号

技法 ロ このアプローチは , これまて、の中て、いち ばんプログラムが長くなってしまっている が , 実際にはいちばん汎用性があるアプロ : 〇 ーチて、ある。現に , List 6 と List 5 と見比べ ていただくとわかるのだが , 実質的に , Li 一〇 st 5 と List 6 の違いは , 状態推移行列だけて、 ある。処理対象の文法の差異 ( 10 進数か浮動 : 〇 小数か ) をデータの違いだけに集約し , 共通 のコードて、処理て、きるということて、ある。 : 〇 細かくいえば , 両者は , そのほかに charKi nd t の定義が異なっているが , こて、は関 一〇 数 stateMachine ( ) がまったく同一て、あると いうことに注目してほしい : 〇 なお , 状態推移表の中に ERR という値が たくさんある。これは , 定義されていない : 〇 入力があったということて、ある。一般に 一〇 素直に状態推移表を作ると , そのサイズは かなり大きなものとなるが , 大半はこのよ : 〇 うな ERR などの未定義値て、占められること になる。したがって , なんとか表のサイズ 一〇 を小さくするような工夫というものもいろ こて、はそれには いろと行われるのだが , 一〇 触れないことにする。 実際にこのモデルを応用している例には , 一〇 字句解析系の自動生成ソフトウェアがある。 コンパイラのフロントエンドて、は必ず字句 解析 (lexical analysis) という作業を行わな 一〇 ければならない。これは今回の例題 1 や例題 2 のような作業をさらに拡張したような処理 : 〇 て、ある。そして , そのような字句解析を行 うソフトウェアを「字句解析系 ( lexical ana lyzer ) 」と呼ぶのだが , これを自動的に作り 出してくれるソフトウェアが , 「字句解析系 自動生成プログラム (lexical analyzer gen erator) 」て、ある。有名なのが lex とか flex と 呼ばれるソフトウェアて、ある。 これらのソフトウェアが生成する字句解 析系は状態推移モデルに基づくものて、ある。 少なくとも , lex や flex が生成する字句解析 系はそうて、ある。このモデルに従うのがい ちばん自動生成しやすいからだ。なぜなら ば , このアプローチにおいては , ソースの 中て、 , 与えられた文法によらず固定部分に しておいてよい部分と , 文法に依存して変 特集 C プログラム設計技法 55 特集 C プログラム List 6 52 : / * * 各ステートに対する状態推移データ 53 : 〇 ! 54 : 55 : / * Eos, Whi te, Sign, D i g i t, Point, Exponent, 0ther, int st0C] { ERR, 56 : static 0 , 8 , ERR, ERR, int stl ] { ERR, 57 : static ERR, ERR, 8 , ERR, ERR, 58 : static int st2 7 , ERR, 3 , 4 , ERR, 〇 ! 59 : static int st3 7 , ERR, ERR, 4 , ERR, int st4 ] 60 : static ERR, ERR, ERR, ERR, ERR, int st5[] 61 : static ERR, ERR, ERR, ERR, ERR, ERR, int st6[] 62 : static OK, 7 , ERR, ERR, ERR, ERR, 〇 ! 63 : static int st7 7 , ERR, ERR, ERR, ERR, 64 : int st8 static ERR, ERR, ERR, ERR, ERR, int st9[] = { OK, 65 : static 7 , ERR, ERR, 4 , ERR, 66 : 67 : / * 〇一 68 : * 状態推移行列 69 : 70 : int *StateMatrix ロ st0, stl, st2, st3, st4, st5, st6, st7, st8, st9, 〇一 72 : } ; 73 : 74 : / * 75 : * 状態推移解釈ルーチン 76 : 77 : static int 78 : stateMachine(charKind-t *kinds, int **matrix, const char *nptr) 80 : i n t s t : 〇一 81 : for ()t = 0 : st ! = OK; + + nptr) 83 : if ((st = matrix[st][kinds[*nptr]]) = ERR) return 0 : 85 : return 1 : 〇。 88 : / * * 浮動小数点数文字列のチェックを行う 89 : 〇一 90 : int 92 : check-double(const char *nptr) return stateMachine(CharacterKinds, StateMatrix, nptr) ; 〇 : ー 97 : #ifdef TEST 98 : #include く stdio. h> 〇 ! 99 : 100 : int mai n( i nt argc, char **argv) 101 : 102 : if (argc く 2 ) 103 : 〇 ! 104 : return 0 : i n i tKinds(CharacterK inds) : 105 : ⅶ ile (* + + argv ! : NULL 106 : printf("%s = XdYn", *argv, 107 : check-double(*argv)) : 108 : return 0 ; 109 : 1 10 : #end i f 〇一 List check doub 厄の実装 ( 第 4 案 ) 〇〇〇〇〇〇 〇 : 1 : #include く stdio. h> 2 : # i nc lude <l i m i ts. h> 3 : 〇一 * 文字の種別を表す列挙 5 : 7 : typedef enum charK ind { 8 : Eos 9 : Wh i te, Sign, D i g i t, Point, 13 : Exponent, 0ther, 15 : } charKind_t; 16 : 1 7 : / * * 各文字コードに対応する文字種別を記憶する配列 〇 ! 20 : charKi nd-t CharacterK indsCUCHAR-MAXJ : 22 : / * 〇 ! 23 : * 文字種設定用補助関数

7. 月刊 C MAGAZINE 1993年5月号

タを取得 , 設定します。 それては以下て、各アイテムの処理の方法 を見ていきましよう (List 3 ) 。 ーチ = ックボックス チェックポックスは , ひとつひとつがオ ンやオフの状態を持ちます。ューザがチェ ックポックスをクリックすると , そのとき の状態がオンならばオフに , オフならばオ ンにしなければなりません。要するに 0 と 1 の反転て、す。これは 1 との排他的論理和を使 うことて、実現て、きます。具体的には List 3 の setCheckBox(67—77 行目 ) のようにしま す。 ラジオボタン 次にラジオボタンの処理を説明しましよ う。ラジオボタンはいくつかのボタンがひ とつのグループを形成します。その中て、た だひとつのボ、タンだけが選択された状態に ならなければなりません。つまり , Fig. 5 て、 はアイテム 3 から 5 はひとつのグループとな ります。 これらのうちのどれかひとつが選択され たときには , それまて、選択されていたアイ テムは選択されていない状態に設定し直さ なければなりません。 この処理は List 3 の setRadioButton ( 52 ~ 64 行目 ) のようにします。この関数はパラ メータにもらったアイテム番号のラジオポ タンを選択された状態 ( 1 ) にします。グルー プ内のそれ以外のラジオボ、タンは選択され ていない状態 ( 0 ) にします。 スタティックテキスト スタティックテキストはマウスクリック などのイベントを受け取らないように disab led になっています。また , 今回のプログラ ムて、は使用していませんが , テキストの中 に , , , が現れると , これらは関数 ParamText て、設定された文字列に置き換え られます。 122 C MAGAZINE 1993 5 List 3 Handle iHandIe; Rect iRect; &iType, &iHandle, &iRect); GetDItem(theDiaIog, Radi0ButtonlID, theltem : = RadioButtonIID); SetCtlValue((Contr01HandIe) iHandle, &iType, &iHandIe, &iRect) : GetDItem(theDiaIog, Radi0Button21D, theltem = = RadioButton21D) : SetCtlValue((ControlHandIe) iHandle, &iType, &iHandIe, &iRect) : GetDItem(theDiaIog, Radi0Button31D, theltem = RadioButton31D) : SetCtlValue((Contr01HandIe) iHandle, 55 : 58 : 62 : 63 : 64 : } 66 : 67 : void setCheckBox(DiaIogPtr theDialog, short theltem) short iType; Handle iHandIe; 70 : Rect iRect; short value; &iRect); GetDItem(theDiaIog, theltem, &iType, &iHandle, 74 : = GetCtlVaIue((Contr01HandIe)iHandle); 75 : value SetCtlValue((Contr01Handle) iHandle, value 76 : 79 : 80 : void doModaIDiaIog() DiaIogPtr dptr; 82 : short itemHit, Handle iHandIe; Rect iRect; 85 : short iType; 86 : dptr ニ GetNevDiaIog(DiaIogID, NULL, (void *)-l); 88 : 89 : GetDItem(dptr, UserItemID, &iType, &iHandle, &iRect); 90 : &iRect); SetDItem(dptr, UserItemID, iType, (void *)drawUserItem, SelIText(dptr, EditTextID, 0 , 32767 ) : 92 : setRadi0Button(dptr, Radi0ButtonIID); 93 : setCheckBox(dptr, CheckBox21D) : 95 : ShowWindow(dptr) ; 96 : 97 : M0daIDiaIog(NULL, &i temHit) : 98 : switch (itemHit) { 99 : case RadioButtonlID: 100 : setRadioButton(dptr, RadioButtonlID); 101 : 102 : break, case RadioButton21D: 103 : RadioButton21D); setRadi0Button(dptr, 10 4 : break; 105 : case RadioButton31D: 106 : RadioButton31D); setRadi0Button(dptr, 107 : break; 108 : case CheckBoxlID: 109 : setCheckBox(dptr, CheckBox11D) : 110 : break; 111 : case CheckBox21D: 112 : setCheckBox(dptr, CheckBox21D) : 113 : break; } while (itemHit ! = 0k & & itemHit ! = cancel); 1 1 6 : DisposDiaIog(dptr) : 119 : 120 : 121 : void handleAppleCh0ice(short item) 122 : { Str255 accName; 123 : int accNum; 124 : 125 : svi tch (item) { 126 : case AboutItemID: 127 : N0teAIert(AboutAlertID, NULL) : 128 : break, 129 : default: 130 : GetItem(AppIeMenuH, item, accName) : 131 :

8. 月刊 C MAGAZINE 1993年5月号

List 7 モジュル分割の戦略 212 : } 213 : 〇 ! につし YC 2 1 4 : / * 浮動小数点数文字列のチェックを行う 215 : 2 1 6 : int check-double(const char *nptr) 217 : 〇一 さて次に , 規模の大きいソフトウェアを 218 : { return stateMachine(CharacterKinds, st0, nptr) : 219 : 220 : 開発する上て、考慮すべき点について , その 221 : 222 : #ifdef TEST 〇 : さわりを述べておこう。いかにして大きな int main(int argc, char **argv) 223 : 224 : { ソフトウェアを作るかという点て、ある。極 if (argc く 2 ) 225 : 226 : return 0 : initKinds(CharacterKinds); 〇 : 227 : めて大ざっぱにいうと , 大きなソフトウェ vhile (* + + argv ! ニ NULL 228 : printf("%s ニ %dYn" 229 : *argv, check-double(*argv)) : アを設計する基本方針は実はひとっしかな 230 : return 0 : 231 : } い。それは分割統治法て、ある。誰て、も , 〇 : 232 : #endif 度に多数のことを考えることはて、きない。 だから , 多数の機能を持った複雑なソフト 〇一 ウェアを一度に開発することはて、きない。 しかし , 小さい問題て、あれば , 解決は容易 の基本方針においては一致している。「ソフ るまて、 , この手続きを再帰的に繰り返せば になる。 そこて、 , 間題全体を複数に分割し , より いいのて、ある。 トウェアの設計技法とは分割の技法にほか たやすい間題 ( ただし個数は増える ) に置き 要するに大規模なソフトウェアの設計て、 ならない」といっても過言て、はない。それぞ 換えるという方針て、ある。分割後の問題が は , どう分割するか , そこがキーなのて、あ れの技法の違いは , 何に注目して分割する まだ難しい場合には , それをさらに分割す る。すて、に述べたように かという点て、ある。 ソフトウェアの る。問題がたやすく解決て、きるサイズにな 設計技法は何種類もあるのだが , どれもこ 分割のストラテジとして , こて、は次の Fig. 3 浮動小数点数文字列のチェックのための状態推稚図 ( フェンスチャート ) SO S2 S4 〇〇〇〇〇〇 S9 S8 S7 S6 S5 S3 0 K 空白類 数字 数字 空白類 数字 文字列終了 文字列終了 空白類 数字 符号 指数 小数点 数字 符号 指数 数字 数字 数字 空白類 空白類 空白類 文字列終了 文字列終了 数字 小数点 文字列終了 小数点 指数 58 C MAGAZINE 1993 5

9. 月刊 C MAGAZINE 1993年5月号

List 7 1 18 : 120 : 121 : 122 : 1 2 3 : 124 : 125 : 126 : 128 : 129 : 130 : 131 : 132 : 133 : 134 : } 135 : 137 : 138 : 139 : 140 : 141 : 142 : 143 : } 1 4 4 : 146 : 147 : 148 : 149 : 150 : 1 5 1 : 1 5 2 : 153 : 154 : 1 5 5 : 156 . 159 : { 160 : 1 6 1 : 162 : 163 : 164 : 165 : 166 : 167 : } 168 : 169 : 170 : { 171 : 172 : 173 : 174 : 175 : 176 : } 178 : 179 : 180 : 181 : 182 : 183 : 184 : 185 : 186 : 188 : 189 : 190 : 191 192 : 194 : { 195 : 196 : } 197 : 198 : / * 199 : 200 : 201 : 203 : 204 : 205 : 206 : 207 : 208 : 209 : 210 : 21 い return (funcp-t)st3; case Exponent: return (funcp-t)NULL; return (funcp-t)st4; statiC funcp-t st4(charKind-t kind) switch (kind) { case S ign: return (funcp-t)st5; case D i g i t : return (funcp-t)st6; return (funcp-t)NUL し : 136 : static funcp-t st5(charKind-t kind) switch (kind) { case D i g i t : return (funcp-t)st6; return (funcp-t)NULL; 145 : static funcp-t st6(charKind-t kind) svitch (kind) { case EOS : return (funcp-t)ok; case White: return (funcp-t)st7; case D i g i t : return (funcp-t)st6; return (funcp-t)NULL; 158 : static funcp-t st7(charKind-t kind) svitch (kind) { case return (funcp-t)ok; case Whi te: return (funcp-t)st7; return (funcp-t)NULL; static funcp-t st8(charKind-t kind) svitch (kind) { case D i g i t : return (funcp-t)st9; return (funcp-t)NU しし static funcp-t st9(charKind-t kind) svitch (kind) { case Eos: return (funcp-t)ok; case White: return (funcp-t)st7; case Digit: return (funcp-t)st9; case Exponent: return (funcp-t)st4; return (funcp-t)NULL; 193 : static funcp-t ok(charKind-t kind) return (funcp-t)NULL; * 状態推移解釈ルーチン static int return 1 : return 0 : if ()t = (funcp-t (*)(charKind-t))NUL い st = (funcp-t (*)(charKind-t))st(kinds[*nptr]) : for ()t = s0; st ! = 0k; + + nptr) { funcp-t (*st)(charKind-t) : 202 : stateMachine(charKind-t *kinds, funcp-t s0(charKind-t), const char *nptr) ロ 技法 めには , もっと広いクラスを受理て、きるモ デルが必要になる。そのような場合には , 状態推移図をネストさせるという対策を施 すことがて、きるのだが , 本稿て、はこれ以上 は触れないことにする。 状態推移モデルの明白な弱点として , ソ ースを見ただけて、 , どのような処理を行お うとしているのかが一見て、は判然としない 点が指摘て、きる。 List 1 や List 3 て、あれば , ソースを注意深く読めば , だいたい何を意 図しているのかが理解て、きよう。 ところが , List 5 や List 6 て、はそうはいかないだろう。 したがって , このアプローチを用いる場合 には , 必ずドキュメントに状態推移図をつ けておくことが重要て、ある。それをしない と , このコードはもっとも保守がめんどう なコードということになりかねない 最後に ,List 5 や List 6 に見られるアプロ ーチを用いると , 処理速度的には不利にな る場合もあることも記憶しておいてほしい 状態推移部分は , 一種のインタブリタを形 成しているからて、ある。もっとも , 多くの アプリケーションて、はこのペナルティは許 容範囲内て、ある。 以上見てきたのは , ソフトウェア全体の 設計の話て、はなく , 単にひとつの関数の話 にしかすぎない。しかも処理的には比較的 簡単だと思われる浮動小数点数文字列のチ ェックプログラムなのて、ある。それなのに たったこれだけの関数にかぎっても , 実は さまざまなアプローチが可能て、ある。そし て , それぞれのアプローチには長所短所が 存在しており , 「こういった状況の下て、は , この方法が優れている」というようなことは いえても , 常にどれかがいちばん優れてい るとはいえないのが実状て、ある。 この多用性がプログラム設計の難しさの 原因にもなっているのだが , 裏を返せばデ ザイナーの腕の見せどころて、もある。した がって , よいソフトウェアを設計するには , いろいろなアプローチ法を知っておく必要 がある。 良きデザイナーになるためには日々の胼 特集 C プログラム が重要て、ある。 特集 c プログラム設計技法 57

10. 月刊 C MAGAZINE 1993年5月号

学 五ロ ルトの処理を示すボタンは黒い線て、囲まれ ます。このデフォルトのボタンはマウスて、 クリックする代わりにリターンキーて、代用 て、きます。安全な動作をデフォルトのボタ たとえば工デイタ ンに設定してください て、文書を変更して Quit しようとすると , Fi g. 3 のようなダイアログを表示します。変更 したデータを失わないためにはセープする 必要があります。そこて、デフォルトボタン は Yes ( セープする ) になっています。 なお , ボ、タン , ラジオボタン , チェック ポックスなどは , コントロールと呼ばれ , コントロールマネージャによって管理され ます。 ーダイアログの表示 ダイアログを表示するためには NewDiaI og あるいは GetNewDialog を使います。 Ne wDialog はリソースを使わないて、 , アプリケ ーションの中から直接ダイアログを生成し ます。これは表示する座標を指定したり , ダイアログアイテムのリストを作成しなけ ればならなかったりて、 , かなり繁雑な処理 となります。よって , 今回は使用しません。 GetNewDialog は Fig. 4 のようにプロトタ イプ宣言されています。この関数 ( 正確には システムコール ) は , 指定されたダイアログ のリソース ( リソースタイプは ' DLOG ' ) を読 み込んて、ダイアログの内部データを初期化 します。このときにダイアログレコードの アドレスや NULL を dStorage に与えます。 NULL を渡した場合は , ツールポックスが アプリケーションのヒープ領域にダイアロ グレコードを作成します。 behind はダイア ログをウインドウのリストのどの位置に挿 入するのかを指定します。一番手前の位置 に挿入するには , (void * ) ー 1 と指定します。リターンされる値はダイア ログレコードへのポインタて、す。ダイアロ グの作成に失敗すると NULL を返します。 GetNewDialog< ダイアログが作成される と同時に表示されるようにリソースを設定 することがて、きます。アプリケーションが Fig. 2 代表的なダイアログアイテム 〇ラジオボタン 1 〇ラジオボ・タン 2 〇ラジオボタン 5 ロチェックポックス 1 ロチェックポックス 2 スタティックテキスト 偏隼可能子キュト Fig. 3 テフォルトボタンの例 Save changes before closing ? Yes Cancel NO Fig. 4 GetNewDialog と DisposDialog DiaIogPtr GetNewDiaIog(short dlgRsrcID, Ptr dStorage, WindowPtr behind) ; dIgRsrcID ダイアログのリソース ( タイプは ' DLOG ' ) の ID ダイアログレコードのアドレス dStorage NULL をあたえるとツールポックスが作成する どのウインドウの下に表示するかを指定する behind ー 1 のときは一番上に表示する ダイアログレコードのアドレス リターン void DisposDiaIog(DialogPtr theDialog); theD i a log 解放するダイアログレコードのアドレス ダイアログの生成と解放 #define DialogID 1000 / * 'DLOG' リソースの * / DialogPtr dptr; dptr = GetNevDiaIog(DialogID, NULL, (void *)-1); ShovWindow(dptr) : / * ここでダイアログの処理をする * / HideWindow(dptr) : DisposDiaIog(dptr); List 1 C 言語雑学講座 119