Presentation is loading. Please wait.

Presentation is loading. Please wait.

情報処理Ⅱ 2007年1月19日(金).

Similar presentations


Presentation on theme: "情報処理Ⅱ 2007年1月19日(金)."— Presentation transcript:

1 情報処理Ⅱ 2007年1月19日(金)

2 本日学ぶこと 前処理指令 マクロ 問題 サイコロを何度も振って,全ての目が最低1回出るまで,何回振らなければならないか?
最小6回,上限なし(∞回?) 欲しいのは現実的な値 100面のサイコロだったら?

3 前処理とコンパイル(1) 前処理・コンパイル・アセンブル・リンクの各処理は通常,コンパイラ(ccなど)が一手に引き受ける.
ソースファイル (前処理前) ソースファイル (前処理後) 前処理 コンパイル アセンブル オブジェクト ファイル 実行ファイル リンク 前処理・コンパイル・アセンブル・リンクの各処理は通常,コンパイラ(ccなど)が一手に引き受ける.

4 前処理とコンパイル(2) 前処理は, 前処理のコマンド(プリプロセッサ)は,cpp
狭義には,「コンパイルに先立って行われる処理」であり,したがってコンパイルとは別 広義には,ccでコンパイルすれば自動的に処理してくれる,という意味でコンパイル作業の一部 前処理のコマンド(プリプロセッサ)は,cpp Cの前処理以外にも使用可能

