ファイル操作

この章ではC言語でファイルを操作する方法を紹介します。

ファイルポインタ

C 言語は、ファイルの操作に必要な情報を記録する FILE データ構造を提供します。この構造はヘッダー ファイル stdio.h で定義されます。すべてのファイル操作関数は、このデータ構造を通じてファイル情報を取得する必要があります。

ファイルの操作を開始する前に、ファイルを指す FILE ポインタを定義する必要があります。これは、ファイル情報を保存するためのメモリ領域を取得するのと同じです。

ファイル* fp;

上の例では、FILE ポインタ fp を定義しています。

以下はファイルを読み取る完全な例です。

#include <stdio.h>

int main(void) {
  ファイル* fp;
  文字c;

  fp = fopen("hello.txt", "r");
  if (fp == NULL) {
    -1 を返します。
  }

  c = fgetc(fp);
  printf("%c\n", c);

  fclose(fp);

  0を返します。
}

上記の例では、新しいファイル ポインター fp を作成した後、次の 3 つのファイル操作関数を 3 つのステップに分けて順番に使用します。他のファイル操作も通常は同じ手順に従います。

最初のステップは、fopen() を使用して指定されたファイルを開き、ファイル ポインタを返すことです。エラーが発生した場合は NULL が返されます。

これは、指定されたファイルの情報を新しく作成されたファイル ポインタ fp に関連付けることと同じです。次の情報が FILE 構造内に記録されます: ファイル内の現在の読み取りおよび書き込み位置、読み取りおよび書き込みエラー レポートの記録。ファイルの終わりインジケーター、およびバッファー領域 開始位置へのポインター、ファイル識別子、カウンター (バッファーにコピーされたバイト数をカウントする) など。後続の操作では、(ファイル名ではなく) このポインターを使用して、指定されたファイルを処理できます。

同時にファイルのキャッシュ領域も作成します。キャッシュ領域の存在により、 fopen() 関数は「ストリームを開く」とも言え、その後のファイルの読み書きはストリームモードになります。

2 番目のステップは、読み取り関数と書き込み関数を使用して、ファイルからデータを読み取るか、ファイルにデータを書き込むことです。上の例では、fgetc() 関数を使用して、開いているファイルから文字を読み取ります。

fgetc() が呼び出されると、まずファイルのデータ ブロックがバッファにコピーされます。コンピューターごとにバッファ サイズは異なりますが、通常は 512 バイト、またはその倍数 (4096 バイトや 16384 など) です。コンピュータのハードドライブがますます大きくなるにつれて、バッファも大きくなります。

fgetc() はバッファからデータを読み取り、同時にファイル ポインタ内の読み取りおよび書き込み位置インジケータを、読み取り文字の次の文字を指すように設定します。すべてのファイル読み取り関数は同じバッファを使用します。その後呼び出される読み取り関数は、インジケーターが指す位置、つまり最後の読み取り関数が停止した位置から読み取りを開始します。

読み取り関数は、バッファー内のすべての文字が読み取られたことを検出すると、次のバッファー サイズのデータ​​ ブロックをファイルからバッファーにコピーするように要求します。このようにして、読み取り関数はファイルの終わりまでファイルのすべての内容を読み取ります。ただし、上記の例ではバッファ領域から 1 文字だけを読み取ります。この関数は、バッファ内のファイルの最後の文字を読み取ると、FILE 構造内のファイル終了インジケータを true に設定します。したがって、次回読み取り関数が呼び出されたときは、定数 EOF が返されます。 EOF はファイルの終わりを表す整数値で、通常は「-1」です。

3 番目のステップ fclose() はファイルを閉じてキャッシュ領域をクリアします。

上記はファイルの読み取りのプロセスです。ファイルの書き込みも同様です。バッファーがいっぱいになると、バッファー内のデータがファイルに転送されます。

fopen()

fopen() 関数はファイルを開くために使用されます。すべてのファイル操作の最初のステップは、fopen() を使用して指定されたファイルを開くことです。この関数のプロトタイプはヘッダー ファイル stdio.h で定義されます。

FILE* fopen(char* ファイル名, char* モード);

2 つのパラメータを受け入れます。最初のパラメータはファイル名 (パスを含むことができます) で、2 番目のパラメータはファイルに対して実行される操作を指定するモード文字列です。たとえば、次の例では、「r」はファイルを読み取りで開くことを意味します。モード。

fp = fopen("in.dat", "r");

ファイルを正常に開くと、fopen() は FILE ポインタを返します。このポインタは、他の関数がファイルを操作するために使用できます。ファイルを開くことができない場合 (たとえば、ファイルが存在しないか、アクセス許可がない場合)、ヌル ポインタ NULL が返されます。したがって、 fopen() を実行した後、オープンが成功したかどうかを判断するのが最善です。

fp = fopen("hello.txt", "r");

if (fp == NULL) {
  printf("ファイルを開けません!\n");
  終了(EXIT_FAILURE);
}

