プログラムの開発を効率化するための技を修得してゆこう.
今回の学習内容は,ソースコードの可読性・保守性の向上のため...
要するに,ソースコードを書き易く・読み易くします.
定数マクロは,もう何度も書きました.
cmd.c:
#include <stdio.h> #include <string.h> #define BUFLEN 256 // 定数マクロの定義 int main(void) { char cmd[BUFLEN]; FILE *fp = stdin; while (1) { printf("コマンド > "); fgets(cmd, BUFLEN, fp); // 定数マクロの利用 if (strcmp(cmd, "quit") == 0) break; } return (0); }
コンパイルコマンドを実行すると, 前処理としてソースコード内のマクロ BUFLEN の部分が数値 256 へ置き換えられ, その置換後のソースコードがコンパイルされる.
コンパイルし実行しよう. "quit"の入力で終わるハズだが...終わらない...なぜだー??
間違いを探し出そう.
... int main(void) { ... while (1) { printf("コマンド > "); fgets(cmd, BUFLEN, fp); printf("cmd:[%s]\n", cmd); // デバッグ用コード // 変数(入力文字列)を表示してみる,i.e. printfデバッグ if (strcmp(cmd, "quit") == 0) break; } ... }
printfデバッグによって原因が判明... 配列cmd 内に改行文字'\n' も代入されている.
デバッグ用コードを削除して,再度コンパイルするのかい? 長いプログラムなら, 何度も書き直すのは面倒臭いぞ.
フラグマクロを利用し, デバッグ機能の on/off を簡単に切り替えられるようにします.
... #define DEBUG // フラグマクロの定義 ... デバッグモード on // #undef DEBUG // マクロ定義を解除 ... デバッグモード off // または...// #define DEBUG// コード内では未定義とし,コンパイル時に切替 /* $ cc -DDEBUG ... ソースコード内だけでなくコンパイル時にもマクロを定義できる. デバッグ作業の前後でコードの書き換えが不要になるよ. */ #define BUFLEN 256 int main(void) { ... while (1) { printf("コマンド > "); fgets(cmd, BUFLEN, fp); #ifdef DEBUG // ここから...デバッグ用コード printf("cmd:[%s]\n", cmd); #endif // ...ここまで if (strcmp(cmd, "quit") == 0) break; } ... }
#ifdef DEBUG 〜 #endif の間のコードは, DEBUG が定義されている場合だけ,有効となる.
デバッグ以外の動作は変わりません.
コードの間違いを修正しよう.
... // #define DEBUG // #undef DEBUG #define BUFLEN 256 int main(void) { char buf[BUFLEN]; // 行文字列('\n' あり) char *cmd; // コマンド文字列('\n' 除去) FILE *fp = stdin; while (1) { printf("コマンド > "); fgets(buf, BUFLEN, fp); // fgets()では改行文字'\n'も配列に格納するのでした... cmd = strtok(buf, " \t\n"); // トークン分割 // strtok()で'\n'等を除去 #ifdef DEBUG // ここから...デバッグ用コード printf("cmd:[%s]\n", cmd); #endif // ...ここまで if (strcmp(cmd, "quit") == 0) break; } return (0); }
問題は解決できた. しかし,strcmp()というかCの文字列は使いづらいぜ...
文字列処理を使い易く,C言語自体を改変してしまおう.
... // 引数付きマクロ #define StrEql(s1, s2) (strcmp(s1, s2) == 0) // 関数マクロ:文字列比較の条件式の定義 /* この程度なら,関数として定義してもよいが, マクロの方が実行時間・メモリ使用量が少なく済む. (というか,増えずに済む.) */ #define IfStr(s1, op, s2) if (strcmp(s1, s2) op 0) // 制御構造マクロ:文字列比較専用のif文の定義 #define IfCmdIs(s) if (strcmp(cmd, s) == 0) // 制御構造マクロ:cmd比較専用のif文の定義 /* 引数付きマクロでは,Cに新しい制御構造さえも追加できる. 引数には,変数・定数だけでなく,演算子・関数なども利用できる. ただし,使いすぎると他人には理解不能なコードとなりがち. 「過ぎたるは及ばざるが如し」 */ int main(void) { ... while (1) { ...// if (strcmp(cmd, "quit") == 0) break;// 使いづらいので... // マクロによる改変例 // コンパイル時にマクロ↓ はどれでも元のコード↑ に展開される if (StrEql(cmd, "quit")) break; // 意図を明確化... // IfStr(cmd, ==, "quit") break; // 意図を更に明確化... // IfCmdIs("quit") break; // 短く } return (0); }
マクロは,関数とは異なり,実行時に計算してくれる訳ではなく, コンパイル時にソースコードの字面を置き換えるだけなので... 意外なコードに展開され,想定外の結果となる場合がある.
// 逆数を求める関数マクロ #define Inv(x) 1.0/x // イマイチな定義方法 /* 使用例 ⇨ 展開結果 ⇨ 実行結果: Inv(2.0) ⇨ 1.0/2.0 ⇨ 0.5 ですが何か? Inv(2 + 3) ⇨ 1.0/2 + 3 ⇨ 3.5 ...えぇえー??? 1/(2+3) = 1/5 = 0.2 じゃねーの? Inv(1/2) ⇨ 1.0/1/2 ⇨ 0.5 ... 逆数の逆数なので 2 に戻るハズだ 1/Inv(2) ⇨ 1/1.0/2 ⇨ 0.5 ... 2 に戻ってください... */ // #define Inv(x) (1.0/(x)) // 改善案
前回作成の pdcl.c に制御構造マクロを導入せよ.
未完成です... とりあえず こちら (昨年度の授業内容)を参考に学習してください.
前回作成の pendraw.c に構造体を導入せよ.
... typedef struct { int w, h; // 元の変数 w, h char *pixel; // 元の変数 img } Img; // 画像の構造体 typedef struct { int x, y; // 元の変数 x, y int state; // 元の変数 pen } Pen; // ペンの構造体 ... void FillImg(Img *img, char c) { int x, y; for (y = 0; y < img->h; y++) { for (x = 0; x < img->w; x++) { img->pixel[img->w*y + x] = c; } } } ... void Game(...) { ... Pen pen; // 変数 pen,x,y を構造体1個に統合 Img img; // 変数 img,w,h を構造体1個に統合 int w, h; // 画面サイズでもあるので残しておく(img と重複) ... getmaxyx(stdscr, h, w); ... pen.state = 0; // pen = 0 pen.x = w/2; // x = w/2 pen.y = h/2; // y = h/2 ... img.w = w; img.h = h; img.pixel = (char *)malloc(sizeof(char)*w*h); if (img.pixel == NULL) ...; FillImg(&img, ' '); LOOP: while (1) { ... Draw(&img, &pen); erase(); ShowImg(&img); ShowPen(&pen); ... Ctrl(&pen, &img, key) ... } ... free(img.pixel); ... } ...
構造体を関数呼出の引数・戻り値として使う場合, 常に,参照渡し(ポインタ)とするとよい. 値渡しでは,構造体のすべてのメンバ変数のコピーのために時間を浪費する. 参照渡しでは,構造体の先頭アドレス1個だけのコピーで済むので, 処理時間もメモリ使用量も少ない.