これはどのような型へのポインタかとい うと , 先ほどの Table と同じ型へのポインタ て、ある。すなわちトータルとしていえば table は次のような型の変数て、ある。 tab 厄は「 int の引数をニつ受け取り , int を返す関数」へのポインタの F 要素の配 さてここまて、きたら , 次には ( プログラム の構造化という観点からみると退歩なのだ が ) 関数 showTabIe をなくして main の中て、展 開してしまうことを検討しよう。結論から いうと List 4 のように記述すればよいのて、あ List 4 て、注目したいのは printf のところて、 使用している table を用いた関数呼び出しの 形式て、ある。 printf("%4d", table[k](i, これは K & R の時代て、はコンパイルエラー てある ( 今て、も一部のコンパイラて、はこのよ うな記述を通さないものがあるかもしれな い。しかしそれは ANSI 準拠て、はないという ことて、ある ) 。伝統的な記法て、は , 次のよう に書かねばならなかった。 (*table[k])(i, printf("%4d" 一見してわかるように , List 4 の書き方は 関数へのポインタ変数に対してインディレ クション ( 単項の * の適用 ) を行わなくても よくなったことによって可能になったもの てある。通常の感覚て、は配列の添字づけの うに修正したプログラムを示す。 っていたところが getFunc(int n) となってい 直後に関数の引数を表す力ッコがきている ることと , table の場合には初期化子が与え List 5 の注目点は getFunc という関数の定 のは奇妙に思えるが , ANSI C 標準にきちん られていたところに関数の本体が与えられ 義と , その呼び出し部分て、ある。まず定義 と従っているコンパイラて、あれば , これは ている点て、ある。 table の場合には [ F ] がつ のほうから考えてみる。 getFunc の定義は次 正しいプログラムて、ある。 いていたために配列変数となり , getFunc は さて少し視点を変えて , table という配列 のようになっている。 (int n ) がついているために関数となる。すな の代わりに「どの関数を呼び出すべきかを決 int (*getFunc(int n))(int, int) わち getFunc の型を文章て、述べればこうな 定する関数」を定義してみよう。その場合に は「呼び出すべき関数へのポインタを返す関 getFunc は「 int の引数をニつ受け取り , 数」を作ればよい このようにすると最初に int を返す関数」へのポインタを返す関数 述べた「呼び出すべき関数を計算する」とい であり , その引数は int n である う目的に近づくことになる。 List 5 にそのよ 引数の値に応じて呼び出すべき関数へのポインタを返す関数 LiSt 1 : / * switch を用いて分岐させたバージョン 2 : 3 : int (*getFunc(int n) ) (int, int) switch (n) { 5 : 6 : case 0 : return add; case 1 : 8 : 9 : return sub; case 2 : 11 : return mul; default: fprintf(stderr, "getFunc: is invalid argument*n ” , n) ; 13 : exit(l); 14 : 16 : } LiSt 1 : int 2 : min(void) static int (*tableCF]) (int, int) 4 : 5 : add, 6 : sub, mul, 8 : 9 : 10 : 11 : 14 : 15 : 19 : 20 : 22 : 23 : 24 : 25 : } / * 二つの int の引数を受け取り , int を返す関数へのポインタ f 皿 cp * / int (*funcp) (int, int) : int i, j, k; for k = 0 ; k く F; + + k ) { * k によって決定された呼び出すべき関数へのポインタを 内側のニつの for ループの外で funcp へとセットしておく * / funcp = tableCk] ; for (i = 1 ; i く N; + + i ) { for (j ー priÄtf{"hd" funcp(), j)) ; printf("}n ) ; printf( ”” ) ; return 0 ; これは , 先の table という配列の宣言と非 常によく似ている。違うのは table CF] とな 110 C MAGAZINE 1991 6
N mul, more List 11 : { 13 : } 16 : { 18 : } 25 : { 38 : } 44 : { 56 : } く stdio. h 〉 1 : #include 2 : 4 : 5 : 7 : 9 : 10 : 12 : 14 : 15 : 17 : 19 : 23 : 24 : 26 : 28 : 29 : 30 : 32 : 33 : 34 : 35 : 36 : 37 : 39 : 41 : 42 : 45 : 49 : 50 : 51 : 52 : 53 : 54 : 55 : 呼び出すべき関数へのポインタを返す関数 int add(int re turn int sub(int n, re turn int mul(int n, return n 十 m,• n, i nt i nt i nt 20 : #define F 3 22 : / * 引数の値に応じて , 呼び出すべき関数へのポインタを返す関数 int (*getFunc(int n)) (int, int) static int (*tableCF]) (int, int) exit(l); fprintf(stderr, if (n く 0 Ⅱ n > = F) { / * 工ラーチェック * / mul, sub, add, 'getFunc. IS invalid argument*n" re turn 10 tableCn] ; 40 : #define N int 43 : main(void) for (k = 0 ; k く F; + + k ) { for (i = 1 ; i く N ; + + i ) { return 0 ; printf("*n") ; printf( ” frl' ) ; printf ぐ、 hd", getFunc(k) (i for (j この宣言は典型的な関数へのポインタ配 列の宣言のパターンを示しているのて、はあ るが , 慣れない人にとってはおそらく非常 に難解に見えるだろう。まず static て、ある が , これは単に効率向上のためにつけただ けなのて、外してもかまわない。また” 下は初期化子 (initializer) て、 , 配列変数の宣 言と同時に初期値を与えるためのものて、あ る。したがって , これも宣言の理解のため に外すことにする。もちろん , 初期化子を 外した場合には別途 table の各要素に値を代 入しておかないとプログラムの動作が変わ ってしまうにの場合不当なアドレスへの関 数コールが起きて , プログラムは暴走する ードウェアにメモリ保護機能が備え カ ) ノ、 られている場合には , メモリ保護違反て、 OS にトラップされることになるだろう ) 。これ らの付加物を取り除くと , 宣言は次のよう int (*tabIeCF])(int, int); これて、もまだなんだかよくわからないの て、 , とりあえず (*tabIeCF]) 全体を TabIe と 書くことにする。すると次のようになる。 int Table(int, int); これならば , ようやく見慣れた形になっ てきた ( はずて、ある ) 。この宣言をこのまま 信じれば , Table という名の関数宣言 ( プロ トタイプ宣言 ) て、ある。どんな関数かという と , int 型の引数を二つ受け取り , int を返す ような関数てあるということになる。 今度は ( * table [ F ] ) の解釈をしてみよう。 まずこれは ( * (table [F] ) ) というように CF] の結合のほうが強い。したがって , table [ F ] が最初に解釈すべき対象て、ある。 tabIeCF] F 要素の配列 table そうして , 次はその一つ一つの要素に * がっくのて , これは「ポインタ」て、ある。 *tabIe[F] ポインタの F 要素の配列 ta e ANSI C : more 109
List 1 227 : / * マクロ定義 * / VOid set macro( Char *name, 151 : 152 : 153 : 154 : 155 : 156 : 157 : 158 : 159 : 160 : 161 : 162 : 163 : 164 : 165 : 166 : 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 : 193 : 194 : { 196 : 197 : 198 : 199 : 200 : 201 : 202 : 203 : 204 : 205 : 206 : 207 : 208 : 209 : 210 : 211 : 212 : 213 : 214 : 215 : 216 : 217 : 218 : 219 : 220 : 221 : 222 : 223 : 224 : 225 : } 226 : line[ 256 ] : 257 : / * if( ( cp = malloc( n + 1 ) ) = NUL し ) error( "MEMORY ERROR" ) : fp ) ) ニ NULL ) error( "MEMORY ERROR" ) : " T00 MANY TARGET ” ) : List 1 Char CC, *cp, *pre, *next int targetnumber, n,• char *linkpointer; , buffer[ 256 ] , 228 : 229 : char *line ) targetnumber ニ 0 : linkpointer ニ NU しし : fp ニ fopen ( makef ⅱ ename, if( ( cp ニ fgets( buffer, while( ! feof( fp ) ) abort() : fprintf( stdout, i f ( fp - = NUL し ) ” MAKEFlLE=%sYn ” 256 , makefi ニ NULL ) break : while( cp ) if( *cp cp 十十 : if( buffer[ 0 ] = if( line[ 0 ] : 0x0a ) { *cp : 0 : break,• ) continue; while( expand_macro( line, buffer ) ) set_ target _or_macro ( ⅱ ne ) : set-command( line e ー se fclose( fp ) : void set_target_or 195 : char name[ 256 ] : char *CP, *dp; while( *cp くニ *dp : 0 ; *dp 十十 : *cp 十十 : if( *cp = if( *cp while( *cp 〉・ dp ニ name; CP line; _mac ro ( Char ⅱ ne ・ ) break; ・ ) break; switch( *cp+ 十 ) cp 十十: if( ! *CP ) break; error( 230 : 231 : 232 : 233 : 234 : 235 : 236 : 237 : 238 : 239 : 240 : 241 : 242 : 243 : 244 : 245 : 246 : 247 : 248 : 249 : 250 : 251 : 252 : 253 : 254 : 255 : } 256 : 258 : 259 : 262 : 263 : 264 : 265 : 266 : 267 : 268 : 269 : 270 : 271 : 272 : 273 : 274 : 275 : 276 : 277 : 278 : 279 : 280 : 281 : 282 : 283 : 284 : 285 : 286 : 287 : 288 : 289 : 290 : 291 : 292 : 293 : 294 : 295 : 296 : 297 : 298 : 299 : 300 : 301 : 302 : int n; char *cp; wh ile ( * ⅱ ne ← if( ! *line + + ) error( "SYNTAX... Missing MACROSTRING" ) : strcpy ( cp, name ) : if( ( cp : malloc( n + 1 ) ) ニ n ニ strlen( name ) ; MACRO[ MACROCOUNT ]. line strcpy( cp, line ) : n = strlen( line ) : if( VERBOSE ) MACRO[ MACROCOUNT ]. name ニ cp; printf( "Define $(%s) to Y"%sY"Yn", MACROCOUNT + + : MACRO[ MACROCOUNT ]. name, MACRO[ MACROCOUNT ] コ ine ) : link->next ニ NULL,• strcpy( link->name, name ) : link->name ニ malloc( strlen( name ) + 1 ) : ⅱ nk = &TARGET [ TARG ETCOUNT ] : 261 : struct _makelist *link; 260 : char *cp, *sp, temp[ 128 ] : VOid set_target( char *name, char *line ) ターゲットリストの登録 * / while( *CP くニ while( *cp ) CP ⅱ ne : link->name ニ malloc( strlen( temp ) + 1 ) : link->next; ⅱ nk link->next = malloc ( sizeof( struct makelist ) ) : *SP ニ 0 ; *sp 十十ニ *cp 十十 : while( *CP > ' sp ニ temp : if( ! *cp ) break,• if( ! *cp ) break; else cp + 十 : strcpy ( link->name, temp ) : link->next : NU しし : if( VERBOSE ) link ニ &TARGET[ TARGETCOUNT ] : printf( "Target %s: Dependent". while( link->next い NU しし ) link->name ) : case case if( TARGETCOUNT > ニ MAXTARGET ) set-target( name, cp ) : break : if( MACROCOUNT 〉ニ MAXMACRO ) link link->next; printf( " Y"%sY"" printf( ” Yn ” ) : link->name ) : set_macro ( name, cp ) : break : default: error ( ” SYNTAX ERROR.. error( MANY MACRO" ) : or Expect i ng COMMANDLINK ニ &COMMAND[ TARGETCOUNT ] : COMMANDLINK- 〉 name ニ NU しし : プロジェクト PragmaC 55
N mo て、第 1 引数に関数へのポインタを渡している のて、ある。呼び出しの実引数には , add, sub, mul と , それぞれ関数名を書いている ことに注意しよう。 add などは「関数名」て、あ る。それを実引数式の中て、記述すると , 関 数へのポインタが渡される。このことは次 のルールによるものて、ある。 「 T 型を返す関数」を表す関数指定子 (function designator) は , それが sizeof のオペランドや単項の & のオペランドと して現れている場合を除けば自動的に 「 T 型を返す関数へのポインタ」型の式に 変換される ( ANS 冂 . 2.2.1 ) こて、 , 「関数指定子」とは「関数型 ( func tion type ) 」をもつ式のことて、ある。もっと も単純な関数指定子は , いうまて、もなく関 数名て、ある。このルールは , 式の中に配列 が出現した場合に自動的にその先頭要素へ のポインタが生成されるルールと対をなす ものて、あり , どちらも「ポインタ生成 (pointer generation) 」と呼ばれる。ただし「関数型」の 変数は宣言て、きす , それを返す関数を作る こともて、きない。つまり「関数型」は非常に 限定された局面て、しか使うことがて、きない のて、ある。もちろん「関数へのポインタ型」 て、あれば , 変数も宣言可能だし , それを返 す関数も定義可能て、あることはいうまても こて、いっている「関数型」とは , い わば関数の本体全体がもつ型のことてある。 なお , List 2 の showTable の記述に注目し ていただきたい。関数へのポインタてある 仮引数 func の宣言と , それを用いた関数呼 び出しを以下のように記している。 static void showTabIe(int func(int, int), int n) 関数へのポインタを引数として渡す LiSt 1 : #include く stdio. h 〉 2 : 3 : static int 4 : add(int n, int m) 6 : return n 十 m; 8 : 9 : static int 10 : sub(int n, int m) 11 : { 12 : return n ー m; 13 : } 14 : 15 : static int 16 : mul(int n, int m) 17 : { return n * m; 19 : } 20 : 21 : static void 22 : showTabIe(int func(int, int), int n) 23 : { 24 : lnt i, J ; 25 : for (i = 1 ; i く n; + + i ) { 26 : for (j ー 27 : printft"hd", func(), j)); 28 : printf("*n") ; 29 : 30 : printf( ” *n ” ) ; 32 : } 33 : 34 : #define N 10 35 : 36 : int 37 : min(void) 38 : { 39 : showTable add, N ・ 40 : showTable sub, N ・ 41 : showTable mul, N ・ return 0 ; 関数へのホインタの配列を利用する LiSt 1 : #include く stdio. h> 3 : static int 4 : add(int n, int m) 6 : return n 十 m; 8 : 9 : static int 10 : sub(int n, int m) 11 : { return n ー m; 13 : } 14 : 15 : static int 16 : mul(int n, int m) 17 : { return n * m; 19 : } 20 : 21 : static void 22 : showTabIe(int func(int, int), int n) printf("%4d", func(i, ANSI C : mo 旧 107
応用 C 言語 C の道目箱 と考えていた。というのは , C 言語関連の書 籍の中て、何回か見た記憶があったからて、あ る。ところが , 実際に C 言語て、書いてみよう とするとなか・なかはかどらなかった。記憶 をたどって , いくっかの書籍を参照してみ ると , そのほとんどがアセンプリ言語て、書 かれていたのて、ある。 ソフトの発展性を考えると , て、きるだけ アセンプリ言語を使用せず , C 言語のみて、記 述したい。だいいち , 本誌は『 C MAG AZINE 』て、あると思い いろいろ試みたが やはりうまくいかず , 「 1cH て、インターバル タイマを設定するときの飛び先は , アセン プリ言語て、記述するしかないのて、あろう」と 結論を下しかけたとき , 参考文献 [ 3 ] 163 頁 のタイマ割り込みプログラムが目に入った このプログラムは , ストップウォッチ的 な機能をもったものて、 , 直接時刻を表示す るものて、はなかったが , すべて C 言語て、記述 されており , 非常に参考になった。今回 , リアルタイム表示関数を C 言語て、紹介て、きる のは , このプログラムのおかげて、ある。 ところて、 , 割り込み処理をプログラムの 中に組み込むと , いろいろな制約が生ずる。 それは割り込みハンドラから ( また割り込み が生じている間 ) , INT21H を含む標準ライ プラリを呼び出した場合 , その動作が保証 されないことなどて、ある。したがって , get char() や printf( ) などの入出力関係の関数は ほとんど使えない。本プログラムて、入力待 ちの状況を getchar( ) を用いないて、 , inkey98 ( ) を使用しているのはそのためて、あ List 1 0 行ー 8 0 ・ ) 011 ワ 3 っ・ 4 ・ - -0 《 0 0 ー 8 0 , 1 ワ乙っ 0 -4 ・戸 0 ^ 0 ー 8 0 、 1 っ乙 -4 ・ L.O ^ 0 ー 8 0 、 1 ワ 0 -4 ・ 0 ^ 0 ″ー 8 0110 乙っ -4 ・戸 0 ^ 0 0 ー 8 0 、 1 っ 0 っ 0 -4 ニ -0 《 0 叮ー 8 01 ーワ 0 っ -4 ・ ^ 0 ー 8 0 ) 0 、 1 ワ 0 っ 4 ・戸 0 ^. 0 ワ 0 ワ 0 ワ 0 っ 0 ワ朝っ 00 CO っっっっっっ 0 っ 0 4 ・・ 4 ・・ 414 ・ -4 ・ -4 ・ -4 , 4 ・ -4 ・ -4 ・′ 0 ′ 0 ′ 0 - -0 - -0 ) 戸 0 ^ 0 cD 《 0 《 0 《 0 ーーーーーー行ーー行ーー 8 8 8 8 8 8 8 8 8 8 0 0 0 0 0 0 0 BOTTOMOFF : getdver(xs) ; strcpy(dosver, "Ver. strcat dosver, (s) : pr i ntv " M S ー D O S コマンドメニュー " , WHI TE, 0 , 0 , の : ” ( コマンド選択 ) 1 / 6 " , WHITE , 40 , 0 , の : printv printv ” Menu v2. 20 " , WHITE , 69 , 0 , の : pr i ntv " 日付 : " , WH I T E , 6 5 , 3 , の : printv(" 時刻 :",WHITE,65,6,0); printv("MS-DOS:",WHITE,65,9,0); printv(dosver,WHITE,68, 10 , 0 ) : textkei3(), 1 , 79 , 19,SKY, 12 , 0 ) textkei3(0,21,79. 23,SKY, 12 , 0 textkei3(64,2,78, 11 , SKY , 12 , 0 strcpy()n [ 0 . menu, "dummy") : strcpy()n 0 . comm, "MORE く README. DOC") : strc y()n 0 . help, mn[0 . x ニ 2 : mnC0 . y = 2 : strcpy(mn[l . menu,"F1 マニュアルの補足説明 ( ご使用の前にお読み下さい " ) : strcpy()n 1 . comm, "A>MORE く README. DOC") : strc y()n 1 . help , " MS ー DOS を使用するあたっての注意事項を表示します " mn 1 . x ニ 2 ; mn 1 . y ニ 2 ; 。 cpy ( 00 [ 2 ヨ 0000 , " F2 アプリケーシンの登録 " ) : strc y ()n [ 2 ] . he I p, " 市販のアプリケーションを登録します mnC2f.x=2; mn[2] . y=4 ; strcpy ()n [ 3 ]. menu , " F3 フロッビイディスクの初期化 " ) : strcpy()n 3 . comm, "A>FORMAT % : strc y ()n 3 . he lp, " 指定ドライプのフロッビイディスクを初期化します mn [ 3 . x=2; mn [ 3 ] . y ニ 6 ; strcpy()n [ 4 . menu, strcpy()n [ 4 . comm, "A>") : strc y()n [ 4 . help, mnC4 . x = 2 : mnC4 . Y ニ 8 ; strcpy mn 5 . menu, strcpy mn 5 . comm, "A>") : strc y()n 5 . help, mn 5 . x ニ 2 : mn 5 . y ニ 10 : strcpy()n 6 . menu, strcpy()n 6 . comm,"A>") : strc Y(mn[6] . help, " mn 6 . x=2 : mn 6 . y= 1 2 : strcpy()n 7 . menu, strcpy()n 7 . comm,"A>"); strc y()n 7 . help, mn 7 . x=2 : mn 7 . y = 14 : strcpy()n 8 . menu, strcpy()n 8 . comm, "A>") : strc y()n [ 8 ] . help, " mn 8 . x ニ 2 ; mn 8 . y = 16 : strcpy()n 9 . menu, strcpy()n 9 . comm,"A>"); strc y()n [ 9 . help, mn[9 . x = 2 : mn[9 . y = 18 : clockon() : selbarms(mn, WHITE, WHITE, 9 , 1 , の : inkey98() ; clockoff() : BOTTOMON : CURON; ex i t ( の : 道具箱に追加された関数 clockon( ) 関数 (CM9104011 , List 2 ) 日付 , 時刻の表示の開始 [ 引数 ] なし 応用 C 言語 91
加・減・乗算の表の出力 / 関数へのポインタを使わない場合 List 呼び出すべき関数を「計算」 するための関数へのポインタ どのようなシチュエーションて関数への ポインタが用いられるのて、あろうか。それ は , 関数へのポインタの性質ーーー関数の間 接的な呼び出し一一を考えれば大体想像が っく。明白なーっの用途は , どの関数を呼 び出せばいいかを「計算」したい場合て、ある。 すなわち , あらかじめ定められた複数の関 数が存在して , そのうちのどれかーっを , ある計算式の値に基づいて選択的に呼び出 すような処理を行いたい場合がそれに相当 する。そのような記述を利用してコンパク トに表現したプログラムをえる過程を示し てみよう。 List 1 は 1 ~ 9 まて、の数の , お互いの和 , 差 , 積の表を作るものて、ある。積の場合に は , いわゆる九九の表となる ( Fig. 1 参照 ) 。 出力の形式は非常にぶつきらばうて、あるが , 関数へのポインタを理解するためのサンプ ルて、あるため , 出力に凝って必要以上にプ Fig. 1 1 ~ 9 までの和 , 差 , 積 2 3 4 5 6 7 8 910 3 4 5 6 7 8 91011 4 5 6 7 8 9 10 11 12 5 6 7 8 9 10 11 12 13 6 7 8 9 10 11 12 13 14 7 8 9 10 11 12 13 14 15 8 9 10 11 12 13 14 15 16 9 10 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 0 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 0 ー 1 ー 2 ー 3 ー 4 ー 5 ー 6 ー 7 ー 8 1 0 ー 1 ー 2 ー 3 ー 4 ー 5 ー 6 ー 7 2 1 0 ー 1 ー 2 ー 3 ー 4 ー 5 ー 6 3 2 1 0 ー 1 ー 2 ー 3 ー 4 ー 5 4 3 2 1 0 ー 1 ー 2 ー 3 ー 4 5 4 3 2 1 0 ー 1 ー 2 ー 3 6 5 4 3 2 1 0 ー 1 ー 2 7 6 5 4 3 2 1 0 ー 1 8 7 6 5 4 3 2 1 0 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 1 0 1 5 20 25 30 35 40 45 6 1 2 1 8 24 30 36 42 48 54 7 1 4 21 28 35 42 49 56 63 8 1 6 24 32 40 48 56 64 72 9 1 8 27 36 45 54 63 72 81 1 : #include く stdio. h> 3 : static int 4 : add(int n, int m) 6 : return n 十 m; 8 : static int 9 : 10 : sub(int n, int m) 11 : { 12 : return n ー m; 13 : } 14 : 15 : static int 16 : mul(int n, int m) 17 : { 18 : return n * m; 19 : } 20 : 21 : #define N 10 23 : int 24 : main(void) 26 : int i, J; / * たし算の表 * / 28 : for (i = 1 ; i く N; + + i ) { 29 : for (j 30 : prÆtf{' hd", add i, printf("*n ) ; 32 : 33 : printf("*n") ; 34 : 35 : / * 引き算の表 * / 36 : for (i = 1 ; i く N; + + i ) { for (j ー 38 : 39 : printf("*n ) ; 40 : 41 : printf( ” *n") : 42 : 43 : / * かけ算の表 * / 44 : for (i = 1 : i く N; + + i ) { 45 : for (j = 46 : printf("hd", 01 i printf( ”新, ) ; printf( ”” ) ; 50 : 51 : return 0 ; 52 : 53 : } ログラムを複雑にしたくないのて、ご容赦願 にしよう。その関数は showTabIe と名づけ こて、 , 三つの表を作るわけだが , る。 showTable はその時々に応じて加・減 ・乗のうちの任意の計算をしなければなら それらの違いは単に足し算を行うのか , 引 き算を行うのか , あるいはかけ算を行うの ない。このために関数へのポインタを利用 かという点にしかすぎない。ところが List 1 する。結論からいえば List 2 のようにコーデ て、はこれを main の中て、類似のコードを単純 イングすればよい に繰り返して記述しているのて、ある。 main の中て、類似のループを 3 度書く代わり に , showTabIe を 3 回呼び出すようにしたわ そこて , この「表を作る」部分を main から 独立させて一つの関数として取り出すこと けて、ある。そうして , それぞれの呼び出し 106 C MAGAZINE 1991 6
ワンポイント プログラミング 講座 List 1 List 2 (*date). tm_mday 1 13 : 114 : } 115 : / * - 116 : / * 休日判定 ( 0 : 休日でない 1 : 指定休日 117 : / * 1 18 : / * 2 : 祝祭日 & 振替休日 119 : / * ー holiday(struct tn *date) 120 : i nt 121 ・ 122 : int day, i, J; 123 : FILE *fp; 124 : if (_horiday-table-read 125 : if((fp = fopen("horiday. tbl" 126 : for (i i 十十 ) 127 : i く 20 & & j = fscanf(fp, "Xd", &(-hoIytb12[i])); 128 : J 129 : fclose(fp) : 130 : 131 : 132 : horiday-table-read : YES; 133 : 134 : 135 : 136 : 137 : 138 : 139 : 140 : 141 : 142 : 143 : 144 : 145 : 146 : 147 : 148 : 149 : 150 : 151 : 152 : 153 : 154 : 155 : 156 : 157 : } 158 : / * ー 159 : / * 日数計算 long daycount (struct tn * f 「 om. struct tm *tO) 161 : 162 : 163 : long days; 164 : -mjd(to) -mjd(from) : 165 : days if (days > = の { 166 : return(days 十 1 い : 167 : 168 : 169 : return(days 170 : } printf( ” i くニ dd; i + + ) { ( i for date. tm_mday holiday(&date) : if (k ! = の printf("YxIb[31n"); : の printf("Yxlb[31m") : else if (j = 6 ) printf("Yxlb[34m") : else if (j printf("X3dYxlb[0m" = の putchar('Yn ・ ) : -4- -0 《 0 0 ー 8 0 ・ 1 つむつ 0 4 ・戸 0 7 ー っ 0 06 っ 0 っ 3 りもワ CO っ 0 っっっ putchar( ・ Yn ・ ) : List 3 calen. C (*date) . tm_mon + 1 : i * 100 + (*date). tm_mday; day (i for 0 : i く number_of_horidays & & day if (day -holytbll[i]) return(2) : ( i 0 : i く number of_horidays & & day for if (day _holytb12[i]) return(l); -mjd(date) : if ((*date). tm_wday - if ((*date) . tm_mday ! day - 引 se { (*date). tm_mon; i * 100 + _mday[i-l]; d ay for ( i : 0 : i く number of-horidays & & day i f (day -holytbll[i]) return(2) : 1 : # i ncl ude く std i 0. h> 2 : # i nc lude く ca l. h > yesterday. tOda y : 6 : ー ong 7 : struct tm date, d2; 8 : 9 : 1 ヶ月分のチェック 13 : void check(int m) 15 : int 17 : date. tm_mon mday [ 田 -1 ] : 18 : for (i 19 : 20 : date. tm-mday mjd(&date) : 21 : tOday if (today yesterday ! = 22 : printf("X4d/X02d/X02d:mjd ニ % 届 \ n ". 23 : 1900 + date. tn_year, date. t 第 - 第 on + し date. tm_mday. tOday) : 24 : 25 : 26 : yeste rday tOday : 27 : -jdate(today. &d2) : 28 : if ((date. tm_year ! = d2. tm-year)&&(date. t 田一田 on ! = d2. tm-mon) 29 : & & (date. tm_nday ! ニ d2. tn-mday)) { 30 : printf("mjd : % 届 : % 4d / % 02d / % 02d : % d \ n " today, 1900 十 date. tm-year, 31 : date. tm-non + date. tm-mday, d2. tm-year) : 32 : 33 : 34 : 36 : 37 : / * 38 : / * チェックを 1 年分繰り返す 39 : / * - 40 : VOid year(int y) 42 : 43 : 44 : date. tm_year - for (m 45 : check 価 ) : 46 : 48 : } 50 : / * ~ 51 : / * テスト : メイン関数 52 : / * ー 53 : void main(void) 54 : { 55 : int y; 56 : / * 1900 年 * / 0 : date. tn-year = 1 月 * / 58 : 0 : date. t 第 - 第 on 59 : 1 日 * / date. tn-nday njd(&date) - 1 : / * の前日 * / yes terday printf(" チェック開始 Yn"); 62 : fo 「 (y : 0 : y く 200 : y + + ) { / 事 1900 - 2100 年 * / 63 : year(y) : 64 : 65 : 66 : p 「 intf ( ”チェック終了 \ n つ : -holytbll[i] : i 十十 ) -hoIytb12[i] : i 十十 ) / * 振替休日 ニ l) { / * 前日を計算 * / i 十十 ) -holytbll[i]; return(N0) : check. c List 2 = 12 : 十十 ) : #include く stdio. h 〉 2 : # i ncl ude く std ⅱ b. h > 3 : # i ncl ude く ca l. h 〉 4 : struct tn date; 6 : / * カレンダ作成 : メイン関数 8 : VOid nain(int Char **av) i nt i , j, k. dd : 12 : printf("usage: calen 91 5 ・ 14 : return; atoi(av[l]) : date. tn_year : date. tn_non atoi (av[2]) date. tn_nday -mjd(&date) : 19 : -mday (date. い - 田 on ] : 20 : dd = 22 : : date. tn_wday; J for (i 23 : ・ 1991 年 5 月のカレンダ出力 \ n つ : 年月日 135 ワンポイントプログラミング講座
LISt 1 379 : / * 303 : 304 : 305 : } 306 : 308 第 309 : { 310 : 311 : 312 : 313 : 314 : - 315 : 316 : 317 : 318 : 319 : 320 : 321 : 322 : 323 : 324 : 325 : 326 : 327 : } 328 : 330 : 331 333 : 334 : 335 : 336 : 337 : 338 : 339 : 340 : 341 : 342 : 343 : 344 : 345 : 346 : 347 : 348 : 349 : 350 : 351 : 352 : 353 : 354 : 355 : 356 : 357 : 358 : 359 : 360 : } 361 : 362 : 363 : 364 : 365 : 366 : 367 : 368 : 369 : 370 : 371 : 372 : 373 : 374 : 375 : 376 : 377 : } 378 : COMMANDLINK->next = N 乢し : TARGETCOUNT + + : ・ VO i d set_command ( char * ⅱ ne 307 : / * コマンドラインの登録 char *cp : List 1 MAKE 処理 381 : #define MAXNEST 8 int NESTLEVE し while( *line くニ if( ! *line + + ) if( VERBOSE ) printf( " n 十十 ) return : by [%s]Yn". line ) : NEST し EVE し for( n ニ 0 : n く TARGETCOUNT; maketargetf ⅱ e ( n ) : if(( cp ニ malloc( strlen( line ) + 1 ) ) strcpy( cp. line ) : COMMANDLINK->name = cp; error( make ⅱ st ニ NU しし ) "MEMORY / COMMAND" ) : int maketargetfile( int number ) 397 : struct _makelist *link; 398 : char *makename, vertab[ MAXNEST + 1 ] : COMMANDLINK->next ニ malloc( sizeof( struct COMMANDLINK = COMMAND い NK->next; COMMANDLINK->name : NU しし : COMMAND い NK- 〉 next ニ NU しし : 329 : / * マクロを拡張する * / int expand_macro( char *cp, char *sp ) 332 : char cc, *mp, macroname[ 256 ] : switch( *sp ) *cp 十十 = i f ( ( cc while( *SP ) sp 十十 : CC . = *SP 十十 ) ! ニ while( ( cc = *SP + + ) ! = mp = macroname : if( ! cc ) error( "BAD MACRO" ) : = NU しし ) error( "BAD MACRO" ) : if( cp cp = convert_macro( cp, *mp : 0 : *mp 十十ニ macroname ) : break : return 0 : *cp = 0 : 380 : 382 : 383 : 385 : { 386 : 387 : 388 : 389 : 390 : 391 : 392 : 393 : } 394 : 395 : 396 : { 399 : 400 : 401 ・ 402 : 403 : 404 : 405 : 406 : 407 : 408 : 409 : 410 : 411 : 412 : 413 : 414 : 415 : 416 : 417 : 418 : 419 : 420 : 421 : 422 : 423 : 424 : 425 : 426 : 427 : 428 : 429 : 430 : 431 : 432 : 433 : 434. 435 : 436 : 437 : 438 : 439 : 440 : 441 : 442 : 443 : 444 : 445 : 446 : 447 : 448 : 449 : 450 : 451 : 452 : 453 : 454 : int n; 384 : void makefiles( void ) int n; uns i gned long targettod : ・ uns i gned long dependtod : if( VERBOSE ) if( NEST し EVE し ) memset( vertab, vertab[ NESTLEVE し ] link = &TARGET[ number ] : makename link->name; if( VERBOSE ) ・ Yt ・ , NESTLEVE し ) : printf( "%sTARGET#%02d Y"%sY"Yn". vertab, while( link->next ! : NU しし ) ⅱ nk link->next; if( VERBOSE ) printf( "%sdependent Y"%sY"" vertab, number 十 1 , makename ) : link->name ) : if( ( n istargetfile( link->name ) ) く 0 ) if( VERBOSE ) printf( ” Yn ” ) : e ー se if( VERBOSE ) printf( " is TARGET#%02dYn ” , n + 1 ) : if( NEST し EVE し〉 : MAXNEST ) error( " T00 MANY NESTING" ) : char * convert_macro ( Char *cp, int n; char *sp : for( n = 0 : n く MACROCOUNT; Char n 十十 ) *name NEST し EVE い + : maketargetfile( n ) : NEST し EVE し一 link : &TARGET[ number ] : makename link->name; if( VERBOSE ) pr i ntf ( "%sMAKI NG#%02d \ " %sY " Yn". vertab, number + l, targettod = getf ⅱ ( makename ) : while( link->next ! : NU しし ) link ー link->next: dependtod = getfiletod( link->name ) : if( ! dependtod ) error2( "NOT FOUND - > " i f ( targettod く dependtod ) update ( number ) : targettod ニ getf ⅱ etOd ( makename ) : makename ) : if( ! strc 叩 ( MACRO[ n ]. name. name ) ) sp ニ MACRO[ n ]. line: while( *CP = *sp 十十 ) cp 十十 : return cp : return NU しし : link->name ) : i f ( ( targettod く dependtod ) Ⅱ ( ! targettod ) ) error2( "NOT UPDATE - 〉 " , makename ) : 56 C MAGAZIN E 1 1 6
シャープ X クループ lnformation from Compiler Makers 前回に引き続き , CCompilerPRO ー 68K ( XC ) のバージョンアップによ る XC ver. 1.01 から ver. 2.0 への 変更点 ( おもに ANSI C 準拠にとも なう変更点など ) をご紹介します。 プログラミング時の参考にしてく ださい 局所的な配列 , 構造体 , 共用体の初期化 内部レベルの変数宣言に使用す る auto 記憶クラス指定子によって 宣言された配列 , 構造体 , 共用体 ( 内部レベルて、記憶クラス指定子を 省略した場合は , すべて auto 変数 として扱われます ) の初期化が XC ver. 2.0 においては可能になりまし 32K バイト以上の auto 変数の使用 XC ver. 1.01 て、は , 32K バイト 以上の auto 変数を使用するとコン パイラて、 $AUTO 変数が 32Kbytes を越えました〃というワーニングの 後 , アセンプラてヾ over flow error というエラーが発生します。しか し , Ver. ・ 2.0 て、は , 32K バイト以上 の auto 変数の使用が可能になりま ただし , 32K バイト以上の auto 変 数を使用する場合 , コンパイル時 に / Na スイッチをつけることが必要 てす。 このスイッチを省略したときに は , 、、 aut0 variable 32KbYtes over 〃のエラーが発生します。 なお , 32K バイト以上の auto 変数 を使用したプログラムを / Na スイツ チをつけてコンパイルした場合に 浮動小数点演算 ライプラリの搭載 あらたに浮動小数点演算ライプ ラリが追加されました 0FLOATF NC. L と FLOATEML. L の 2 種類て、 す。 FLOATFNC. L を使用した場合 は , デバイスドライバとして登録 している FLOAT * . X(FLOAT2. X など ) を使用することにより , プ ログラムのサイズを小さくするこ とがて、きます。ただし , FLOATF NC. L を使用して作成された実行フ アイルは , FLOAT* . X が組み込 まれていなければ実行て、きません。 FLOATEML. L を使用すると , FLOAT * . X が組み込まれていな くても実行可能なファイルが作成 されます。 FLOATEML. L 自身が 浮動小数点演算ルーチンをもって おり , それを利用して演算を行う ためて、すが , プログラムのサイズ は大きくなります。 コンパイルするとき , デフォル トて、は FLOATFNC. L が使用され ます。浮動小数点演算ライプラリ として FLOATEML. L を使用する ように変更する場合は , / Nf スイツ チをつけてコンパイルします。 プリプロセッサ命令 # 演算子と # # 演算子のプリプロセ ッサ命令を新たに導入しました。 プリプロセッサの引数に , # , # # の オペレータを使用することにより トークンの加工を行うことがて、き ます。 、、 # トークン〃て、トークンの文字列 化を行い , 、、トークン # # トークン〃 て、トークンを連結します。たとえ ば , #define sample(str) #str 、、コ ンパイラ〃と定義されたマクロは次 のように展開されます。 sample(XC) →、、 XC 〃ヾコンパ イラク→、、 XC コンパイラク , また #define sample2(), b) a##b と定 義されたマクロは sample2(ABC, DEF) → ABCDEF と展開されます。 ■ scanf 関数の注意点 scanf 関数の format string 変換 書式中に使用した空白文字は読み とばされ , 次の入力処理を行いま 3 : main() 2 : 1 : #include く stdio. h> List 1 C CompiIer PPO-68K ver. 2. O が行えます。 3 のように一度に複数のデータ入力 正することが必要て、す。また , List 理を行いません。 List 2 のように修 るため , 期待したとおりの入力処 は空白文字が変換書式の最後にあ す 0List 1 のサンプルプログラムて、 タブ ) , 、、 \ f 〃 ( 改真 ) , 、、 \ r ク ( 復帰 ) て、 空自文字とは , 空白文字をつけてはいけません。 入力の場合は , 変換書式の最後に す。そのため続けて 1 データだけの 7 : 8 : 9 : 10 : char c; i nt i : scanf("%cYn ” ,&c) : printf("%cYn" scanf("%dYn",&i) : printf("XdYn", i) : List 2 1 : #include く stdio. h> 2 : 3 : ma i n ( ) 5 : 6 : 7 : 8 : 9 : 10 : Char c; i nt i : printf("XdYn". i); printf("%cYn",c); List 3 1 : #include く stdio. h> 2 : 3 : ma i n ( ) 5 : 7 : 8 : char c; int i; scanf("%cyn%d",&c,&i); printf("%cYnXdYn",c,i); も , す。 154 ワーニングメッセージは出ま 1 1 6 C M AGAZIN E
明解 )IC 言ロロ 入門講座 す。現在着目している点数 ( tensu [ i ] ) が 50 未 満て、あれば , 次の繰り返しへと強制的にプ ログラムの流れを変えるのて、 , 16 , 17 行が スキップされることになります。すなわち 16 , 17 行は tensu[i] が 50 以上の場合だけ実行 されます。 16 行目て、点数を表示し , 17 行目 て、人数 ( ninzu ) をインクリメントします。 19 行目て、は ninzu には 50 点以上の学生数が人っ ていますのて、 , それを表示します。 【間題 2 】 List 2 のプログラムと同じ 動作をするプログラムを continue 文を使 用せずに書け ( goto 文を使うこと ) 実は List 2 のプログラムは continue 文や goto 文を使わなくても記述て、きます。 con tinue 文を使わずに書いたプログラムを List 3 に示します。 15 , 16 行目は tensuCi] が 50 点 以 E の場合だけ実行します。 break 文や continue 文は繰り返しの流れを 強制的に変えるものて、す。これは繰り返し の直後へのジャンプ ( got 。 ) および繰り返しの 最初 ( 次の繰り返し ) へのジャンプと同じこ とて、す。 goto 文はプログラムの読みやすさ を低下させるものだと前回説明しました。 break 文や continue 文も同様にプログラムの 可読性をそこないます。 50 点の学生の学籍番号を求める List 1 : # i nc I ude く stdio. h> 2 : 3 : #define MAX 4 : 5 : int main(void) tensu[MAX] : 7 : int 8 : int 9 : for ( i = 0 : i く MAX : i + + ) &tensu Ci]) : scanf( ” %d ” for ( i ニ 0 : i く MAX : i + + ) if (tensu[i] ニ 5 の break; = MAX) puts ( " 50 点の学生はいません " ) : e 1 se printf ( " 50 点の学生の学籍番号は % d です " , i + 1 ) : return(0) : 19 : } / * 人数 * / 6 / * テストの点数 * / / * 添え字用の変数 * / / * MAX 人の点数を読み込む * / 50 点の学生の点数を表示し人数を求める LiSt 1 : # i nclude く stdio. h> 2 : 3 : #define MAX 4 : 5 : int main(void) tensu[MAX] : 7 : int 8 : int n 1 nzu : 9 : int for ( i ニ 0 : i く MAX : i + + ) scanf("%d" &tensu[i]) : ninzu for ( i = 0 : i く MAX : i + + ) { 14 : if (tensu[i] く 5 の continue; printf("%d 番 : %d 点 Yn", i + 1 , tensu[i]); n i nzu 十十 : printf ( " 50 点以上の学生は % d 人です " , ninzu); return(0) : / * 人数 * / 6 / * テストの点数 * / / * 人数 * / / * 添え字用の変数 * / / * MAX 人の点数を読み込む * / 重要 List 2 を書き直したプログラム List goto 文と同様 , break 文や continue 文 はなるべく使わない さて goto 文 , break 文 , continue 文および return 文はプログラムの流れを強制的に飛び 越すものて、あり , これらをまとめて飛び越 し文 (jump statement) と呼びます (Fig. 3 ) 。 こまて、解説した式文 , 複文 , 繰り返し 文 , 飛び越し文などをまとめて文と呼びま す (Fig. 4 ) 。複文が「ひとつの文」となったよ うに , 繰り返し文 , 飛び越し文なども「ひと つの文」となることはもう説明の必要はない て、しよう。 さて List 3 と List 2 はもう 1 か所違うとこ 1 : # i nc lude く stdio. h> 2 : 3 : #define MAX 6 4 : 5 : int main(void) tensu[MAX] : 7 : 8 : nlnzu 9 : 20 : / * 人数 * / / * テストの点数 * / / * 添え字用の変数 * / / * MAX 人の点数を読み込む * / int int int for ( i = 0 : i く MAX : i + + ) scanf("%d" &tensu[i]) : for ( i ニ 0 : i く MAX : i + + ) { if (tensu[i] 〉 = 50 ) { printf("Xd 番 . Xd 点 Yn", i + 1 , tensu[. i)); mnzu 十十 : printf ( " 50 点以上の学生は % d 人です ninzu); return(0) : 明解 ANSI C 言語入門講座 115