C言語ファイル操作
1. ファイルとは何ですか?
ディスク上のファイルはファイルです。
コンピュータ ファイルはファイルの一種であり、通常のファイル キャリアとは異なり、コンピュータのハードディスクをキャリアとして使用してコンピュータに保存される情報の集合です。
*ファイルを使用する理由
ファイルを使用すると、データをコンピュータのハード ドライブに直接保存し、データの永続性を実現できます。
——しかし、プログラミングでは、通常、プログラム ファイルとデータ ファイルという 2 種類のファイルについて話します。
プログラムファイル
ソース プログラム ファイル (接尾辞 .c)、ターゲット ファイル (Windows 環境では接尾辞 .obj)、および実行可能プログラム (Windows 環境では接尾辞 .exe) が含まれます。
データファイル
ファイルの内容は必ずしもプログラムではなく、プログラムの実行中にデータを読み取る必要があるファイルやコンテンツを出力するファイルなど、プログラムの実行中に読み書きされるデータです。
2. ファイル名
ユーザーがファイルを識別して参照できるように、ファイルには一意のファイル識別子が必要です。
ファイル名には、ファイル パス + ファイル名トランク + ファイル接尾辞 の 3 つの部分が含まれます
。例: c:\code\test.txt
。便宜上、ファイル識別子はファイル名と呼ばれることがよくあります。
3. ファイルの種類
データの構成方法に応じて、データ ファイルはテキスト ファイルまたはバイナリ ファイルと呼ばれます。
データはメモリ内にバイナリ形式で保存されており、そのまま外部メモリに出力した場合はバイナリファイルとなります。
外部ストレージに ASCII コードで保存する必要がある場合は、保存前に変換する必要があります。ASCII 文字の形式で保存されたファイルはテキスト ファイルです。
データはどのようにメモリに保存されるのでしょうか?
文字は常に ASCII 形式で保存され、数値データは ASCII 形式またはバイナリ形式で保存できます。
たとえば、整数 10000 がある場合、ASCII コード形式でディスクに出力すると、ディスク上で 5 バイト (1 文字につき 1 バイト) を占有しますが、バイナリ形式で出力すると、ディスク上で 5 バイトを占有します。
ディスク上で占有するのは 4 バイトだけです。
コードテスト: (ここで理解できない場合は、以下を読んでファイルポインタを理解できます)
#include <stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf = NULL;
return 0;
}
実行結果:(バイナリ化けているコード)
情報から見つけた方法で解釈できます:
結果表示:
4. ファイルバッファ
ASIC 標準では、データ ファイルの処理に「バッファ ファイル システム」が使用されています。いわゆるバッファ ファイル システムとは、システムがプログラムで使用されるファイルごとにメモリ内に「ファイル バッファ」を自動的に作成することを意味します。
メモリからディスクに出力されたデータは、まずメモリ内のバッファに送られ、バッファがいっぱいになった後にまとめてディスクに送られます。
ディスクからコンピュータにデータを読み取る場合、データはディスク ファイルから読み取られてメモリ バッファに入力され (バッファがいっぱいになります)、その後データがバッファからプログラム データ領域 (プログラム変数、など)一つ一つ。バッファのサイズは C コンパイル システムによって決定されます。
コード例:
#include <stdio.h>
#include <windows.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
//注:fflush 在高版本的VS上不能使用了
printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}
ここでは、fflush の役割を理解することに重点を置く必要があります。fflush
が最初に 10 秒間実行を開始したとき、開いているファイルにはデータがありません。
プログラムの実行を 10 秒間待機すると、次の結果が得られます。
5. ファイルポインタ(ポイント)
バッファ ファイル システムでは、重要な概念は「ファイル ポインタ」と呼ばれる「ファイル タイプ ポインタ」です。
使用される各ファイルは、メモリ内の対応するファイル情報領域を開き、ファイル関連情報 (ファイル名、ファイルのステータス、ファイルの現在の場所など) を保存します。
この情報は構造体変数に保存されます。構造タイプはシステムによって宣言され、FILE という名前が付けられます。
たとえば、VS2013 コンパイル環境によって提供される stdio.h ヘッダー ファイルには次のファイル タイプ宣言があります。
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
}; typedef struct _iobuf FILE;
異なる C コンパイラの FILE タイプの内容はまったく同じではありませんが、似ています。
ファイルが開かれるたびに、システムはファイルの状態に基づいて FILE 構造に変数を自動的に作成し、そこに情報を入力します。ユーザーは詳細を気にする必要はありません
。
一般に、この FILE 構造体の変数は FILE ポインタを通じて維持されるため、より使いやすくなります。
以下では、FILE* のポインター変数を作成できます。
FILE* pf;//文件指针变量
pf を FILE 型データを指すポインタ変数として定義します。
pf に特定のファイルのファイル情報領域を指すようにすることができます(構造体変数です)。
ファイル情報領域の情報を介してファイルにアクセスできます。言い換えれば、それに関連付けられたファイルは
、ファイル ポインター変数を通じて見つけることができます。例えば:
ファイルの開閉
ファイルは読み取りまたは書き込み前に開き、使用後は閉じる必要があります。
プログラムを作成するときにファイルが開かれると、そのファイルを指す FILE* ポインター変数が返されます。これは、ポインターとファイルの間の関係を確立することと同じです。
ASIC では、ファイルを開くには fopen 関数を使用し、ファイルを閉じるには fclose を使用することが規定されています。
FILE * fopen ( const char * filename, const char * mode );
int fclose ( FILE * stream );
次のように開きます。
コード例:
/* fopen fclose example */
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile = fopen ("myfile.txt","w");
if (pFile!=NULL)
{
fputs ("fopen example",pFile);
fclose (pFile);
}
return 0;
}
ファイルのアドレスを開くには 2 つの方法があります。
相対パス
例えば:
pFile = fopen ("myfile.txt","w");
pFile = fopen (".\\myfile.txt","w");//当前目录
pFile = fopen ("..\\myfile.txt","w");//上一级目录
絶対パス
例えば:
pFile = fopen ("C:nser\\Admin\\desktop\\myfile.txt","w");
ファイルのシーケンシャルな読み取りと書き込み(困難)
ストリームから文字を取得する
ストリームに文字を書き込む
ストリームから文字列を取得する
文字列をストリームに書き込む
コード例:
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a'+i, stdout);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
ここで、fputc はテキスト ファイル data.txt にデータを保存します。凡例:
fgetc の簡単なアプリケーションもあります。
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(stdin);
printf("%c\n", ch);
ch = fgetc(stdin);
printf("%c\n", ch);
ch = fgetc(stdin);
printf("%c\n", ch);
ch = fgetc(stdin);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgetc は前の data.txt テキスト ファイルから文字を読み取り、読み取るたびに次の文字をポイントして、次の図のように順番に読み取ります。
fputs コード例:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件 - 写一行
fputs("hello bit\n", pf);
fputs("hello xiaobite\n", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs は、読み取られた 2 つの文字列を同じ行に出力します
。data.txt の内容を次のように変更します。
fgets は 2 番目の文字列から指定されたサイズの文字列を読み取ります。コード例:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//读文件 - 读一行
char arr[10] = {
0 };
fgets(arr, 10, pf);
printf("%s", arr);
fgets(arr, 10, pf);
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
コードの実行結果:
一連の関数を比較します。
scanf/fscanf/sscanf
フォーマットされたデータを標準入力から読み取る
フォーマットされたデータをストリームから読み取る
文字列からフォーマットされたデータを読み取る
printf/fprintf/sprintf
フォーマットされたデータを標準出力に出力する
フォーマットされたデータをストリームに書き込む
フォーマットされたデータを文字列に書き込む
fprintf コード例:
#include<stdio.h>
struct S
{
int a;
float s;
};
int main()
{
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
struct S s = {
100, 3.14f };
fprintf(pf, "%d %f", s.a, s.s);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
コードの実行結果:
fscanf コードの例:
#include<stdio.h>
struct S
{
int a;
float s;
};
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
struct S s = {
0};
fscanf(pf, "%d %f", &(s.a), &(s.s));
fprintf(stdout, "%d %f", s.a, s.s);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
sprintf コードの例:
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
char arr[30] = {
0 };
struct S s = {
100, 3.14f, "hehe" };
sprintf(arr, "%d %f %s", s.a, s.s, s.str);
printf("%s\n", arr);
return 0;
}
実行結果:
sscanf コード例:
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
char arr[30] = {
0 };
struct S s = {
100, 3.14f, "hehe" };
struct S tmp = {
0};
sprintf(arr, "%d %f %s", s.a, s.s, s.str);
sscanf(arr, "%d %f %s", &(tmp.a), &(tmp.s), tmp.str);
printf("%d %f %s\n", tmp.a, tmp.s, tmp.str);
return 0;
//}
コードの実行結果:
データ ブロックをストリームに書き込む
ストリームからデータのチャンクを読み取る
fwrite コードのデモ:
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
struct S s = {
99, 6.18f, "bit" };
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s, sizeof(struct S), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
コードの実行結果:
これはバイナリ結果であるため、表示が文字化けしますが、fread を呼び出して
コードのデモを読むことができます。
#include<stdio.h>
struct S
{
int a;
float s;
char str[10];
};
int main()
{
struct S s = {
0 };
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fread(&s, sizeof(struct S), 1, pf);
printf("%d %f %s\n", s.a, s.s, s.str);
fclose(pf);
pf = NULL;
return 0;
}
操作結果:
ファイルのランダムな読み取りと書き込み
fseek は、
ファイル ポインタの位置とオフセットに基づいてファイル ポインタを見つけます。
ストリーム位置インジケーターの位置を変更する
例:
まず、data.txt のデータを以下のように変更します。
/* fseek example */
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针到f
fseek(pf, 5, SEEK_SET);//从起始位置开始
int ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
コードの実行結果:
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针到f
fseek(pf, -4, SEEK_END);//从末尾位置开始
int ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
操作結果:
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针到f
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
実行結果:
ストリーム内の現在位置を取得します (オフセットを計算します)。
コード例:
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针到f
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
int pos = ftell(pf);
printf("%d\n", pos);
fclose(pf);
pf = NULL;
return 0;
}
操作結果:
ストリームの位置を先頭に設定します(開始位置に戻ります)
コードデモ:
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//定位文件指针到f
int ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);//a
fclose(pf);
pf = NULL;
return 0;
}
操作結果:
6. ファイル終了の判定
間違って使用されたfeof
留意事項: ファイル読み取りプロセス中、feof 関数の戻り値を直接使用してファイルが終了したかどうかを判断することはできません。
代わりに、ファイルの読み取りが終了したときに、読み取りが失敗したか、ファイルの終わりに達したかを判断するために使用されます。
- テキストファイルの読み込みが完了したかどうかは、戻り値が EOF (fgetc) か NULL (fgets) かを判定します (例:
fgetc
は EOF かどうかを判定します。fgets
は戻り値が NULL かどうかを判定します)。- バイナリファイルの読み込み終了判定は、戻り値が実際に読み込む数値より小さいかどうかで判定します。例: fread は、戻り値が実際に読み取られる数値より小さいかどうかを判断します。
正しい使い方:
テキストファイルの例
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
バイナリファイルの例
#include <stdio.h>
enum {
SIZE = 5 };
int main(void)
{
double a[SIZE] = {
1.0,2.0,3.0,4.0,5.0};
double b = 0.0;
size_t ret_code = 0;
FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组
fclose(fp);
fp = fopen("test.bin","rb");
// 读 double 的数组
while((ret_code = fread(&b, sizeof(double), 1, fp))>=1)
{
printf("%lf\n",b);
}
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
fclose(fp);
fp = NULL;
}
ここで、ファイルをコピーするための別のコードを紹介します。
//拷贝文件
//拷贝data1.txt 文件,产生一个新的文件data2.txt
#include<stdio.h>
int main()
{
FILE* pfRead = fopen("data1.txt", "r");//打开data1.txt
if (pfRead == NULL)
{
perror("open file for read");
return 1;
}
FILE* pfWrite = fopen("data2.txt", "w");//打开data2.txt
if (pfWrite == NULL)
{
perror("open file for write");
fclose(pfRead);
pfRead = NULL;
return 1;
}
//读写文件
int ch = 0;
while ((ch = fgetc(pfRead)) != EOF)//将data1.txt中的代码依次复制到data2.txt中
{
fputc(ch, pfWrite);
}
//关闭文件
fclose(pfRead);
pfRead = NULL;
fclose(pfWrite);
pfWrite = NULL;
return 0;
}
要約する
つまり、この章のファイル操作の内容は非常に複雑なので、注意深く学習して、できるだけ早くマスターできることを願っています。