上の例では、fopen() が null ポインタを返した場合、プログラムはエラーを報告します。

fopen() のパターン文字列は以下の通りです。

  • r: 読み取りモード。データの読み取りにのみ使用されます。ファイルが存在しない場合は、NULL ポインタが返されます。
  • w: 書き込みモード。データの書き込みにのみ使用されます。ファイルが存在する場合、ファイルの長さは書き込み前に 0 に切り捨てられます。ファイルが存在しない場合は、ファイルが作成されます。
  • a: 書き込みモード。ファイルの末尾にデータを追加するためにのみ使用されます。ファイルが存在しない場合は、ファイルが作成されます。
  • r+: 読み取りおよび書き込みモード。ファイルが存在する場合、ポインタはファイルの先頭を指し、ファイルの先頭にデータを追加できます。ファイルが存在しない場合は、NULL ポインタが返されます。
  • w+: 読み取りおよび書き込みモード。ファイルが存在する場合、ファイル長は 0 に切り捨てられてからデータが書き込まれます。このモードでは実際にはデータを読み取ることはできませんが、データは消去されます。ファイルが存在しない場合は、ファイルが作成されます。
  • a+: 読み取りおよび書き込みモード。ファイルが存在する場合、ポインタはファイルの末尾を指し、コンテンツを既存のファイルの末尾に追加できます。ファイルが存在しない場合は、ファイルが作成されます。

前のセクションで述べたように、fopen() 関数は開かれたファイルのバッファを作成します。読み取りモードでは読み取りバッファが作成され、書き込みモードでは書き込みバッファが作成され、読み取りおよび書き込みモードでは 2 つのバッファが同時に作成されます。 C 言語は、バッファ領域を介してストリーム形式でファイルにデータを読み書きします。

ファイル内のデータはバイナリ形式で保存されます。ただし、読み込む際には解釈方法が異なり、バイナリのまま解釈することを「バイナリストリーム」、バイナリデータをテキストに変換してテキスト形式で解釈することを「テキストストリーム」と呼びます。書き込み操作も同様で、バイナリ書き込みとテキスト書き込みに分けられます。後者には、テキストをバイナリに変換する追加の手順が含まれます。

fopen() のモード文字列はデフォルトでテキスト ストリームで読み書きされます。 「b」サフィックス (バイナリを意味する) を追加すると、「バイナリ ストリーム」として読み書きされます。例えば、「rb」はバイナリデータを読み出すモードであり、「wb」はバイナリデータを書き込むモードである。

パターン文字列には、排他モードを示す「x」接尾辞も付いています。ファイルがすでに存在する場合、ファイルを開くことはできません。ファイルが存在しない場合は、新しいファイルが作成され、開いた後は他のプログラムやスレッドは現在のファイルにアクセスできなくなります。たとえば、「wx」は、ファイルを排他モードで書き込むことを意味します。ファイルがすでに存在する場合、開くことは失敗します。

標準ストリーム

Linux システムでは、デフォルトで 3 つのオープン ファイルが提供されており、それらのファイル ポインタは次のとおりです。

  • stdin (標準入力): デフォルトのソースはキーボードで、ファイル ポインタ番号は 0 です。
  • stdout (標準出力): デフォルトの出力先はモニターで、ファイルポインタ番号は 1 です。
  • stderr (標準エラー): デフォルトの宛先はモニター、ファイルポインタ番号 2 です。

Linux システム内のファイルは必ずしもデータ ファイルである必要はなく、デバイス ファイルである場合もあります。つまり、ファイルは読み取りまたは書き込みが可能なデバイスを表します。ファイルポインタ stdin はデフォルトでキーボードをファイルとして扱います。このファイルを読み取ることで、ユーザーのキーボード入力を取得できます。同様に、stdoutstderr はデフォルトで表示をファイルとして扱い、プログラムの実行結果をこのファイルに書き込み、ユーザーは実行結果を見ることができます。それらの違いは、stdout はプログラムの通常の実行結果を書き込み、stderr はプログラムのエラー メッセージを書き込むことです。

これら 3 つの入出力チャネルは Linux によってデフォルトで提供されるため、標準入力 (stdin)、標準出力 (stdout)、および標準エラー (stderr) と呼ばれます。実装が同じであるため、どちらもファイル ストリームであるため、まとめて「標準ストリーム」と呼ばれます。

Linux では、これら 3 つのファイル ポインター (ファイル ストリーム) が指すファイルを変更できます。これをリダイレクトと呼びます。

標準入力がキーボードではなく他のファイルにバインドされている場合は、ファイル名の前に不等号「<」を追加し、プログラム名の後に続けることができます。これを「入力リダイレクト」と呼びます。

$ デモ < in.dat

上記の例では、「demo」プログラム コードの「stdin」はファイル「in.dat」を指します。つまり、「in.dat」からデータを取得します。

標準出力がモニターではなく別のファイルにバインドされている場合は、ファイル名の前に不等号「>」を追加し、その後にプログラム名を追加できます。これを「出力リダイレクト」と呼びます。

