情報処理II

情報処理Ⅱ
2007年1月19日(金)
本日学ぶこと

前処理指令
マクロ

問題



サイコロを何度も振って,全ての目が最低1回出るまで,何回
振らなければならないか?
• 最小6回,上限なし(∞回?)
• 欲しいのは現実的な値
100面のサイコロだったら?
2
前処理とコンパイル(1)
ソースファイル
(前処理前)
前処理
ソースファイル
(前処理後)
コンパイル
アセンブル
オブジェクト
ファイル

リンク
実行ファイル
前処理・コンパイル・アセンブル・リンクの各処理は通常,コ
ンパイラ(ccなど)が一手に引き受ける.
3
前処理とコンパイル(2)

前処理は,



狭義には,「コンパイルに先立って行われる処理」であり,した
がってコンパイルとは別
広義には,ccでコンパイルすれば自動的に処理してくれる,と
いう意味でコンパイル作業の一部
前処理のコマンド(プリプロセッサ)は,cpp

Cの前処理以外にも使用可能
4
前処理指令
(Preprocessing directive)

マクロ定義(#define)





「プリプロセッサ
指令」ともいう
オブジェクト形式マクロ ⇒「定数」の定義
関数形式マクロ ⇒「関数もどき」の定義
マクロとは…コンピュータ関連の作業において,複数の機能や
意味をまとめて扱えるようにしたもの
ソースファイルの取り込み(#include) ⇒12月8日参照
条件付きコンパイル(#if ... #endif など)
5
オブジェクト形式マクロ(1)

語の置き換えを行う.



要素数は,コンパイル時に
#define 置換対象 置換内容
評価可能な定数式なので,
#define WORD_SIZE 6
問題なし
と記述すると,それ以降
int a[WORD_SIZE]; は int a[6]; と同じ意味になる.
プログラム修正により変わり得る定数値があるときに,よく用
列挙型のほうがいいかも
いられる.


配列の上限値,他と区別する値など.
うまく使うことで,定数値を変えるときのプログラム修正箇所を
少なくできる.
6
オブジェクト形式マクロ(2)

注意点



前提: #define WORD_SIZE 6+1
単純に置き換える.
• int a[WORD_SIZE * 2]; は,int a[6+1 * 2];
に置き換えられる(意図した動作ではない).
⇒ #define WORD_SIZE (6+1) とすればよい.
語のみを置き換える.
• print_WORD_SIZE() のような「語の一部」や,
printf("WORD_SIZE"); のような「文字列中の語」は,
置き換えない.
7
オブジェクト形式マクロ(3)

注意点(続き)



予約語も置換可能.
• #define char signed char は文法上問題ないが,
よい書き方ではない.
typedef signed char schar; とすべきである.
置換内容のない名前も定義できる.
• #define DEBUG
末尾にセミコロンをつけない.
• #define WORD_SIZE 6; は(たいていの場合)間違い.
8
関数形式マクロ(1)

オブジェクト形式マクロとほぼ同じ書式.
置換対象に「(…)」をつける.



#define pint(x) printf("%d\n",x) に対して,
pint(a+1); は printf("%d\n", a+1); に置き換え
られる.
複数の引数をとることもできる.そのときは,置換対象の各引
数をカンマで区切る.
カッコ内に何も書かなければ,引数なしの関数形式マクロが定
義される.
9
関数形式マクロ(2)

注意点


単純に置き換える.
• #define mul(x, y) x*y に対して,
z=mul(6+1,2); としたとき,z=14ではなくz=8となる.
⇒ #define mul(x, y) ((x)*(y)) のように,
置換対象の引数と,評価式全体にカッコをつける.
置換内容の中に,引数を2箇所以上書くことができる.
その回数だけ置換される.
• #define triple(x) ((x)+(x)+(x)) に対して
b=triple(++a); と書くと,
b=((++a)+(++a)+(++a)); となる.
10
関数形式マクロ(3)

置換内容の中で「#引数」と書くと,引数を文字列にできる.


#define pint(x) printf(#x " = %d\n", x)
に対して,pint(a+1); は
printf("a+1" " = %d\n", a+1); に置き換えられる.
通常の関数定義では,変数名を
引数にとってその文字列を得る
「文字列リテラルの連結」に
ことはできない.
より,これは
printf("a+1 = %d\n", a+1);
と同じとなる.
11
関数か関数形式マクロか

関数…「機能」を正確に表現したいとき






例:int square_int(int x) { return x * x; }
引数や戻り値の型に制約される.
関数呼び出しのオーバーヘッドがある.
ローカル変数や制御文を活用できる.
実引数が++aなどのときも,その評価は一度だけ.
マクロ…「機能」を簡便に表現したいとき





例:#define square_int(x) ((x) * (x))
引数や評価式に型はない.
(狭義の)コンパイル前に展開され,オーバーヘッドは少ない.
ローカル変数や制御文は使用しにくい.
(マクロ利用側の)引数は,置換内容の回数だけ評価される.
12
前処理指令と空白・コメント
...不可
...必須
# define pint( x ) printf ( #x " = %d\n" , x )
...任意

一つの前処理指令は,1行で書かなければならない.ただし,



行末に「\」を置くことで,複数行で書ける.
関数形式マクロの場合,括弧の途中で改行できる.
前処理指令の中でコメント(/* */ もしくは //)を書くと,
前処理時に空白文字に置き換えられる.
13
サイコロ問題

仕様




①
目の数は6つ.ただし変更の可能性あり.
サイコロの目は1~6のいずれかとする.
出た目はその都度出力する.
全ての目が出たら,何回振ったかを出力し,終了する.
②
③
④
⑤
⑥
⑦
⑧
⑨
⑩
⑪
14
サイコロ問題の考え方(1)

サイコロの振り方


ライブラリ関数のrandを用いる
• あらかじめ #include <stdlib.h>
• int a = rand(); により,aにはint型の値が一つ代
入される.この値は,ある範囲の中でどの値も等しい確率
で選ばれる(一様乱数).
• int spot = rand() % 6; で,spot には 0~5
のいずれかが代入される.
• int spot = rand() % 6 + 1; とすればいい!
擬似乱数のため,何度実行しても同じ目が出る.これを変える
には,ライブラリ関数のsrandを呼び出して,適切な値の種を
与えればよい.
15
サイコロ問題の考え方(2)

「全ての目が出る」とは?


int spot_counter[SPOT_MAX + 1];
• 目ごとに出た回数
• 目が出たら,spot_counter[spot]++;
• 特定の目がまだ出ていないかの判定は,
if (spot_counter[spot] == 0)
• 全ての目が出たかを,この配列変数だけで判定することは
できるが,非効率
int counter_unfound = SPOT_MAX;
• まだ出ていない目がいくつあるか
• 一つずつ減らしていき,0になれば「全ての目が出た」
16
サイコロ問題で定義したマクロ

#define SPOT_MAX 6



目の数を表す,オブジェクト形式マクロ
ここ以外で「6」と書かない.これにより,目の数が変わるような
プログラムにも対処しやすい.
#define cast_dice(spot_max) (rand() %
(spot_max) + 1)



サイコロを1回振って,出た目を返す,関数形式マクロ
呼び出し元では int spot = cast_dice(SPOT_MAX);
であり,これは
int spot = (rand() % (SPOT_MAX) + 1); になる.
「cast_dice(100*5)」のように使うことも可能
17
条件付きコンパイル(1)

#if 定数式
…
#endif


定数式が真のときに「…」を残し,そうでなければ「…」を捨てる.
定数式の評価や「…」の取捨は,前処理時(≠実行時)に行われ
る.
18
条件付きコンパイル(2)


「#if 定数式」に代えて,「#ifdef 名前」や
「#ifndef 名前」も利用可能.
「#else」や「#elif 定数式」も記述可能.
条件付きコンパイル
#if 条件式1
…
#elif 条件式2
…
#else
…
#endif

参考: Cのif文
if (条件式1) {
…
} else if (条件式2) {
…
} else {
…
}
条件付きコンパイルは入れ子にできる.
19
ヘッダファイルで条件付きコンパイル

ある環境の /usr/include/stdio.h より


#ifndef _STDIO_H
#define _STDIO_H 1
…
#endif /* !_STDIO_H */
この記述により,複数のファイルに #include <stdio.h>
があっても問題なく動作する.
• ヘッダファイルの中で,別のヘッダファイルをインクルードす
ることがある.
• _STDIO_H を定義して「…」を獲得するのは,1回だけ.
20
次に学ぶこと

ファイル入出力

問題


これまでそのプログラムを何回実行したか,記録できる?
ソースファイルの先頭に,右揃えで行番号をつけて出力でき
る?
21
ファイルとは


補助記憶装置に保存する単位となる,データの集まり.
プログラムが終了しても,内容が保持されるデータ構造.




(比較)変数の値はメモリ上にあるため,プログラム終了時に破
棄される.
コンピュータの電源を切って入れ直しても,保持されていること
が多い.
ストリームと呼ばれるバイト列として,読み書き可能.
バイト列とは?


char配列で表現できるデータ構造.
'\0' で終わるものではなく,途中に '\0' があってもいい
という点が,文字列と異なる.
22
実行回数管理プログラム

仕様


./count を実行すると,count.txt というファイルに実行
回数が保存される.
考え方


実行後,count.txt があれば,読み出して,それまでの実
行回数を獲得する.なければ「0回」とする.
実行回数を1増やして,出力するとともに,count.txt に書
き込む.
23
ファイルポインタ

Cでファイルを操作するには,ファイルポインタを使用する.


stdio.h で定義されているFILE型のポインタ.
例: FILE *fp;
fp
FILE
オブジェクト
24
ファイル操作のライブラリ関数(1)

FILE *fopen(char *path, char *mode);


ファイルを開き(プログラムから使えるようにし),ファイルポイン
タを返す.
• 第1引数はファイル名(「パス名」ともいう).
• 第2引数が "r" なら,読み込み専用で開く.
• 第2引数が "w" なら,書き込み専用で開く.
char *fgets(char *s, int size, FILE
*stream);

「size-1バイト」,「改行文字まで」,「ファイルの終わりまで」の
うち最小のバイト数を読み込んで,s が指し示す配列領域に
格納し,最後に '\0' をつける.
25
ファイル操作のライブラリ関数(2)

int fprintf(FILE *stream, const char
*format, ...);



第1引数に出力先のファイルポインタを指定する.あとは
printfと同じ.
比較: int printf(const char *format, ...);
int fclose(FILE *fp);

開いたファイルを閉じる(プログラムから使えないようにする).
26
ifとfopenの組み合わせ

コード例




if ((fp = fopen("count.txt", "w")) == NULL) {
printf("failed to open file: count.txt\n");
return 1;
}
ファイルを開くことができなければ,メッセージを出力して,関数
の処理を終える.開ければ,fpにファイル構造体のポインタが
代入され,あとのファイル処理で利用できる.
カッコの対応に注意.
• × if (fp=fopen(ファイル名, "w"))==NULL)
• × if (fp=fopen(ファイル名, "w")==NULL)
この例では,「fpへの代入」と「if文」を分けて書いてもよい.し
かし「代入」と「while文」を同時に書く(分けると保守性を損な
う)ことが多いので,この記述に慣れてほしい.
27
(余談)入力方法

プログラム内に書き込む.


コマンドライン引数から獲得する.


int main(int argc, char *argv[])
標準入力(キーボード入力)から獲得する.


int a = 44, b = 16;
scanf,getchar,fgetsなど
ファイルをアクセスする.

fopen,freadなど
入力
出力
実行
プログラム
28
入力方法の得失(1)

プログラム内に書き込む(埋め込む).





ハードコーディング(hardcode)とも呼ばれる.
手軽(原始的)であり,他の環境でも実行しやすい.
入力の値の型は,プログラム内で指定できる.
入力の値が変わるたびにコンパイルが必要.
「メリットとデメ
リット」のこと.あ
る目的を達成す
るための手段が
複数あるとき,
どれを選ぶかの
判断材料になる.
コマンドライン引数から獲得する.




入力の値が変わってもコンパイル不要.
実行時に毎回引数指定が必要.
• ただしシェルのヒストリ機能を使えば省力化できる.
入力サイズには(現実的な)制限がある.
入力の値の型は必ず文字列.
29
入力方法の得失(2)

標準入力から獲得する.





入力の値が変わってもコンパイル不要.
入力サイズに制限がない.
実行時に毎回入力が必要.
• ただしシェルのリダイレクション機能を使えば省力化できる.
入力の値の型は原則として文字または文字列.
ファイルをアクセスする.




最も洗練された手法.
標準入力の特長を受け継ぐ.
ファイルの内容を変えなければ,同じ入力が得られる.
プログラムは複雑になる.
30
入力方法の比較
洗練
柔軟
コマンドライン引数
ファイルアクセス
標準入力
静的
動的
(main関数実行前に
入力値が決まる)
埋め込み
(実行中に入力値を
与える)
原始的
固定
31
まとめ



前処理指令をうまく使えば,読みやすく保守しやすいプログ
ラムを書くことができる.
前処理は,コンパイルの前に行われる.そのため,前処理指
令の書式はCの文法と異なる.
ファイル操作により,プログラムの外から情報を受け取ったり,
情報を保存したりすることができる.
32
スケジュール

第13回:1月26日(金)



(1月31日(水)は金曜日の授業日だが,授業はしない)
第14回:2月2日(金)


ファイル入出力(続き),標準入出力,mallocなど
おさらい問題を実施
試験:2月9日(金) 13:10~14:40

Cの書籍1冊および自筆ノート1冊の持込可
33