10: ファイル入出力 C プログラミング入門 基幹2 (月4) Linux にログインし、以下の講義ページ を開いておくこと http://www-it.sci.waseda.ac.jp/ teachers/w483692/CPR1/ 2014-06-16
標準ライブラリ関数によりファイルの出力を行う 画像ファイルの生成を例題として 今日の内容 標準ライブラリ関数によりファイルの出力を行う 画像ファイルの生成を例題として 配列の作成を復習する 関数を作ってプログラムを構造化する 2014-06-16 C プログラミング入門 基幹2 (月4)
Portable gray map image 単純なフォーマット テキスト形式とバイナリ形式がある PGM 画像ファイル Portable gray map image 他に、PPM (カラー), PBM (2値) がある 単純なフォーマット テキスト形式とバイナリ形式がある PGM 画像ファイルの例 (テキスト形式) PGM 画像 (4×4 pixel) フォーマットを識別するマジックナンバー P2 4 4 255 0 127 255 255 127 255 127 0 header 横幅 高さ [pixels] 画素値の最大値 (=白) 画素値 2014-06-16 C プログラミング入門 基幹2 (月4)
テキストエディタで、前頁のテキストファイルを作り、 mini.pgm というファイル名で保存する ダブルクリックして画像ビューアで開く 4x4 ピクセルなので、拡大しないと見えない 手で作ると大変なので、プログラムで作りだそうというのが、今日の本題です。 2014-06-16 C プログラミング入門 基幹2 (月4)
ファイル入出力の流れ ファイルを開く ファイルを読む・ファイルに書き込む ファイルを閉じる プログラムから指定してた名前のファイルに読み書きできるように準備すること ファイルを読む・ファイルに書き込む I/O (input/output) と呼ばれる 通常は、ファイルの内容を順番にアクセスする (シーケンシャルアクセス; sequential access) ⇄ランダムアクセス (random access) ファイルを閉じる ファイルの処理を完了し、バッファに残っているものを全て出力する 2014-06-16 C プログラミング入門 基幹2 (月4)
例題: PGM ファイルの出力 fopen(), fprintf(), fclose() などは stdio.h で宣言されている #include <stdio.h> int main(void) { FILE *fp; fp = fopen("mini.pgm", "w"); fprintf(fp, "P2\n"); fprintf(fp, "4 4\n"); fprintf(fp, "255\n"); fprintf(fp, " 0 127 255 255\n"); ... fclose(fp); 開いたファイルを識別するため情報を指すポインタ ファイルポインタと呼ばれる。変数名は何でもよい ファイルを mini.pgm という名前で、書きこみモードで開く ファイルに文字列を書きこむ ファイル閉じる 2014-06-16 C プログラミング入門 基幹2 (月4)
fopen() にファイル名とモードを指定 FILE 型へのポインタが返る ファイルを開く fopen() にファイル名とモードを指定 FILE 型へのポインタが返る ファイルを操作するために必要な情報が入っている 変数に入れて使う モード 意味 "r" 読み込み (read)。ファイルがなければエラーとなる。 "w" 書き込み (write)。既存のファイルは削除される "a" 追記 (append)。既存のファイルがあればその末尾に追記され、なければ新規作成される。 fopen() のプロトタイプ 構造体(今後の講義で解説)であるが、 内容を理解する必要は通常ない #include <stdio.h> FILE *fopen(const char *filename, const char *mode ); 2014-06-16 C プログラミング入門 基幹2 (月4)
ファイルオープンの失敗 ファイルが開けない場合がある 失敗した場合 NULL ポインタが返る 読み込みモードで、ファイルが存在しない場合 書き込みモードで、書き込み禁止の場所の場合 etc... 失敗した場合 NULL ポインタが返る { FILE *fp; fp = fopen("some.file", "w"); if(fp == NULL) { // エラー処理を行う 2014-06-16 C プログラミング入門 基幹2 (月4)
ファイルに文字列を書きだす ファイルに書き込むための関数を利用 fprintf() は printf() と同じ使い方だが、第1引数にファイルポインタを与える fprintf() のプロトタイプ #include <stdio.h> int fprintf(FILE *stream, const char *format, ...); その他のファイル書き込み関数 関数 内容 fwrite ポインタで指すメモリ領域を指定したサイズだけ読み取って、ファイルに書き込む fputc (putc) 1バイトだけファイルに書き込む fputs 文字列をファイルに書き込む fflush バッファの中身をすべて書きだす 2014-06-16 C プログラミング入門 基幹2 (月4)
ファイルポインタを引数に fclose() を呼ぶ ファイルを閉じる ファイルポインタを引数に fclose() を呼ぶ ファイルの書き込みは、効率のためにバッファリングがされるが、 fclose() は自動的に fflush() を呼び出す fclose() のプロトタイプ #include <stdio.h> int fclose(FILE *fp); 2014-06-16 C プログラミング入門 基幹2 (月4)
特定のパターンならループを使ってファイルを書きだせば書けそう 複雑な場合は? 大きい画像を作る 特定のパターンならループを使ってファイルを書きだせば書けそう 複雑な場合は? 画素値 0, 50, 100, 150 のグラデーション { FILE *fp; int i; fp = fopen("grad.pgm", "w"); ... for(i = 0; i < 4; ++i) fprintf(fp, "%d %d %d %d\n", i*50, i*50, i*50, i*50); } 2014-06-16 C プログラミング入門 基幹2 (月4)
配列の各要素を画素値だと思って、そこに絵を書く ループを使ってファイルに書き出す 配列をキャンバスに使う 配列の各要素を画素値だと思って、そこに絵を書く ループを使ってファイルに書き出す unsigned char image[32][32] 画素値の配列を作って、 絵を書く 255 255 21 2014-06-16 C プログラミング入門 基幹2 (月4)
コード例 { FILE *fp; int x, y; unsigned char image[32][32] = {{0}}; // ファイル出力 fp = fopen("image.pgm", "w"); // エラーチェックをしてヘッダを書きだす ... for(y = 0: y < 32; ++y) for(x = 0; x < 32; ++x) fprintf(fp, "%d ", image[y][x]); } fclose(fp); 32×32ピクセルの配列をつくり、ゼロで初期化する。 配列 image に画素値をいろいろ入れる 空白でそれぞれの画素値が区切られるようにする 行、列の順番で考えている 2014-06-16 C プログラミング入門 基幹2 (月4)
1 バイト (unsigned char) の配列をそのままファイルに書いた形式。ヘッダは P5 fwrite() を使って書きだす PGM のバイナリ形式の場合 1 バイト (unsigned char) の配列をそのままファイルに書いた形式。ヘッダは P5 fwrite() を使って書きだす unsigned char image[32][32] 255 255 21 buf の指す場所から、 size [bytes] * count [個]の領域の値を fp の指すファイルに書き出す #include <stdio.h> size_t fwrite(const void *buf, size_t size, size_t count, FILE *fp); fwrite() のプロトタイプ 2014-06-16 C プログラミング入門 基幹2 (月4)
配列に様々な絵を書きこむ関数を実現するには、配列へのポインタとサイズを渡す 配列に絵を書く関数 配列に様々な絵を書きこむ関数を実現するには、配列へのポインタとサイズを渡す 配列のサイズを関数は直接知ることができない // width x height の画像 img を color で塗りつぶす void fill(unsigned char *img, int width, int height, int color) { int x, y; for(y = 0; y < height; ++y) for(x = 0; x < width; ++x) // NG: img[y][x] = color; img[x + y*width] = color; } width x height の領域の先頭へのポインタ ポインタ引数で配列の先頭位置を知っただけなので、2次元配列としてアクセスできない。代わりに、座標からメモリ上の位置を計算する 2014-06-16 C プログラミング入門 基幹2 (月4)
printf() は標準出力 (画面) に文字列を出す fprintf() はファイルに文字列を出す 配列に文字列を書きこむ ファイル名などをプログラムで生成 自動的に日付を付ける 連番にする etc... printf() は標準出力 (画面) に文字列を出す fprintf() はファイルに文字列を出す 文字配列に出力するには、 sprintf() を使う sprintf() のプロトタイプ メモリ領域の先頭へのポインタ #include <stdio.h> int sprintf(char *buffer, const char *format, ...); 2014-06-16 C プログラミング入門 基幹2 (月4)
sprintf() が書きだすのに十分大きい配列を用意しておく はみ出してもエラーは出ない 他の使われている領域を破壊する可能性がある 実用上は 256 など、大きい値を指定する { char filename[10] = ""; sprintf(filename, "img-%d.pgm", 5); たとえば、ここが変数となり、ループなどで変化する char filename[10] '\0' '\0' '\0' '\0' '\0' '\0' '\0' '\0' '\0' '\0' この例ではぎりぎりの文字数となる。 5 の代わりに 10 としたら、はみ出してメモリ破壊をしてしまう。 'i' 'm' 'g' '-' '5' '.' 'p' 'g' 'm' '\0' 2014-06-16 C プログラミング入門 基幹2 (月4)
読み込みの処理は、書きこむよりも難しいことが多い テキストファイルの読み込み 手順 fopen() のモードを "r" で開く fgetc(), fgets() で文字列を読み込み、メモリに書きこむ fscanf() によって文字列を数値などに変換してメモリに書きこむ その後、 sscanf() によって処理をしてもよい 読み込みの処理は、書きこむよりも難しいことが多い ファイルに「正しい」情報があるとは限らない 2014-06-16 C プログラミング入門 基幹2 (月4)