$ デモ > out.dat

上記の例では、「demo」プログラム コードの「stdout」はファイル「out.dat」を指します。つまり、データを「out.dat」に書き込みます。

出力リダイレクト > は、まず out.dat の元の内容をすべて消去してから書き込みます。記述した情報を「out.dat」の末尾に追加したい場合は、「>>」記号を使用します。

$デモ>>out.dat

上記の例では、「demo」プログラム コードの「stdout」はデータをファイル「out.dat」に書き込みます。 >と異なり、書き込み開始位置はout.datのファイルの末尾となります。

標準エラーのリダイレクト記号は 2> です。 「2」はファイルポインタの番号を表し、「2>」はファイルポインタ2番の書き込みを「err.txt」にリダイレクトすることを意味する。ファイルポインタ 2 番は標準エラー stderr です。

$ デモ > out.dat 2 > err.txt

上記の例では、「demo」プログラム コード内の「stderr」はエラー情報をファイル「err.txt」に書き込みます。そして stdout はファイル out.dat に書き込みます。

入力リダイレクトと出力リダイレクトを 1 つのコマンドに結合することもできます。

$ デモ < in.dat > out.dat

// または
$ デモ > out.dat < in.dat

リダイレクトの別のケースとして、あるプログラムの標準出力 stdout を別のプログラムの標準入力 stdin に向ける場合があります。この場合、| 記号を使用する必要があります。

$random|合計

上記の例では、「random」プログラム コードの「stdout」への書き込みは、「sum」プログラム コードの「stdin」から読み取られます。

fclose()

fclose() は、fopen() を使用して開かれたファイルを閉じるために使用されます。そのプロトタイプは stdio.h で定義されます。

int fclose(FILE* ストリーム);

ファイルポインタ「fp」をパラメータとして受け取ります。ファイルが正常に閉じられた場合、fclose() 関数は整数 0 を返します。操作が失敗した場合 (ディスクがいっぱいであるか、I/O エラーが発生した場合)、特別な値 EOF を返します (参照)。詳細については次のセクションを参照してください)。

if (fclose(fp) != 0)
  printf("何かが間違っています。");

使用されなくなったファイルは fclose() を使用して閉じる必要があります。そうしないとリソースを解放できません。一般に、システムには同時に開くことができるファイルの数に制限があります。時間内にファイルを閉じることで、この制限を超えないようにすることができます。

終了後

C 言語のファイル操作関数は、ファイルの終わりに達した場合に特別な値を返すように設計されています。プログラムがこの特別な値を受け取ると、ファイルの終わりに到達したことがわかります。

ヘッダー ファイル stdio.h は、この特別な値に対してマクロ EOF (ファイルの終わりの略) を定義しており、その値は通常 -1 です。これは、ファイルから読み取られたバイナリ値は、符号なし数値として解釈されるか ASCII コードとして解釈されるかにかかわらず、負の値になることができないため、安全に -1 を返すことができ、ファイル自体のデータと一致しないためです。 。 対立。

実際に文字列の末尾に格納される値「\0」とは異なり、ファイルの末尾には「EOF」が格納されるわけではないことに注意してください。この値はファイル内に完全に存在しません。ファイルの終わりに達したことを検出する操作関数は、この値を返します。

##freopen()

freopen() はファイルを新しく開くために使用され、すでに開かれているファイル ポインタに直接リンクされます。これにより、ファイル ポインタを再利用できるようになります。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

FILE* freopen(char* ファイル名、char* モード、FILE* ストリーム);

fopen() と比較すると、再利用されるファイル ポインタを表す 3 番目のパラメータがあります。他の 2 つのパラメータ、つまりファイル名と開くモードは同じです。

freopen("output.txt", "w", stdout);
printf("こんにちは");

上記の例では、ファイル output.txtstdout に関連付けており、stdout に書き込まれた内容はすべて output.txt に書き込まれます。 printf() はデフォルトで stdout に出力するため、上記のコードを実行すると、output.txt ファイルが hello に書き込まれます。

freopen() の戻り値は、その 3 番目のパラメータ (ファイル ポインタ) です。オープンに失敗した場合 (ファイルが存在しない場合など)、ヌル ポインタ NULL が返されます。

ファイルポインタがすでに開いているファイルを指していない場合、freopen() は以前に開いたファイルを自動的に閉じます。freopen()fopen() と同等です。

以下は scanf() に関連付けられた freopen() の例です。

int i、i2;

scanf("%d", &i);

freopen("someints.txt", "r", stdin);
scanf("%d", &i2);

上記の例では、scanf() が 2 回呼び出され、最初の呼び出しはキーボードから読み取り、次に freopen() を使用して stdin ポインターをファイルに関連付けます。 。

一部のシステムでは、「freopen()」を使用してファイルのオープンモードを変更できます。この場合、freopen() の最初のパラメータは NULL でなければなりません。

