プログラムの開発を効率化するための技を修得してゆこう.
今回の学習内容は,ソースコードの可読性・保守性の向上のため...
要するに,ソースコードを書き易く・読み易くします.
定数マクロは,もう何度も書きました.
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個だけのコピーで済むので, 処理時間もメモリ使用量も少ない.