ファイル入出力関数の詳細と具体例を示す.
fopen() 関数のプロトタイプ宣言は,
FILE *fopen(const char *filename, const char *mode);
となっている.
fopen 関数の第 1 引数「ファイル名」は,ディスク上でのファイルの名称を表す文字列である.
ファイル名を表す文字列定数や変数を渡せばよい.
例:"textfile.txt"など.
パスを省略した場合は,exeファイルと同じフォルダ内(またはOSが指定する作業ディレクトリ)にファイルが書き出され/読み込まれる.
作業ディレクトリと別の場所にファイルを保存したければ,フルパスで指定する.
例えば,"c:\\users\\textfile.txt"
パスの区切り文字は\\とすること! さもないとエスケープシーケンスとみなされて,ファイルが開けない.
fopen 関数の第 2 引数「モード」は,同じく文字列であるが,以下に示すファイルに対する操作を指定する.
バイナリファイルの場合には,モードの文字列にbを追加し,"rb","wb" のように指定する.
| モード | 意味 | 備考 |
|---|---|---|
| "r" | 読み込み専用でファイルを開く.read | 既存ファイルが無ければエラー. |
| "w" | 書き込み専用で新規ファイルを開く.write | ファイルが既に存在する場合は消去・上書き. |
| "a" | 追加書き込み専用でファイルを開く.append | 元のファイルの末尾に追記.ファイルが無い場合は作成される. |
| "r+" | 読み書き用でファイルを開く. | 既存ファイルが無ければエラー. |
| "w+" | 読み書き用で新規ファイルを開く. | ファイルが既に存在する場合は消去・上書き. |
| "a+" | 読み込み+追加書き込み用でファイルを開く. | 元のファイルの末尾に追記.ファイルが無い場合は作成される. |
fopen() は,ファイルのオープンに成功すると,そのファイルへのファイルポインタ(FILE構造体へのポインタ)を返す.
ファイルポインタ変数には,「どのファイルの,どの辺りを読み書きしているか」が書かれている.
なんらかの原因によりオープンに失敗すると,NULL ポインタを返す.
このため,以下のようなfopen()の戻り値のチェック処理(NULLチェック)を必ず入れること.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
const char fname[] = "datafile.txt"; // ファイル名
fp = fopen(fname, "w"); // fnameは,ファイル名
if( fp == NULL ) {
printf("エラー.%s が開けません.\n", fname); // 開けなかったファイル名を表示
exit(1);
}
// ... 読み書き処理
printf("%s を保存しました (or 読み込みました).\n", fname); // 処理後にファイル名とメッセージを出す場合
fclose(fp); // ファイルを閉じる
}
ファイル読み書きの基本単位は 1 バイト ( byte ) であり,1 バイトづつ読み書きする関数が用意されている.
int fgetc(FILE* fp);
int fputc(int 書き出す値, FILE* fp);
fgetc, fputc = file {get, put} char の省略である.
fgetc()
1バイト読み込み fgetc() は,ファイルのunsigned charとして)1バイト読み込み,int型の値として返す.
この関数の戻り値は int =32bit だが,ファイル読み込んだ数値はそのうちの下位 1 バイトに格納されている.
したがって,戻り値を charやunsigned char型の別の変数に代入しても,問題はない.
エラーの時(またはファイルの最後で読むものがなくなった時)は,EOF (End Of File, -1と定義されている) を返す.
文字・文字列で解説した通り,ASCIIコードはすべて符号なし整数であるため,EOF 記号を判定すれば「どの文字でもない」ことがわかる.
fputc()
1 バイト書き出しfputc() は,「書き出す値」の下位 1 バイトを,
ファイルの 現在の位置に unsigned char 型の値として書き込む.
戻り値は,書き込みが成功すると書き込んだ値を返し,失敗すると EOF を返す.
fgetc, fputc 関数は,ファイルに書かれている内容を数値として取り出す/書き出すだけであるので,
テキストファイル・バイナリーファイルを問わずいかなるファイルでも読み書きできる.
fputc の例#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
const char fname[] = "fputc_sample.txt"; /* ファイル名 */
fp = fopen(fname, "w");
if( fp == NULL ) {
printf("エラー.%s が開けません.\n", fname);
exit(1);
}
char c;
for(c='a'; c<='z'; c++) { /* 文字コードを使ったループ */
fputc(c, fp);
}
fputc('\n', fp); /* ファイル内で改行 */
for(c='A'; c<='Z'; c++) {
fputc(c, fp);
}
printf("%s を保存しました.\n", fname);
fclose(fp); /* ファイルを閉じる */
}
fgetc の例
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
const char fname[] = "fputc_sample.txt"; /* 読み込みたいファイル名 */
fp = fopen(fname, "r"); /* fnameは,既に存在するファイル名 */
if( fp == NULL ) {
printf("エラー.%s が開けません.\n", fname);
exit(1);
}
int c;
do {
c = fgetc(fp);
printf("0x%02x %d \n", c, c); /* 読み込んだファイルの内容を 16進数 と 10進数 で表示 */
} while ( c != EOF );
printf("%s を読み込みました.\n", fname);
fclose(fp); /* ファイルを閉じる */
}
ファイル内の「行」単位で文字列の入出力を行うには,fputs関数や,fgets関数を使用する.
int fputs(char* 書き出す文字列, FILE* fp);
char* fgets(char* 文字列バッファ, int 文字列バッファ長さ, FILE* fp);
fgets, fputs = file {get, put} string の省略である.
「行」という考え方がそもそもテキストファイルであるので,この関数はテキストファイルを扱うことが前提である!
fputs()
fputs() は,引数の文字列すべてをファイルに書き出す.
戻り値は,成功すると負でない値を返し,エラーの時は EOF を返す.
fgets()
fgets() は,ファイルから1行を文字列バッファに読み込み,成功すると読み込んだ文字列(の先頭アドレスを指すポインタ)を返す.
エラー(ファイルの終端に達した等)の時は, NULL を返す.
fputs の例#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
const char fname[] = "fputs_sample.txt"; /* ファイル名 */
fp = fopen(fname, "w");
if( fp == NULL ) {
printf("エラー.%s が開けません.\n", fname);
exit(1);
}
fputs("Hello world!\n", fp); /* 直接文字列を渡す */
char str[] = "This is sample text.";
fputs(str, fp); /* 配列をを渡す */
printf("%s を保存しました.\n", fname);
fclose(fp); /* ファイルを閉じる */
}
ファイル入出力で一番柔軟性が高いのは,以下の関数である.
int fprintf(FILE* fp, char* 書式指定文字列, ...);
int fscanf(FILE* fp, char* 書式指定文字列, ...);
fprintf() と fscanf() は,画面・キーボード入出力で使う printf() と scanf() とほぼ同じ動作をするが,
最初の引数がファイルポインタである点が異なる.
もちろん,入出力がキーボードや画面に対して行われるのではなく,ファイルに対して行われるため,例えば fprintf() 関数を呼び出しても画面には何も表示されない..
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
const char fname[] = "sample.txt";
fp = fopen(fname, "r");
if( fp == NULL) {
printf("File Open Error\n");
exit(1);
}
char buf[100]; // 読み込み用バッファ
while( fscanf(fp, "%s", buf) != EOF) { // EOF = end of file を表す記号
printf("%s\n", buf);
}
fclose(fp);
return 0;
}
テキストデータの読み書きには,fscanf(), fprintf() 関数が便利だが,それだけでは十分ではない.
例えば,bmp, jpeg画像ファイル,mp3, wav音声ファイル,mp4, mov動画ファイルなどは,
数値データの配列であって文字列ではないため,テキスト入出力関数では扱えない.
このようなデータを扱う場合,以下のバイナリ入出力関数を用いる必要がある.
size_t fread(void* buffer, size_t サイズ, size_t 個数, FILE* fp);
size_t fwrite(void* buffer, size_t サイズ, size_t 個数, FILE* fp);
はそれぞれ,buffer を先頭アドレスとするデータを,指定の長さ分,ファイルに読み書きする関数である.
読み書きするデータの総バイト数は,1個当たり「サイズ」バイトのデータを「個数」個,すなわち,(サイズ)×(個数)バイトとなる.
戻り値は,「実際に」ファイルに書き込んだデータの個数(バイト数ではない!)が返る. 戻り値が第3引数の「個数」と等しければ書き込み成功,それ以下であれば何らかの理由で書き込みが失敗したことを表す. (頻度は高くないが,ディスクが一杯,壊れているなど)
注1:void*は,voidポインタであり,「どんな型でも指すことができる汎用ポインタ」である.
従って,第1引数には,charだけでなく,int,floatやdoubleなどの単純型だけでなく,配列や構造体など,あらゆるデータのアドレスを渡すことができる.
注2:size_t型は,コンパイラがもつ最大の値を格納できる整数と定義されていて, 多くの場合,unsigned longと同等である.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp;
const char fname[] = "data.bin"; // ファイル名
fp = fopen(fname, "wb"); // binary モード
if( fp == NULL ) {
printf("エラー.%s が開けません.\n", fname); // 開けなかったファイル名を表示
exit(1);
}
const int N = 5; // 配列の要素数
float a[5] = {0.0, 1.2, -5.3, 99.99, 10000}; // float 配列
fwrite(a, sizeof(float), N, fp);
printf("%s を書き出しました.\n", fname);
fclose(fp); // ファイルを閉じる
}
は,float 型配列 5 個分 = 20 byte のデータをファイルに書き出す.
fwrite()は,戻り値として書き込むことができたデータ数を返すので,エラー処理を丁寧に書くならば
if( fwrite(a, sizeof(float), N, fp) != N) {
printf("write error!\n");
}
とすると良い.
fgetc() や fputc() では,「エラーの時」と,「ファイル終端の時」で戻り値が同じであり,判定できない.
また,その他の関数を使用した読み書きでも,ファイルの終端まで読み込んだかどうかの判定が必要である.
これらの判定には, feof 関数や ferror関数を使う.
int feof(FILE* fp);
int ferror(FILE* fp);
feof() は,現在の位置がファイルの終わりであれば 0以外の値 を,そうでなければ 0 を返す.
ファイルの中身にどれだけデータが書かれているか不明の場合に,while文と組み合わせて大いに役立つ関数である.
ferror() は,ファイル入出力にエラーが起きていれば 0以外の値を,エラーが起きていなけれな 0 を返す.
ファイルの終端まで読み込みたい場合
while ( ! feof(fp) ) {
/* fgetc() など...*/
}
fprintf関数など,多くのファイル入出力関数の引数には,fopen()関数が返した有効なファイルポインタを渡すが,ここに stdout や stderr と記述すると,ファイルではなく画面に出力される.
この機能は,ファイル出力プログラムの動作チェックに重宝する.
なお,stdoutは,コマンドプロンプトでのリダイレクト(exeファイル名に続けて > 記号で画面出力をファイルに振り替える機能)により,
出力先がファイルに変更されるが,
stderrは,必ず画面に出力される.
stdout と stderr は,fopen(), close()しなくとも使用できる.
fprintf(stdout, " ... ", ...); /* ファイルではなく標準出力に出力.printfと等価.*/
fprintf(stderr, " ... ", ...); /* ファイルではなく標準エラー出力に出力.*/
ここでは,実際のファイル入出力のサンプルプログラムを例示する.
パターンは決まっているので,サンプルを各自で実行してみて,定石を覚えてしまうと良い.
以下のプログラムを作成し,実行してみよ.myfile.txtは,どのようなファイルとなるか. 実行後にテキストエディタで開いてみよ.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp = NULL;
const char fname[] = "myfile.txt"; /* ファイル名.*/
/***********************************************/
/* 書き込みモードでテキストファイルを開く */
/***********************************************/
fp = fopen(fname, "w");
if( fp == NULL) {
printf("File Open Error\n");
exit(1);
}
/***********************************************/
/* fputcの例. */
/***********************************************/
fputc('a', fp);
fputc('b', fp);
fputc('c', fp);
fputc('\n', fp); /* 改行 */
fputc('d', fp);
fputc('\t', fp); /* tab */
fputc('e', fp);
fputc('f', fp);
fputc('\n', fp); /* 改行 */
/***********************************************/
/* fputsの例 */
/***********************************************/
char str[] = "Hello Meiji!\n";
fputs(str, fp); /* 1行書き出し */
fputs(str, fp); /* 1行書き出し */
/***********************************************/
/* fprintfの例 */
/***********************************************/
for(int i=0; i<10; i++) {
fprintf(fp, "%d , ", i); /* コンマ区切りの例.csvファイルとして出力する方法. */
}
fprintf(fp, "\n");
fclose(fp); /* これを忘れてはいけない. */
}
さきほど作成したmyfile.txtを読み込む例.先の書き出しのサンプルを実行した後に実行すること.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp = NULL;
const char fname[] = "myfile.txt"; /* ファイル名 */
/* 読み込みモードでテキストファイルを開く */
fp = fopen(fname, "r");
if( fp == NULL) {
printf("File Open Error\n");
exit(1);
}
/* fgetcの例.先頭から最後まで1バイトごとに,読み込みと画面に表示 */
int c;
while( ! feof(fp) ) {
c = fgetc(fp);
printf("%c (0x%02x) %d \n", c, c, c); /* 読み込んだ1バイトを,文字,16進数,10進数で表示 */
}
fclose(fp); /* これを忘れてはいけない. */
}
これを実行すると何が表示されるか,よく考えてみよ.
最後に表示される −1 は,ファイル終端記号(EOF)である.
同様に,myfile.txtをfgetsで読み込む例.
ファイル書き出しのサンプルを実行した後に,実行すること.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp = NULL;
const char fname[] = "myfile.txt"; /* ファイル名 */
/* 読み込みモードでテキストファイルを開く */
fp = fopen(fname, "r");
if( fp == NULL) {
printf("File Open Error\n");
exit(1);
}
/* ここまではfgetcの場合と同じ */
/*****************************************/
/*************************************************/
/* fgetsの例. */
/* 先頭から最後まで1行づつ読み込んで画面に表示 */
/*************************************************/
const int N = 100; /* 行バッファのサイズ.1行の文字数より大きい値にしておく.constでないと次の行がエラーとなる.*/
char buffer[N]; /* 行バッファ. ここに 1 行分のデータを格納する */
while( ! feof(fp) ) {
if( fgets(buffer, N, fp) != NULL) { /* 1 行読込.改行コードまで読み込まれ,ヌル文字が自動的に付加される.*/
printf("%s", buffer); /* 読み込んだ1行を文字列で表示*/
}
}
fclose(fp);
}
これを実行すると何が表示されるか,考えてみよう.
最後に表示される −1 は,ファイル終端記号(EOF)である.
以下のプログラムを作成し,実行してみよ. myfile.binは,どのようなファイルとなるか.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE* fp = NULL;
const char fname[] = "myfile.bin"; /* ファイル名 */
/* 書き込みモードでバイナリファイルを開く */
if( (fp = fopen(fname, "wb")) == NULL) {
printf("File Open Error¥n");
exit(1);
}
const int N = 1; /* データ個数 */
int i = 100; /* データ */
if( fwrite(&i, sizeof(int), N, fp) != N) {
printf("write error¥n");
exit(1);
}
fclose(fp);
i = 0; /* clear */
/*************************** ここでいったん区切り *****************************************/
/* 読み込みモードでバイナリファイルを再度開く */
if( (fp = fopen(fname, "rb")) == NULL) {
printf("Open Error¥n");
exit(1);
}
if( fread(&i, sizeof(int), N, fp) != N) {
printf("read error¥n");
exit(1);
}
fclose(fp);
printf("i = %d¥n", i);
}