freopen(NULL, "wb", stdout);

上記の例では、stdout の開始モードを w から wb に変更します。

fgetc()、getc()

fgetc()getc() は、ファイルから文字を読み取るために使用されます。これらの使用法は getchar() と似ていますが、違いは getchar()stdin から読み取るためにのみ使用されるのに対し、これら 2 つの関数は指定されたファイルから読み取ることです。それらのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int fgetc(FILE *stream)
int getc(FILE *stream);

fgetc()getc() の使用法は同じで、両方ともパラメータが 1 つだけ、つまりファイル ポインタしかありません。 2 つの違いは、getc() は一般にマクロを使用して実装されるのに対し、fgetc() は関数として実装されるため、前者のパフォーマンスが優れている可能性があることです。これら 2 つの関数は文字を返しますが、戻り値の型は char ではなく int であることに注意してください。これは、読み取りが失敗した場合、この値は通常 -1 を返すためです。

#include <stdio.h>

int main(void) {
  ファイル*fp;
  fp = fopen("hello.txt", "r");

  int c;
  while ((c = getc(fp)) != EOF)
    printf("%c", c);

  fclose(fp);
}

上の例では、getc() はファイルの各文字を順番に読み取り、ファイルの終わりに達するまでそれを変数 c に入れ、EOF を返し、ループが終了します。変数 c の型は char ではなく int です。負の値に等しい可能性があるため、int に設定することをお勧めします。

fputc()、putc()

fputc()putc() はファイルに文字を書き込むために使用されます。これらの使用法は putchar() と似ていますが、違いは putchar()stdout に書き込むのに対し、これら 2 つの関数はファイルに書き込むことです。それらのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int fputc(int char, FILE *stream);
int putc(int char, FILE *stream);

fputc() の使用法は putc() と同じで、最初のパラメータは書き込まれる文字、2 番目のパラメータはファイル ポインタを受け取ります。それらの違いは、putc() は通常マクロを使用して実装されるのに対し、fputc() は関数としてのみ実装されるため、理論的には putc() のパフォーマンスが向上することです。

書き込みが成功した場合は書き込まれた文字を返し、書き込みが失敗した場合は EOF を返します。

fprintf()

fprintf() は、フォーマットされた文字列をファイルに書き込むために使用され、その使用法は printf() と似ています。違いは、printf() は常に stdout に書き込むのに対し、fprintf() は指定されたファイルに書き込み、その最初のパラメータはファイル ポインタでなければならないことです。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int fprintf(FILE* ストリーム、const char* 形式、...)

fprintf()printf() を置き換えることができます。

printf("こんにちは、世界!\n");
fprintf(stdout, "Hello, world!\n");

上記の例では、fprintf() を指定して stdout に書き込むと、printf() を呼び出した場合と同じ結果になります。

fprintf(fp, "合計: %d\n", sum);

上記の例では、指定された形式の文字列をファイル ポインタ fp に書き込みます。

以下は stderr にエラーメッセージを出力する例です。

fprintf(stderr, "何かの数値。\n");

fscanf()

fscanf() は、指定されたパターンに従ってファイルからコンテンツを読み取るために使用されます。その使用法は scanf() と似ています。違いは、scanf() は常に stdin からデータを読み取るのに対し、fscanf() はファイルからデータを読み取ることです。そのプロトタイプはヘッダー ファイル stdio.h で定義されており、最初のパラメーターは である必要があります。ファイル。

int fscanf(FILE* ストリーム、const char* 形式、...);

以下に例を示します。

fscanf(fp, "%d%d", &i, &j);

上の例では、fscanf() はファイル fp から 2 つの整数を読み取り、それらを変数 ij に代入します。

fscanf() を使用するための前提条件は、ファイルのプレースホルダー解析規則が scanf() とまったく同じであることを知っていることです。 fscanf() は連続読み取りが可能で、ファイルの終端に達するかエラー(読み取り失敗、マッチング失敗)が発生するまで読み取りを停止しないため、通常は fscanf() をループ内に配置します。

while(fscanf(fp, "%s", 単語) == 1)
  置く(単語);

上の例では、fscanf() はファイルの各単語を順番に読み取り、ファイルの終わりまで 1 行に 1 つずつ出力します。

fscanf() の戻り値は、割り当てに成功した変数の数です。割り当てが失敗した場合は、EOF が返されます。

fgets()

fgets() は、ファイルから指定された長さの文字列を読み取るために使用されます。その名前の最初の文字は f で、これは file を意味します。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

char* fgets(char* str, int STRLEN, File* fp);

最初のパラメータ str は、読み取ったコンテンツを格納するために使用される文字列ポインタです。 2 番目のパラメータ STRLEN は読み取られる長さを指定し、3 番目のパラメータは読み取られるファイルを指す FILE ポインタです。

fgets() は STRLEN - 1 文字を読み取るか、改行文字またはファイルの終わりに達すると読み取りを停止し、読み取った内容の末尾にヌル文字 \0 を追加して文字列にします。 。 fgets() は文字列に改行 (\n) を格納することに注意してください。

