これまで,データの入出力については, 書式付き入出力関数 scanf( ) および printf( ) だけで対処してきた. しかし,問題によっては,書式付き入出力では不便な場合もある. そこで,今回および次回は,書式無し入出力関数について勉強しよう.
今回は,すべての入出力処理の基本となるバイト単位(文字単位)の入出力とする.
本題の前に,既に理解している入出力処理について,事情を整理しておこう. これまでは,テキストデータ(文字列データ)の入出力しか取り扱って来なかった.
また,入力の場合,これまでは, 空白・改行で区切られた単語や数字さえ正しく入力できれば充分だった.
しかし,ファイルの内容がいつでもテキストデータである,とは限らない. たとえば音声や画像のデータファイルでは,大抵の場合, バイナリデータ(二進数データ)として記録されている. また,テキストファイルの場合であっても, 空白や改行の文字について,区切文字として無視するのではなく, 意味のあるデータとして利用したい場合もある. これらのような場合には,データを 1 バイトずつ入力する必要がある.
バイナリデータとテキストデータの違いは次の通り. たとえば,数 255 を記録する場合:
データを1バイトずつ入力するためには, もちろん fscanf(fp, "%c", ...) も使えるが, より簡単な次の関数を使うとよい:
#include <stdio.h> int fgetc(FILE *fp)
この関数は, ファイルから1バイト分のデータを unsigned char 型として読み取り, int 型へ変換して返す. ただし,データを読み尽くした場合には,EOF を返す.
また,1バイトの出力としては,次の関数を使うとよい:
#include <stdio.h> int fputc(int c, FILE *fp)
これは,fprintf(FILE *fp, "%c", unsigned char c) とほぼ同じものだ.
バイト単位入出力関数 fgetc( ) および fputc( ) の利用例として, ファイルの内容をコピーする関数 cp( ) の定義例を List 1 に示す. また,List 2 は, 書式付き入出力関数 fscanf( ) および fprintf( ) による定義例である. これらのリストを比較して, fgetc( ) と fscanf( ) の間の類似点・相違点を理解しよう.
#include <stdio.h> /* ファイルコピー関数 * fin:入力のファイルポインタ * fout:出力のファイルポインタ */ void cp1(FILE *fin, FILE *fout) { int c; while (1) { if ((c = fgetc(fin)) == EOF) break; // fgetc() の戻り値が EOF のとき, // 変数 c にも EOF が代入される. fputc(c, fout); } }
void cp2(FILE *fin, FILE *fout) { unsigned char c; while (1) { if (fscanf(fin, "%c", &c) == EOF) break; // fscanf() の戻り値は EOF となるが, // 変数 c には EOF は代入されない. fprintf(fout, "%c", c); } }
参考までに,List 3 は, List 1 および 2 をテストするためのプログラム (cp コマンドの機能限定版クローン p-cp) の定義例である.
#include <stdio.h> #include <stdlib.h> void cp1(...) { ... } // List 1 void cp2(...) { ... } // List 2 int main(int argc, char *argv[]) { FILE *fp1, *fp2; if (argc < 3) goto ERR_ARG; if ((fp1 = fopen(argv[1], "r")) == NULL) goto ERR_FP1; if ((fp2 = fopen(argv[2], "w")) == NULL) goto ERR_FP2; cp1(fp1, fp2); // fgetc 版 cp // cp2(fp1, fp2); // fscanf %c 版 cp fclose(fp2); fclose(fp1); return (EXIT_SUCCESS); ERR_FP2: fclose(fp1); ERR_FP1: ERR_ARG: fprintf(stderr, "コピー失敗\n"); return (EXIT_FAILURE); }
実行例:
$ cat infile.txt # 入力(コピー元)ファイルの内容を表示 ooo rrr zzz o o r z ooo r zzz $ ./p-cp infile.txt outfile.txt # ファイルコピー $ cat outfile.txt # 出力(コピー先)ファイルの内容を表示 ooo rrr zzz o o r z ooo r zzz # 同じ内容になっている
入力と出力の内容をよく比較してみよう. 空白文字の個数まで完全に一致している. (fgetc 版でも fscanf %c 版でも動作は同じハズ.)
文字列入力 fscanf(..., "%s", ...) の場合, こうはならない. 疑うのなら, List 2 の cp2() 関数の "%c" を "%s" に変えて List 3 の p-cp コマンドを作り,実行してみよう. (もちろん,変数 char c についても 配列 char s[256] とかに変える必要あり.)
なお,上の実行例では,テキストファイルのコピーだったが, List 1 や List 2 の cp()(バイト単位の入出力)では, バイナリファイル(プログラム,画像,音声,等)のコピーも可能となる. (文字列単位の入出力だと正しくコピーできない.)
次の問題について,ソースファイルと実行例をレポートせよ.
実行例:
$ cat infile.txt # テスト用入力ファイルの内容を表示している ooo rrr zzz o o r z ooo r zzz $ ./p-cat infile.txt # 作成したプログラムでも同じ結果になるハズ ooo rrr zzz o o r z ooo r zzz $ ./p-cat infile.txt infile.txt infile.txt # 複数のファイルを連結している ooo rrr zzz o o r z ooo r zzz ooo rrr zzz o o r z ooo r zzz ooo rrr zzz o o r z ooo r zzz
条件:厳守!
テスト用メイン関数:
int main()
{
char *str = "Hello, world.\n";
// fputs(str, stdout);
// fprintf(stdout, "%s", str);
myFputs(str, stdout);
return (0);
}
実行例:
$ ./myfputs Hello, world. $