5 前処理指令 (Preprocessing directive)
「プリプロセッサ 指令」ともいう マクロ定義(#define) オブジェクト形式マクロ ⇒「定数」の定義 関数形式マクロ ⇒「関数もどき」の定義 マクロとは…コンピュータ関連の作業において,複数の機能や意味をまとめて扱えるようにしたもの ソースファイルの取り込み(#include) ⇒12月8日参照 条件付きコンパイル(#if ... #endif など) 「マクロとは」の説明は, による.

6 要素数は,コンパイル時に評価可能な定数式なので,問題なし
オブジェクト形式マクロ(1) 語の置き換えを行う. #define 置換対象 置換内容 #define WORD_SIZE 6 と記述すると,それ以降 int a[WORD_SIZE]; は int a[6]; と同じ意味になる. プログラム修正により変わり得る定数値があるときに,よく用いられる. 配列の上限値,他と区別する値など. うまく使うことで,定数値を変えるときのプログラム修正箇所を少なくできる. 要素数は,コンパイル時に評価可能な定数式なので,問題なし 列挙型のほうがいいかも

7 オブジェクト形式マクロ(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"); のような「文字列中の語」は,置き換えない.

8 オブジェクト形式マクロ(3) 注意点(続き) 予約語も置換可能.
#define char signed char は文法上問題ないが,よい書き方ではない. typedef signed char schar; とすべきである. 置換内容のない名前も定義できる. #define DEBUG 末尾にセミコロンをつけない. #define WORD_SIZE 6; は(たいていの場合)間違い.

9 関数形式マクロ(1) オブジェクト形式マクロとほぼ同じ書式. 置換対象に「(…)」をつける.
#define pint(x) printf("%d\n",x) に対して,pint(a+1); は printf("%d\n", a+1); に置き換えられる. 複数の引数をとることもできる.そのときは,置換対象の各引数をカンマで区切る. カッコ内に何も書かなければ,引数なしの関数形式マクロが定義される.

10 関数形式マクロ(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)); となる.

11 関数形式マクロ(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); と同じとなる.

12 関数か関数形式マクロか 関数…「機能」を正確に表現したいとき マクロ…「機能」を簡便に表現したいとき
例:int square_int(int x) { return x * x; } 引数や戻り値の型に制約される. 関数呼び出しのオーバーヘッドがある. ローカル変数や制御文を活用できる. 実引数が++aなどのときも,その評価は一度だけ. マクロ…「機能」を簡便に表現したいとき 例:#define square_int(x) ((x) * (x)) 引数や評価式に型はない. (狭義の)コンパイル前に展開され,オーバーヘッドは少ない. ローカル変数や制御文は使用しにくい. (マクロ利用側の)引数は,置換内容の回数だけ評価される.

13 # define pint( x ) printf ( #x " = %d\n" , x )
前処理指令と空白・コメント ...不可 ...必須 # define pint( x ) printf ( #x " = %d\n" , x ) ...任意 一つの前処理指令は,1行で書かなければならない.ただし, 行末に「\」を置くことで,複数行で書ける. 関数形式マクロの場合,括弧の途中で改行できる. 前処理指令の中でコメント(/* */ もしくは //)を書くと,前処理時に空白文字に置き換えられる.

14 サイコロ問題 仕様 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ 目の数は6つ.ただし変更の可能性あり.
サイコロの目は1~6のいずれかとする. 出た目はその都度出力する. 全ての目が出たら,何回振ったかを出力し,終了する.

15 サイコロ問題の考え方(1) サイコロの振り方 ライブラリ関数のrandを用いる
あらかじめ #include <stdlib.h> int a = rand(); により,aにはint型の値が一つ代入される.この値は,ある範囲の中でどの値も等しい確率で選ばれる(一様乱数). int spot = rand() % 6; で,spot には 0~5 のいずれかが代入される. int spot = rand() % 6 + 1; とすればいい! 擬似乱数のため,何度実行しても同じ目が出る.これを変えるには,ライブラリ関数のsrandを呼び出して,適切な値の種を与えればよい.

16 サイコロ問題の考え方(2) 「全ての目が出る」とは? int spot_counter[SPOT_MAX + 1]; 目ごとに出た回数
特定の目がまだ出ていないかの判定は, if (spot_counter[spot] == 0) 全ての目が出たかを,この配列変数だけで判定することはできるが,非効率 int counter_unfound = SPOT_MAX; まだ出ていない目がいくつあるか 一つずつ減らしていき,0になれば「全ての目が出た」

17 サイコロ問題で定義したマクロ #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)」のように使うことも可能

18 条件付きコンパイル(1) #if 定数式 … #endif 定数式が真のときに「…」を残し,そうでなければ「…」を捨てる.
定数式の評価や「…」の取捨は,前処理時(≠実行時)に行われる.

19 条件付きコンパイル(2) 「#if 定数式」に代えて,「#ifdef 名前」や 「#ifndef 名前」も利用可能.
「#else」や「#elif 定数式」も記述可能. 条件付きコンパイルは入れ子にできる. 条件付きコンパイル 参考: Cのif文 #if 条件式1 #elif 条件式2 #else #endif if (条件式1) { } else if (条件式2) { } else { }

20 ヘッダファイルで条件付きコンパイル ある環境の /usr/include/stdio.h より
#ifndef _STDIO_H #define _STDIO_H 1 … #endif /* !_STDIO_H */ この記述により,複数のファイルに #include <stdio.h> があっても問題なく動作する. ヘッダファイルの中で,別のヘッダファイルをインクルードすることがある. _STDIO_H を定義して「…」を獲得するのは,1回だけ.

21 次に学ぶこと ファイル入出力 問題 これまでそのプログラムを何回実行したか,記録できる?
ソースファイルの先頭に,右揃えで行番号をつけて出力できる?

22 ファイルとは 補助記憶装置に保存する単位となる,データの集まり. プログラムが終了しても,内容が保持されるデータ構造. バイト列とは?
(比較)変数の値はメモリ上にあるため,プログラム終了時に破棄される. コンピュータの電源を切って入れ直しても,保持されていることが多い. ストリームと呼ばれるバイト列として,読み書き可能. バイト列とは? char配列で表現できるデータ構造. '\0' で終わるものではなく,途中に '\0' があってもいいという点が,文字列と異なる.

23 実行回数管理プログラム 仕様 考え方 ./count を実行すると,count.txt というファイルに実行回数が保存される.

24 ファイルポインタ Cでファイルを操作するには,ファイルポインタを使用する. FILE オブジェクト fp
stdio.h で定義されているFILE型のポインタ. 例: FILE *fp; FILE オブジェクト fp

25 ファイル操作のライブラリ関数(1) FILE *fopen(char *path, char *mode);
ファイルを開き(プログラムから使えるようにし),ファイルポインタを返す. 第1引数はファイル名(「パス名」ともいう). 第2引数が "r" なら,読み込み専用で開く. 第2引数が "w" なら,書き込み専用で開く. char *fgets(char *s, int size, FILE *stream); 「size-1バイト」,「改行文字まで」,「ファイルの終わりまで」のうち最小のバイト数を読み込んで,s が指し示す配列領域に格納し,最後に '\0' をつける.

26 ファイル操作のライブラリ関数(2) int fprintf(FILE *stream, const char *format, ...);
比較: int printf(const char *format, ...); int fclose(FILE *fp); 開いたファイルを閉じる(プログラムから使えないようにする).

27 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文」を同時に書く(分けると保守性を損なう)ことが多いので,この記述に慣れてほしい.

28 (余談)入力方法 プログラム内に書き込む. コマンドライン引数から獲得する. 標準入力(キーボード入力)から獲得する.
int a = 44, b = 16; コマンドライン引数から獲得する. int main(int argc, char *argv[]) 標準入力(キーボード入力)から獲得する. scanf,getchar,fgetsなど ファイルをアクセスする. fopen,freadなど 入力 出力 実行 プログラム

29 入力方法の得失(1) プログラム内に書き込む(埋め込む). コマンドライン引数から獲得する.
ハードコーディング(hardcode)とも呼ばれる. 手軽(原始的)であり,他の環境でも実行しやすい. 入力の値の型は,プログラム内で指定できる. 入力の値が変わるたびにコンパイルが必要. コマンドライン引数から獲得する. 入力の値が変わってもコンパイル不要. 実行時に毎回引数指定が必要. ただしシェルのヒストリ機能を使えば省力化できる. 入力サイズには(現実的な)制限がある. 入力の値の型は必ず文字列. 「メリットとデメリット」のこと.ある目的を達成するための手段が複数あるとき,どれを選ぶかの判断材料になる.

30 入力方法の得失(2) 標準入力から獲得する. ファイルをアクセスする. 入力の値が変わってもコンパイル不要. 入力サイズに制限がない.
実行時に毎回入力が必要. ただしシェルのリダイレクション機能を使えば省力化できる. 入力の値の型は原則として文字または文字列. ファイルをアクセスする. 最も洗練された手法. 標準入力の特長を受け継ぐ. ファイルの内容を変えなければ,同じ入力が得られる. プログラムは複雑になる.

31 入力方法の比較 ファイルアクセス コマンドライン引数 標準入力 埋め込み 洗練 柔軟 静的 動的 原始的 固定 (main関数実行前に
入力値が決まる) 動的 (実行中に入力値を 与える) 埋め込み 原始的 固定

32 まとめ 前処理指令をうまく使えば,読みやすく保守しやすいプログラムを書くことができる.
前処理は,コンパイルの前に行われる.そのため,前処理指令の書式はCの文法と異なる. ファイル操作により,プログラムの外から情報を受け取ったり,情報を保存したりすることができる.

33 スケジュール 第13回:1月26日(金) (1月31日(水)は金曜日の授業日だが,授業はしない) 第14回:2月2日(金)
ファイル入出力(続き),標準入出力,mallocなど (1月31日(水)は金曜日の授業日だが,授業はしない) 第14回:2月2日(金) おさらい問題を実施 試験:2月9日(金) 13:10~14:40 Cの書籍1冊および自筆ノート1冊の持込可


Download ppt "情報処理Ⅱ 2007年1月19日(金)."

Similar presentations


Ads by Google