fgets の 3 番目の引数が stdin の場合、scanf() と同等の標準入力を読み取ることができます。

fgets(str, sizeof(str), stdin);

読み取りが成功した場合、fgets() の戻り値は最初のパラメータ、つまり文字列へのポインタになります。それ以外の場合は、ヌル ポインタ NULL を返します。

fgets() を使用してファイルの各行を読み取ることができます。以下はファイルのすべての行を読み取る例です。

#include <stdio.h>

int main(void) {
  ファイル* fp;
  char s[1024]; //配列は行に収まる十分な大きさでなければなりません
  int 行数 = 0;

  fp = fopen("hello.txt", "r");

  while (fgets(s, sizeof s, fp) != NULL)
    printf("%d: %s", ++linecount, s);

  fclose(fp);
}

上記の例では、行を読み込むたびに行番号と行の内容が出力されます。

次の例では、ユーザー入力をループします。

文字の単語[10];

put("文字列を入力してください (終了するには q):");

while (fgets(words, 10, stdin) != NULL) {
  if (単語[0] == 'q' && 単語[1] == '\n')
    壊す;

  置く(単語);
}

put("完了");

上の例では、ユーザーが入力した文字列が 9 文字を超える場合、fgets() はそれを複数回読み取ります。このループは、「q」 + Enter キーが出現するまで終了しません。

fputs()

fputs() 関数は、文字列をファイルに書き込むために使用されます。 puts() 関数との唯一の違いは、文字列の末尾に改行文字を追加しないことです。これは、fgets() が改行を保持するため、fputs() は改行を追加しないためです。 fputs() 関数は通常、fgets() とペアになります。

そのプロトタイプは stdio.h で定義されます。

int fputs(const char* str, FILE* stream);

これは 2 つのパラメータを受け入れます。最初のパラメータは文字列ポインタで、2 番目のパラメータは書き込まれるファイルへのポインタです。 2 番目のパラメータが stdout (標準出力) の場合、内容はコンピュータ画面に出力されます。これは printf() と同等です。

文字の単語[14];

Puts("文字列を入力してください。");
fgets(単語, 14, 標準入力);

put("これはあなたの文字列です:");
fputs(ワード、標準出力);

上記の例では、最初に fgets() を使用して stdin からユーザー入力を読み取り、次に fputs() を使用して stdout に出力します。

書き込みが成功すると、fputs() は負でない整数を返し、それ以外の場合は EOF を返します。

fwrite()

fwrite() は、より大きなデータ ブロックを一度に書き込むために使用されます。その主な目的は、配列データを一度にファイルに書き込むことであり、バイナリ データの書き込みに適しています。そのプロトタイプは stdio.h で定義されます。

size_t fwrite(
  const void* ptr、
  size_t サイズ、
  size_t nmemb、
  ファイル*fp
);

4 つのパラメータを受け入れます。

  • ptr: 配列ポインタ。
  • size: 各配列メンバーのサイズ (バイト単位)。
  • nmemb: 配列メンバーの数。
  • fp: 書き込み先のファイルポインタ。

fwrite() プロトタイプの最初のパラメータの型は、型なしのポインタである void* であることに注意してください。コンパイラは、パラメータ ポインタを void* 型に自動的に変換します。 fwrite() は配列メンバーの型を知らないため、各メンバーのサイズ (2 番目のパラメーター) とメンバーの数 (3 番目のパラメーター) を知る必要があります。

fwrite() 関数の戻り値は、正常に書き込まれた配列メンバーの数です (バイト数ではないことに注意してください)。通常、戻り値は第 3 パラメータの nmemb ですが、書き込みエラーが発生して一部のメンバのみが書き込まれた場合、戻り値は nmemb より小さくなります。

配列 arr 全体をファイルに書き込むには、次の書き込み方法を使用できます。

fwrite(
  ああ、
  sizeof(arr[0]),
  sizeof(arr) / sizeof(arr[0]),
  FP
);

上記の例では、「sizeof(a[0])」は各配列メンバーが占めるバイト数、「sizeof(a) / sizeof(a[0])」は配列全体のメンバーの数です。

次の例では、サイズ 256 バイトの文字列をファイルに書き込みます。

文字バッファ[256];

fwrite(バッファ, 1, 256, fp);

上の例では、配列 buffer の各メンバーは 1 バイトで、合計 256 のメンバーがあります。 fwrite() は連続メモリコピーなので、fwrite(buffer, 256, 1, fp) と書くことでも目的を達成できます。

fwrite() は配列全体を書き込む必要があるとは規定していません。配列の一部のみを書き込むことも可能です。

あらゆる種類のデータは 1 バイト データの配列、または 1 つのメンバーの配列として見ることができるため、fwrite() は実際には配列だけでなくあらゆる種類のデータを書き込むことができます。たとえば、fwrite() は Struct 構造をファイルに書き込んで保存できます。

