以前勉強していたところ、プログラムの実行が終了すると以前に保存したデータが消えてしまい、再度実行する際に再度入力しなければならないことが わかりました。保存したデータを次回使えるようにするにはファイル操作を使用してデータをファイルに保存する方法については、この記事で紹介します。
目次
1. ファイルとは何ですか
ディスク(ハードディスク)上のファイルはファイルです
しかし、プログラミングでは、通常、プログラム ファイル、データ ファイルという2 種類のファイルについて話します。
プログラムファイル
ソースプログラムファイル(拡張子は.c)、ソースプログラムをコンパイルして生成されるターゲットファイル(Windows環境の場合、拡張子は.obj)、実行可能プログラム(拡張子は.exe)を含みます。 Windows環境の場合)。
データファイル
ファイルの内容は必ずしもプログラムではなく、プログラムがデータを読み取る必要があるファイルや内容を出力するファイルなど、プログラムの実行中に読み書きされるデータです。たとえば、.txt ファイルが作成されます。
この記事では、データ ファイルに対する関連操作について説明します。
これまでの章で扱ったデータの入出力はすべて端末を対象としており、端末のキーボードからデータが入力され、実行結果が端末の画面に表示されます。
実際には、情報をディスクに出力し、必要に応じてディスクからデータをメモリに読み込んで使用することもあり、ここでディスク上のファイルが処理されます。
2. ファイル名
ファイルには、ユーザーの識別と参照のために一意のファイル識別子が必要です
ファイル名は、ファイルパス + ファイル名トランク + ファイルサフィックスの 3 つの部分で構成されます。
例: c:\code\test.txt
便宜上、ファイル ID はファイル名と呼ばれることがよくあります。
3. ファイルポインタ
ファイル操作を学ぶときの重要な概念は「ファイルタイプポインタ」、つまり「ファイルポインタ」です。
使用された各ファイルは、メモリ内の対応するファイル情報領域を開きます。この領域は、ファイルの関連情報 (ファイル名、ファイルのステータス、ファイルの現在の場所など) を保存するために使用されます。 )。この情報は構造体変数に保存されます。構造タイプはシステムによって宣言され、 FILE という名前が付けられます。
たとえば、 VS2008 コンパイル環境によって提供されるstdio.hヘッダー ファイルには、次のファイル タイプ宣言があります。
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
ここで知っておく必要があるのは、FILE が構造体であることだけであり、ファイルの関連情報を格納するだけで十分です。
異なる C コンパイラの FILE タイプの内容はまったく同じではありませんが、似ています。
ファイルを開くたびに、システムがファイルの状況に応じてFILE構造の変数を自動的に作成し、情報を埋めていくため、ユーザーは詳細を気にする必要がありません。
一般に、この FILE 構造体の変数は、FILE ポインタを介して維持されるため、使用するのがより便利です。
以下では、FILE* ポインター変数を作成できます。
FILE* pf;//文件指针变量
pf を FILE 型データを指すポインタ変数として定義します。pf に特定のファイルのファイル情報領域を指すようにすることができます(構造体変数です)。ファイル情報領域の情報を介してファイルにアクセスできます。つまり、それに関連付けられたファイルは、ファイル ポインタ変数を通じて見つけることができます。
図:
4. ファイルの開閉
ファイルは読み取りおよび書き込み前に開いておく必要があり、使用後はファイルを閉じる必要があります。
プログラムをプログラミングするとき、ファイルを開くときに、ファイルを指す FILE* ポインター変数が返されます。これは、ポインターとファイルの間の関係を確立することと同じです。
ASIC では、ファイルを開くにはfopen 関数を使用し、ファイルを閉じるにはfcloseを使用することが規定されています。
FILE* fopen(const char* filename, const char* mode);
返回文件信息区的地址 文件名 打开方式
int fclose (FILE* stream);
要关闭的文件流 也就是文件信息区的地址
次のように開きます
ファイルの使用状況 | 意味 | 指定したファイルが存在しない場合 |
「r」(読み取り専用) | データを入力するには、既存のファイルを開きます | うまくいかない |
「w」(書き込みのみ) | データを入力するには、テキスト ファイルを開きます | 新しいファイルを作成する |
「a」(追加) | テキストファイルの末尾にデータを追加します | うまくいかない |
「rb」(読み取り専用) | データを入力するには、バイナリ ファイルを開きます | うまくいかない |
「wb」(書き込みのみ) | データを入力するには、バイナリ ファイルを開きます | 新しいファイルを作成する |
「ab」(追加) | バイナリファイルの末尾にデータを追加する | うまくいかない |
「r+」(読み取りおよび書き込み) | テキスト ファイルを開いて読み取りと書き込みを行う | うまくいかない |
「w+」(読み取りおよび書き込み) | 読み取りおよび書き込み用の新しいファイルを作成します | 新しいファイルを作成する |
「a+」(読み取りおよび書き込み) | ファイルの最後で読み取りと書き込みを行うためにファイルを開きます。 | 新しいファイルを作成する |
「rb+」(読み取りおよび書き込み) | バイナリ ファイルを読み書き用に開きます。 | うまくいかない |
「wb」(読み取りおよび書き込み) | 読み取りおよび書き込み用の新しいバイナリ ファイルを作成します | 新しいファイルを作成する |
「ab+」(読み取りおよび書き込み) | バイナリ ファイルを開き、ファイルの最後で読み取りと書き込みを行う | 新しいファイルを作成する |
注:ファイルを書き込み用に開くと (「w」を含む)、元のファイルの内容は破壊されます。
サンプルコード:
#include<stdio.h>
int main()
{
//fopen返回文件信息区的地址,通过文件信息区操作文件
//打开文件
//打开成功,放回一个指向FILE类型的指针
//打开失败,返回空指针
FILE* pf = fopen("test.txt", "r");//这里文件是相对路径,也可以用绝对路径
if (pf == NULL)
{
//打开失败
perror("fopen:");//打印错误信息
return 1;
}
//写文件
//....
//关闭文件
fclose(pf);
pf = NULL;//置空
return 0;
}
5. ファイルのシーケンシャル読み取りおよび書き込み
シーケンシャルな読み取りと書き込みとは、ファイルの読み取りまたは書き込み中に、ファイル ポインターが自動的に後方に移動するため、ループを使用してファイル全体の読み取り/書き込みができることを意味します。
関数:
関数 | 関数名 | に適用する |
文字入力機能 | fgetc | すべての入力ストリーム |
文字出力機能 | fputc | すべての出力ストリーム |
テキスト行入力機能 | fgets | すべての入力ストリーム |
テキスト行出力機能 | fputs | すべての出力ストリーム |
フォーマット入力機能 | fscanf | すべての入力ストリーム |
フォーマット出力機能 | fprintf | すべての出力ストリーム |
バイナリ入力 | 恐れる | 書類 |
バイナリ出力 | fwrite | 書類 |
I/O/読み取りと書き込みはメモリに関連します
例:
int fgetc ( FILE * stream );
int fputc ( int character, FILE * stream );
一度に 1 文字のスペースを読み取り/書き込みし、成功した場合は文字の ASCII コード値を返し、失敗した場合は EOF を返します。
//写文件
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (fopen == NULL)
{
perror("fopen");
return 1;
}
//写文件
//把26个字母写到文件中
for(int i=0;i<26;i++)
{
fputc('a'+i, pf);//写一个字符
}
fclose(pf);
pf = NULL;
return 0;
}
//读文件
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (fopen == NULL)
{
perror("fopen");
return 1;
}
//读文件
//读一个字符,返回躲到字符的ASCII码值
//读取失败,或者读到文件末尾 返回EOF -1
for(int i=0;i<26;i++)
{
int ch = fgetc(pf);
//读取完一个会让文件内容指针向后移动,可以读取全部数据,不会只读a
//与pf++不同,pf指向的是整个文件信息区
printf("%c", ch);
}
fclose(pf);
pf = NULL;
return 0;
}
char * fgets ( char * str, int num, FILE * stream );
int fputs ( const char * str, FILE * stream );
一度に 1 行のスペースの読み取り/書き込みを行い、成功した場合は 1 行の文字を保存した配列アドレスを返し、失敗した場合は NULL を返します。
//fputs 写一行数据
#include<stdio.h>
int main()
{
//打开文件
FILE*pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写一行数据 hello world
fputs("hello world\n",pf);
fputs("xilanhua\n", pf);//会追加
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//fgets 读一行数据
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//默认在程序所在工程目录下生成
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读一行数据
//char* fgets(char* str,int num, FILE* stream);
//将读到的字符串拷贝放到 str字符数组中,
//然后最多读num-1 个,因为第num个放 '\0'
char arr[20];
fgets(arr, 5, pf);//读一行,读到 '\n' 为止
printf("%s\n", arr);//hell
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
フォーマットされた読み取りと書き込み
int fprintf ( FILE * stream, const char * format, ... );
...可变参数列表
int fscanf ( FILE * stream, const char * format, ... );
fprintf と fscanf および printf と scanf のパラメータ リスト部分には、より多くの FILE* streamがあることがわかります。
例:
//fprintf 写文件
#include<stdio.h>
struct S
{
int n;
float f;
char arr[20];
};
int main()
{
struct S s = { 100,3.14f,"zhangsan" };
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%d %f %s", s.n, s.f, s.arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//读文件
struct S
{
int n;
float f;
char arr[20];
};
int main()
{
struct S s;
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf, "%d %f %s", &(s.n), &(s.f), &(s.arr));
printf("%d %f %s", s.n, s.f, s.arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
sprintf と sscanf も fprintf と fscanf に似ており、前者はデータとファイルの間の操作であり、後者はデータと文字列の間の操作です。
int sprintf ( char * str, const char * format, ... );
int sscanf ( const char * s, const char * format, ...);
- sscanf : フォーマットされたデータを文字列に書き込みます
- sprintf : 文字列内のフォーマットされたデータを読み出します
例:
#include<stdio.h>
struct S
{
int n;
float f;
char arr[20];
};
int main()
{
struct S s = { 200,3.5f,"wangwu" };
//把一个结构体转换为字符串
char arr[50] = { 0 };
sprintf(arr, "%d %f %s\n", s.n, s.f, s.arr);
printf("字符串的数据:%s\n", arr);//以字符串的形式打印
//把一个字符串 转换为 对应的格式化数据
struct S tmp = { 0 };
sscanf(arr, "%d %f %s", &tmp.n, &tmp.f, &tmp.arr);
printf("格式化的数据:%d %f %s\n", tmp.n, tmp.f, tmp.arr);
return 0;
}
上記の関数はすべての入力/出力ストリームに対して機能します。ストリームとは何を意味しますか:
流れは水の流れとして理解でき、データ送信は水のように流れ、データをそこに入れる/取り込むことができます。
ファイル、画面、ネットワーク、その他の外部デバイス、ストリームは外部デバイスと対話する方法を知っているため、ストリームを操作するだけで済みます。
ファイルの読み書き時:ファイルストリーム
C 言語プログラムはデフォルトでこれら 3 つのストリームを開きます
- 端末デバイス画面の標準出力ストリーム stdout
- キーボードの標準入力ストリーム stdin
- 画面の標準エラー ストリーム stderr
stdout stdin stderr はすべて FILE* へのポインタです
したがって、キーボードを開いたり閉じたりする代わりに、ファイルを開いたり閉じたりする必要があります。
fputc を使用して標準入力ストリームからデータを読み取る
//标准输入流
#include<stdio.h>
int main()
{
//从标准输入流读
int ch = fgetc(stdin);
printf("%c", ch);
return 0;
}
//标准输出流
#include<stdio.h>
int main()
{
fputc('a', stdout);//标准输出流,打印到屏幕上
return 0;
}
2 レベルの読み取りおよび書き込みファイル:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
例:
//写文件
#include<stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { "张三",20,95.5f };
FILE* pf = fopen("test.dat", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s, sizeof(struct S), 1, pf);
//字符串的二级制形式与文本的形式是相同的
//整数和浮点型数据的二级制与文本形式是不同的
//之前的函数使用字符的形式保存的,是认识的
//现在是不认识的
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//读文件
#include<stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("test.dat", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(struct S), 1, pf);//返回值是实际成功读到的元素得个数
printf("%s %d %f", s.name, s.age, s.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
6. ファイルのランダムな読み取りと書き込み
ファイル ポインタのポインティングとオフセットを変更することで、ファイルのランダムな読み取りを実現できます。主に以下の3つの機能を使います。
fseek :
- ファイルの位置とオフセットに基づいてファイル ポインタを定義します。
int fseek ( FILE * stream, long int offset, int origin );
フテル:
- 開始位置を基準としたファイル ポインタのオフセットを返します。
long int ftell ( FILE * stream );
巻き戻し:
- ファイルポインタの位置をファイルの先頭に戻します
void rewind ( FILE * stream );
例:
#include<stdio.h>
int main()
{
//文件中已保存内容为abcdefg
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
ch = fgetc(pf);//读取完,文件指针向后移
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
ch = fgetc(pf);
printf("%c\n", ch);//d
printf("%d\n", ftell(pf));//返回文件指针想对于文件起始位置的偏移量
//我们使用fseek函数打印b
fseek(pf, -3, SEEK_CUR);//文件指针当前位置
ch = fgetc(pf);
printf("%c\n", ch); //b
fseek(pf, 1, SEEK_SET);//文件指针起始位置
ch = fgetc(pf);
printf("%c\n", ch); //b
fseek(pf, -6, SEEK_END);//文件指针末尾位置
ch = fgetc(pf);
printf("%c\n", ch); //b
rewind(pf);//让文件指针返回到起始位置
printf("%d\n", ftell(pf));//0
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
7. ファイルの種類
データの構成方法に応じて、データ ファイルはテキスト ファイルまたはバイナリ ファイルと呼ばれます。
データはメモリ上にバイナリ形式で保存されており、変換せずに外部ストレージに出力するとバイナリファイルになります。
外部ストレージにASCII コード形式で保存する必要がある場合は、保存前に変換する必要があります。ASCII 文字の形式で保存されたファイルはテキスト ファイルです。データはどのようにしてメモリに保存されるのでしょうか?
文字はすべて ASCII 形式で保存され、数値データはASCII 形式またはバイナリ形式で保存できます。
整数 10000 がある場合、ASCII コード形式でディスクに出力すると、ディスク上で 5 バイト (1 文字につき 1 バイト) を占有し、バイナリ形式で出力した場合は、ディスク上で 5 バイトしか占有しません。ディスクセクションに 4 ワード (int 型) (VS2022 でテスト済み)。
保管方法
1. 2層システムストレージ
コード例:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen");//打印错误信息
return 1;
}
//写文件
int a = 10000;
fwrite(&a, sizeof(int), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
ファイルを開いたところ、次のことがわかりました。
ファイルはテキスト モードで開くため、バイナリ システムの形式ではありません。既存の項目 test.txt を追加することで、開くモードを変更できます。
見える
これはリトルエンディアンストレージであるため、この効果がわかります。
2. fprintf を使用してデータを保存します (ASCII コード値形式)
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");//打印错误信息
return 1;
}
//写文件
int a = 10000;
fprintf(pf,"%d",a);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
結果
ここに格納されるのは、文字「1」「0」「0」「0」「0」に対応するASCIIコード値(16進数)です。
したがって、テキスト形式で開くと 10000 が表示されます。
8. ファイルの終了判定
feof を誤って使用すると
、ファイルの読み取りプロセス中に、feof 関数の戻り値を使用してファイルが終了したかどうかを直接判断できないことに注意してください。代わりに、ファイルの読み取りが終了したときに適用され。
1. テキストファイルの読み込みが終了したか、戻り値がEOF(fgetc)かNULL(fgets)かを判定
例えば:
- getc は EOF かどうかを判断します。
- fgets は戻り値が NULL かどうかを判断します。
2. バイナリファイルの読み込み終了を判定し、戻り値が実際に読み込む数値より小さいかどうかを判定します。
例えば:
- fread は戻り値が実際に読み込む数値より小さいかどうかを判断します。
これらの判定の条件はすべてこれらの関数の戻り値に関係します。
読み取り関数の別の関数:
読み取り関数がエラーステータスの読み取り、戻り、設定を同時に失敗した場合、ferror を使用してこのステータスを検出します。
ファイルの終わりに到達し、戻り、同時に状態を設定します。feof を使用してこの状態を検出します
- ファイルの終わりフラグに遭遇したときに終了する読み取り操作の場合、feof はゼロ以外の値を返します。
- ferror 読み取り失敗 (エラー) が発生したときに終了する読み取り操作の場合は、ゼロ以外の値を返します。
例:
#include<stdio.h>
int main()
{
//文件内部abcdef
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ",ch);
}
if (feof(pf))//检查是否有这样的状态设置,有返回非0
{
printf("遇到文件结束标志而结束\n");
}
else if(ferror(pf))
{
printf("遇到文件错误为结束");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
9. ファイルバッファ
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);//刷新缓冲区,才将输入缓冲区的数据写到文件(磁盘)
printf("在睡眠10秒—此时,再打开test.txt文件,文件有内容了");
Sleep(10000);
fclose(pf);//fcolse 在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}
fflush はバッファーをアクティブに更新するため、バッファー内のデータがディスクに書き込まれます。更新後、データがファイル内に存在し、そのファイルは過去 10 秒間存在していなかったことがわかります。
fclose はバッファーもリフレッシュするため、プログラムの実行を終了する前に、データがファイルに入っていることがわかります。
記事の終わり