fwrite(&s, sizeof(s), 1, fp);

上記の例では、「s」は Struct 構造体ポインタであり、メンバーの配列とみなすことができます。 s の属性にポインタが含まれている場合は、ポインタを保存しても意味がない可能性があるため、保存するときに注意する必要があります。復元したときに、ポインタが指すデータがまだ存在するという保証はありません。

後で紹介する fwrite()fread() は、書き込まれたデータを解釈しないため、バイナリ データの読み書きに適しています。バイナリ データには、C 言語の文字列の終了マークであるヌル文字「\0」が含まれる場合があるため、バイナリ ファイルの読み取りおよび書き込みは、テキストの読み取りおよび書き込み関数 (「fprintf()」など) の使用には適していません。 。

以下はバイナリ ファイルへの書き込みの例です。

#include <stdio.h>

int main(void) {
  ファイル* fp;
  unsigned char bytes[] = {5, 37, 0, 88, 255, 12};

  fp = fopen("output.bin", "wb");
  fwrite(バイト, sizeof(char), sizeof(バイト), fp);
  fclose(fp);
  0を返します。
}

上記の例では、バイナリ ファイルを書き込むときに、fopen()wb モードで開き、バイナリ書き込みを示す必要があります。 fwrite() はデータをシングルバイト配列として解釈できるため、その 2 番目のパラメータは sizeof(char)、3 番目のパラメータは配列 sizeof(bytes) の合計バイト数です。

上の例で記述したファイル output.bin を 16 進エディタで開くと、次の内容になります。

05 25 00 58 ff 0c

fwrite() はファイルにデータを継続的に書き込むこともできます。

struct clientData myClient = {1, 'foo bar'};

for (int i = 1; i <= 100; i++) {
  fwrite(&myClient, sizeof(struct clientData), 1, cfPtr);
}

上記の例では、fwrite() は 100 個のデータをファイルに連続的に書き込みます。

fread()

fread() 関数は、ファイルから大きなデータ ブロックを一度に読み取るために使用されます。その主な目的は、バイナリ データの読み取りに適した配列にファイルの内容を読み取ることです。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

サイズ_t フレッド(
  void* ptr、
  size_t サイズ、
  size_t nmemb、
  ファイル*fp
);

fwrite() とまったく同じように、4 つのパラメータを受け取ります。

  • ptr: 配列アドレス。
  • size: 各配列メンバーのサイズ (バイト単位)。
  • nmemb: 配列のメンバーの数。
  • fp: ファイルポインタ。

ファイルの内容を配列 arr に読み込むには、次のような書き込み方法を使用できます。

フレッド(
  ああ、
  sizeof(arr[0]),
  sizeof(arr) / sizeof(arr[0]),
  FP
);

上記の例では、配列の長さ (2 番目のパラメーター) と各メンバーのサイズ (3 番目のパラメーター) の積が、配列によって占有されるメモリ空間のサイズになります。 fread() はファイルから同じサイズの内容を読み取り (4 番目のパラメータ)、次に ptr (最初のパラメータ) でこれらの内容のメモリ アドレスを指します。

次の例では、ファイルの内容を倍精度浮動小数点数の 10 メンバー配列に読み取ります。

収益が倍増[10]。
fread(収益, sizeof(double), 10, fp);

上記の例では、各配列メンバーのサイズは sizeof(double) で、メンバーが 10 個の場合、sizeof(double) * 10 のサイズがファイル fp から読み取られます。

fread() 関数の戻り値は、正常に読み取られた配列メンバーの数です。通常の場合、戻り値は第 3 パラメータの nmemb ですが、読み取りエラーが発生したり、ファイルの終端まで読み込んだ場合、戻り値は nmemb より小さくなります。したがって、fread() の戻り値を確認することは非常に重要です。

fread()fwrite() は併用できます。プログラムが終了する前に、fwrite() を使用してデータをファイルに保存し、次にプログラムを実行するときに fread() を使用してデータをメモリに復元します。

以下は、前のセクションで生成されたバイナリ ファイル output.bin を読み取る例です。

#include <stdio.h>

int main(void) {
  ファイル* fp;
  符号なし文字 c;

  fp = fopen("output.bin", "rb");
  while (fread(&c, sizeof(char), 1, fp) > 0)
    printf("%d\n", c);
  0を返します。
}

実行後、次の結果が得られます。

5
37
0
88
255
12

##feof()

feof() 関数は、ファイルの内部ポインタがファイルの終わりを指しているかどうかを判断します。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int feof(FILE *fp);

feof() は引数としてファイルポインタを受け取ります。ファイルの終わりに達した場合は、ゼロ以外の値 (true を意味) が返され、それ以外の場合は 0 (false を意味) が返されます。

fgetc() などのファイル読み取り関数が EOF を返した場合、ファイルの終わりを読み取った可能性と、読み取りエラーが発生した可能性があります。 feof() を使用すると、どの状況であるかを判断できます。

以下は、feof() を使用してファイルの終わりに到達したかどうかを判断し、ループでファイル全体を読み取る例です。

整数;
文字名[50];

FILE* cfPtr = fopen("clients.txt", "r");

while (!feof(cfPtr)) {
  fscanf(cfPtr, "%d%s\n", &num, name);
  printf("%d %s\n", 番号, 名前);
}

fclose(cfPtr);

上記の例では、ループを使用して feof() がファイルの終わりに到達したかどうかを判断し、それによってファイルの内容全体を読み取ります。

feof() が true の場合、fseek()rewind()、および fsetpos() 関数を通じてファイルの内部読み取りおよび書き込み位置のインジケーターを変更して、ファイルのステータスをクリアできます。この機能。

fseek()

各ファイル ポインタには、現在開かれているファイルの読み書き位置 (ファイル位置)、つまり次の読み書きが開始される位置を記録する内部ポインタ (内部ポインタ) があります。ファイル操作関数 (getc()fgets()fscanf()fread() など) はすべて、このインジケーターで指定された位置から順番にファイルの読み取りと書き込みを行います。

このインジケーターを変更してファイル内の特定の場所に移動したい場合は、fseek() 関数を使用できます。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int fseek(FILE* ストリーム、long int オフセット、int whence);

fseek() は 3 つのパラメータを受け取ります。

  • stream: ファイルポインタ。
  • offset: ベースからのバイト数 (3 番目のパラメータ)。型は long int で、正 (ファイルの最後に移動)、負 (ファイルの先頭に移動)、または 0 (そこに留まる) のいずれかになります。
  • whence: 計算の開始点を決定するために使用される位置データ。その値は次の 3 つのマクロ (stdio.h で定義) です: SEEK_SET (ファイルの先頭)、SEEK_CUR (内部ポインタの現在位置)、SEEK_END (ファイルの末尾)

以下の例を参照してください。

// ファイルの先頭までロケートします
fseek(fp, 0L, SEEK_SET);

// ファイルの末尾に配置
fseek(fp, 0L, SEEK_END);

// 現在の位置から 2 バイト戻ります
fseek(fp, 2L, SEEK_CUR);

// ファイルの 10 番目のバイトを検索します
fseek(fp, 10L, SEEK_SET);

// ファイルの最後から 10 バイト目を検索します
fseek(fp, -10L, SEEK_END);

上の例では、 fseek() の第二引数がlong型なので、移動距離に接尾辞「L」を付けてlong型に変換する必要があります。

次の例では、ファイルのすべてのバイトを逆に出力します。

for (カウント = 1L; カウント <= サイズ; count++) {
  fseek(fp, -count, SEEK_END);
  ch = getc(fp);
}

fseek() はバイナリ ファイルを操作する場合にのみ使用するのが最適であり、テキスト ファイルを読み取る場合には使用しないことに注意してください。テキスト ファイルでは文字のエンコーディングが異なるため、場所の正確なバイト位置を簡単に判断することはできません。

通常の状況では、fseek() の戻り値は 0 です。エラーが発生した場合 (ファイルのスコープを超えた移動など)、戻り値は 0 以外 (-1 など) になります。

ftell()

ftell() 関数は、ファイル内のポインターの現在位置を返します。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

long int ftell(FILE* ストリーム);

ファイルポインタをパラメータとして受け取ります。戻り値はlong型の整数で、内部ポインタの現在位置、つまりファイルの先頭から現在位置までのバイト数を表し、「0」はファイルの先頭を表します。エラーが発生した場合、ftell()-1L を返します。

ftell()fseek() と組み合わせて使用​​できます。最初に内部ポインタの位置を記録し、一連の操作の後に fseek() を使用して元の位置に戻ります。

長いファイル位置 = ftell(fp);

//一連のファイル操作の後
fseek(fp, file_pos, SEEK_SET);

次の例では、まずファイルの末尾にポインターを配置し、次にファイルの先頭から末尾までのバイト数を取得します。

fseek(fp, 0L, SEEK_END);
サイズ = ftell(fp);

巻き戻し()

rewind() 関数は、ファイルの先頭へのファイル内部ポインタを返します。そのプロトタイプは stdio.h で定義されます。

void rewind(ファイル*ストリーム);

ファイルポインタをパラメータとして受け取ります。

rewind(fp) は基本的に fseek(fp, 0l, seen_set) と同等です。唯一の違いは、rewind() には戻り値がなく、現在のファイルのエラーインジケータをクリアすることです。

fgetpos()、fsetpos()

fseek()ftell() に関する潜在的な問題は、どちらもファイル サイズを long int 型が表現できるサイズに制限してしまうことです。これは非常に大きいように見えますが、32 ビット マシンでは、long int は 4 バイトの長さで、最大 4GB の範囲を表すことができます。ストレージ デバイスの容量が急速に増加するにつれて、ファイルはますます大きくなり、多くの場合この範囲を超えます。これを考慮して、C 言語には、大きなファイルを処理するための 2 つの新しい位置決め関数、fgetpos()fsetpos() が追加されました。

それらのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int fgetpos(FILE* ストリーム, fpos_t* pos);
int fsetpos(FILE* ストリーム, const fpos_t* pos);

fgetpos() 関数は、ファイル内のポインタの現在位置をポインタ変数 pos に保存します。この関数は 2 つのパラメータを受け入れます。1 つ目はファイル ポインタ、2 つ目はポインタの位置を格納する変数です。

fsetpos() 関数は、ファイル内のポインタの位置を、ポインタ変数 pos で指定されたアドレスに移動します。変数 posfgetpos() メソッドを呼び出して取得する必要があることに注意してください。 fsetpos()fgetpos() の 2 つのパラメータは同じでなければなりません。

ファイル内の内部ポインタの位置を記録するポインタ変数 pos は、タイプ fpos_t* (ファイル ポジション タイプの略で、ファイル位置決めタイプを意味します) です。整数である必要はなく、Struct 構造体であっても構いません。

以下に使用例を示します。

fpos_t ファイル_pos;
fgetpos(fp, &file_pos);

//一連のファイル操作の後
fsetpos(fp, &file_pos);

上記の例では、最初に fgetpos() を使用して内部ポインタの位置を取得し、次に fsetpos() を使用してポインタの位置を復元します。

実行が成功すると、fgetpos()fsetpos() は両方とも 0 を返しますが、それ以外の場合は 0 以外の値を返します。

ferror()、clearerr()

すべてのファイル操作関数の実行に失敗した場合、エラーステータスがファイルポインタに記録されます。後続の操作でエラー インジケーターが読み取られる限り、前の操作が間違っていたことがわかります。

ferror() 関数は、エラー インジケーターのステータスを返すために使用されます。この関数を使用すると、前のファイル操作が成功したかどうかを判断できます。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int ferror(FILE *stream);

ファイルポインタをパラメータとして受け取ります。前の操作でエラーが発生した場合、ferror() はゼロ以外の整数 (true を示す) を返し、それ以外の場合は 0 を返します。

clearerr() 関数はエラーインジケータをリセットするために使用されます。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

void clearerr(FILE* fp);

ファイル ポインタをパラメータとして受け取り、戻り値はありません。

以下に例を示します。

FILE* fp = fopen("file.txt", "w");
char c = fgetc(fp);

if (ferror(fp)) {
  printf("ファイルの読み取り中にエラーが発生しました: file.txt\n");
}

クリアラー(fp);

上記の例では、fgetc() は「書き込みモード」で開かれたファイルを読み取ろうとしますが、読み取りに失敗すると EOF が返されます。このとき、ferror() を呼び出すと、前の操作が間違っていたことがわかります。処理後は clearerr() を使用してエラーステータスをクリアしてください。

ファイル操作関数が正常に実行された場合、ferror()feof() は両方とも 0 を返します。実行が異常な場合は、何が問題だったのかを判断する必要があります。

if (fscanf(fp, "%d", &n) != 1) {
  if (ferror(fp)) {
    printf("io エラー\n");
  }
  if (feof(fp)) {
    printf("ファイルの終わり\n");
  }

  クリアラー(fp);

  fclose(fp);
}

上記の例では、fscanf() 関数がエラーを報告したときに、ferror()feof() をチェックして、どのような問題が発生したかを判断します。これら 2 つのインジケーターは状態が変化しても変更されないため、clearerr() を使用して両方のインジケーターを同時にクリアする必要があります。

取り除く()

remove() 関数は、指定されたファイルを削除するために使用されます。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int 削除(const char* ファイル名);

ファイル名をパラメータとして受け取ります。 remove() は、削除が成功した場合は 0 を返し、それ以外の場合は 0 以外の値を返します。

削除("foo.txt");

上の例では、foo.txt ファイルを削除します。

ファイルの削除は、ファイルを閉じたときに行う必要があることに注意してください。ファイルが fopen() で開かれた場合は、削除する前に fclose() で閉じる必要があります。

名前を変更()

rename() 関数は、ファイルの名前を変更したり、ファイルを移動したりするために使用されます。そのプロトタイプはヘッダー ファイル stdio.h で定義されます。

int rename(const char* old_filename, const char* new_filename);

2 つのパラメータを受け入れます。最初のパラメータは現在のファイル名で、2 番目のパラメータは新しいファイル名です。名前の変更が成功した場合、rename()0 を返し、それ以外の場合は 0 以外の値を返します。

rename("foo.txt", "bar.txt");

上の例では、foo.txt の名前を bar.txt に変更します。

名前を変更したファイルには、既存のファイルと同じ名前を付けることはできないことに注意してください。また、名前を変更するファイルがすでに開いている場合は、まずそのファイルを閉じてから、開いているファイルの名前を変更する必要があります。

以下はファイルの移動例です。

rename("/tmp/evidence.txt", "/home/beej/nothing.txt");

作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0