目录
1. 为什么使用文件
2. 什么是文件
3. 文件的使用
文件指针
文件的打开和关闭
三个标准的输入/输出流:
4. 文件的顺序读写
对字符操作:
fputc:
fgetc:
练习复制整个文件:
对字符串操作:
fputs:
fgets:
fprintf:
fscanf:
辨析sprintf&sscanf:
对二进制操作:
fwrite:
fread:
5. 文件的随机读写
fseek:
ftell:
rewind:
6. 文本文件和二进制文件
7. 文件读取结束的判定
被错误使用的feof
顺便介绍一下ferror :
8. 文件缓冲区
小结
1. 为什么使用文件
数据存储:文件是一种持久性的数据存储方式。程序可以将数据写入文件,以便在程序关闭后仍然保留数据。这对于保存用户设置、应用程序状态、日志信息等非常有用。
数据交换:文件允许不同程序之间的数据交换。数据可以写入文件,然后由另一个程序读取并处理。这在数据导入和导出、数据备份和还原等方面非常常见。
长期存储:文件允许数据长期存储,以供将来使用。这对于文档、照片、音频和视频等媒体文件非常有用。
除此之外还有很多文件的用途,在这就不一一列举了。
2. 什么是文件
3. 文件的使用
文件指针
缓冲文件系统中,关键的概念是 “ 文件类型指针 ” ,简称 “ 文件指针 ” 。当你使用fopen
函数打开一个文件时, 被打开的文件在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE .
比如:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
- 不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
- 每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
- 一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE * pf ;—— 文件指针变量定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
文件的打开和关闭
文件在读写之前应该先 打开文件 ,在使用结束之后应该 关闭文件 。在编写程序的时候,在打开文件的同时,都会返回一个 FILE* 的指针变量指向该文件,也相当于建立了指针和文件的关系。ANSIC 规定使用 fopen 函数来打开文件, fclose 来关闭文件。当你使用fclose
函数关闭文件时,相应的文件信息区将被释放,并且文件指针将不再指向该文件,这也是确保释放文件资源的重要步骤之一。//打开文件 FILE * fopen ( const char * filename, const char * mode ); //关闭文件 int fclose ( FILE * stream );
不管是使用 "w"
还是 "w+"
,如果想要创建的文件存在,它将被清空并覆盖,不会再创建一个新的 "data.txt" 文件。
举例来看一下文件操作:
未运行代码时,此目录下没有 data.txt 文件。
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行代码后,以写入模式("w")打开data.txt 。如果文件已经存在,它将被覆盖。如果文件不存在,它将被创建。函数返回一个指向该文件的 FILE
指针,该指针被存储在 pf
中。
if (pf == NULL)
:这是一个条件语句,检查文件是否成功打开。如果文件打开失败,fopen
函数会返回NULL指针,所以这里使用pf == NULL
来检查是否成功。
如果文件打开失败,perror
函数会打印出与最后一次发生的错误相关的错误消息。在这里,它会打印出与"fopen"相关的错误消息。
fclose(pf)
:关闭文件流,确保数据被写入"data.txt"文件中。
pf = NULL
:将文件指针pf
设置为NULL,以避免在之后的代码中意外使用已关闭的文件流。
三个标准的输入/输出流:
读写文件的时候,需要 打开文件,读写文件,关闭文件。
大家有没有注意到我们在使用scanf和printf没有听过说打开键盘、打开屏幕这种操作,默认直接可以操作。
因为C语言程序,只要运行起来,就默认打开三个流。
- 标准输入流 stdin
- 标准输出流 stdout
- 标准错误流 stderr
以上三个流的类型都是 FILE*
**标准输入流 stdin
**:
stdin
是程序的标准输入流,通常与键盘输入相关联。- 当程序需要从用户获取输入时,它可以使用
stdin
来读取用户输入。例如,使用scanf
函数可以从stdin
中读取用户输入的数据。- 在终端环境中,你可以通过键盘输入数据,这些输入将被传递到程序的
stdin
流中。stdin
默认情况下是缓冲输入的,这意味着用户输入的数据通常会在按下 Enter 键后才被程序读取。
**标准输出流 stdout
**:
stdout
是程序的标准输出流,通常与终端显示相关联。- 当程序需要向用户显示输出时,它可以使用
stdout
来输出信息。例如,使用printf
函数可以将信息输出到stdout
流中,以便在终端上显示。- 在终端环境中,
stdout
通常是标准输出窗口,可以在其中查看程序的输出。stdout
也可以重定向到文件,这意味着程序的输出可以保存到文件中而不是显示在终端上。
**标准错误流 stderr
**:
stderr
是程序的标准错误流,通常与错误和警告信息相关联。- 当程序遇到错误或需要显示警告信息时,它可以使用
stderr
来输出这些信息。通常,错误消息应该输出到stderr
而不是stdout
,以便能够与标准输出区分开来。- 与
stdout
类似,stderr
也可以重定向到文件,以便将错误信息记录到文件中以供后续分析。
这三个标准流使程序能够与用户进行交互、显示输出、记录错误,并具有灵活的输入/输出重定向功能,以满足不同的运行环境和需求。在大多数操作系统和终端环境中,这些流默认已经存在, 因此你可以在 C 语言程序中使用它们,而不需要额外的配置。
4. 文件的顺序读写
对字符操作:
fputc:
int fputc(int character, FILE *stream);
character
:要写入文件的字符,通常以整数形式传递,因为fputc
处理字符的ASCII码值。stream
:指向要写入的文件流的指针,通常通过fopen
打开文件后获得。fputc
函数将字符character
写入到由stream
指定的文件中,并返回写入的字符。- 如果写入失败,则返回一个表示错误的值 EOF。通常情况下,
fputc
会以一个字节的形式写入字符到文件,因此它主要用于处理文本文件。
#include <stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
程序未运行前 :
程序运行后:
我们使用循环写入文件:
#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, pf);
}
fclose(pf);
pf = NULL;
return 0;
}
在每次循环中,fputc
函数将字符写入标准输出流(stdout)。字符'a'加上i的值会产生'a'到'z'的字母序列,并将每个字符写入data文件。
程序运行后:
下面代码使用了fputc
函数来将字符写入标准输出流(stdout)。标准输出流(stdout)是一个特殊的文件流,它通常用于将程序的输出信息显示在终端或命令行窗口上。
#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
函数将字符写入标准输出流(stdout)。字符'a'加上i的值会产生'a'到'z'的字母序列,并将每个字符输出到终端。
成功打印到屏幕上:
fgetc:
int fgetc(FILE *stream);
stream
:指向要读取的文件流的指针,通常通过fopen
打开文件后获得。
fgetc
函数从文件流 stream
中读取一个字符,并将其作为整数返回。如果成功读取一个字符,它会返回该字符的 ASCII 码值(0 到 255之间的整数),然后指针指向的位置自动向后移动一位, 如果到达文件的末尾(EOF),则返回一个特殊的值 EOF
(通常被定义为 -1
)来表示文件结束或读取错误。
举例如下:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
如果从键盘获取字符,则将fgets参数换成标准输入流:stdin。
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;
}
输入hello world后读取到四个字符:
练习复制整个文件:
我们准备将 data1.txt 中的文本复制到 data2.txt 中。
int main()
{
FILE* pfRead = fopen("data1.txt", "r");
if (pfRead == NULL)
{
perror("open file for read");
return 1;
}
FILE* pfWrite = fopen("data2.txt", "w");
if (pfWrite == NULL)
{
perror("open file for write");
fclose(pfRead);
pfRead = NULL;
return 1;
}
//读写文件
int ch = 0;
while ((ch = fgetc(pfRead)) != EOF)
{
fputc(ch, pfWrite);
}
//关闭文件
fclose(pfRead);
pfRead = NULL;
fclose(pfWrite);
pfWrite = NULL;
return 0;
}
打开data2我们可以看到,成功实现将 data1的文本复制到data2中:
对字符串操作:
fputs:
int fputs(const char *str, FILE *stream);
str
:要写入文件的字符串,通常以字符数组或字符串常量的形式传递。stream
:指向要写入的文件流的指针,通常通过fopen
打开文件后获得。
fputs
函数会将字符串 str
写入到由 stream
指定的文件中,直到字符串的结束符(null终止字符 \0
)为止。它不会在字符串末尾添加额外的换行符('\n'
),因此如果您想在每次写入后换行,需要显式添加 '\n'
到 str
中。
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;
}
fgets:
char *fgets(char *str, int n, FILE *stream);
str
:一个指向字符数组的指针,用于存储读取到的文本数据。通常,您需要提前声明一个足够大的字符数组来存储读取的数据。fgets
会将读取的数据存储到这个数组中。n
:要读取的最大字符数,包括字符串结尾的 null 终止字符。这个参数可以防止fgets
读取过多的数据,从而导致缓冲区溢出。stream
:指向要读取的文件流的指针,通常通过fopen
打开文件后获得。
fgets
函数会从文件流 stream
中读取字符,直到满足以下条件之一:
- 读取了
n-1
个字符。 - 遇到换行符('\n')。
- 到达文件的末尾(EOF)。
一旦满足上述任何条件,fgets
就会停止读取字符,并将读取的字符存储在 str
指向的字符数组中。如果成功读取一行数据,fgets
会在字符串的末尾添加一个 null 终止字符('\0'),以确保字符串正确终止。
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\n", arr);
fgets(arr, 10, pf);
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fprintf:
int fprintf(FILE *stream, const char *format, ...);
stream
:指向要写入的文件流的指针,通常通过fopen
打开文件后获得。format
:一个格式字符串,类似于printf
中的格式字符串,它指定了要写入的数据的格式和位置。...
:可变数量的参数,根据format
字符串中的格式说明符,用来提供要写入的数据。
fprintf
函数将按照 format
字符串中的格式说明符,将格式化的数据写入到文件流 stream
中,并返回写入的字符数,如果写入失败则返回EOF(-1)。
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:
int fscanf(FILE *stream, const char *format, ...);
stream
:指向要读取数据的文件流的指针,通常通过fopen
打开文件后获得。format
:一个格式字符串,类似于scanf
中的格式字符串,它指定了要从文件中读取的数据的格式和位置。...
:可变数量的参数,根据format
字符串中的格式说明符,用来接收从文件中读取的数据。
fscanf
函数会按照 format
字符串中的格式说明符,从文件流 stream
中读取数据,并将数据存储到提供的参数中。函数返回成功读取的参数数量,如果读取失败则返回EOF(-1)。
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;
}
上一次使用fprintf成功写入文件:
读取后储存到结构体变量s中,使用fprintf将数据打印到屏幕上。
输出结果:
辨析sprintf&sscanf:
scanf:从标准输入流读取格式化的数据
printf:向标准输出流写入格式化的数据
fscanf:适用于所有输入流的格式输入函数
fprintf:适用于所有输出流的格式化输出函数
sscanf:从字符串中读取格式化的数据
sprintf:将格式化的数据转换成字符串
我们来举例看一下sprintf函数怎么使用:
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);
printf("%s\n", arr);
return 0;
}
sprintf函数用于将格式化的数据写入到一个字符串中。
int sprintf(char *str, const char *format, ...);
输出结果:
我们来看一下在内存中的 arr:
我们来举例看一下sscanf函数怎么使用:
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;
}
sscanf函数用于从一个字符串中按照指定的格式读取数据,并将读取到的数据存储到变量中。
int sscanf(const char *str, const char *format, ...);
输出结果:
对二进制操作:
fwrite:
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
ptr
:指向要写入数据的内存块的指针。size
:每个数据项的大小(以字节为单位)。count
:要写入的数据项的数量。stream
:指向文件的指针,该文件将接收写入的数据。
fwrite
的工作原理是将数据从内存中的 ptr
指针指向的位置复制到文件指针 stream
所指向的文件中。它会写入指定数量的数据项,每个数据项的大小由 size
参数指定。写入的数据项数量由 count
参数指定。
fwrite
返回一个 size_t
类型的值,表示成功写入的数据项数量。如果返回值等于 count
,则表示写入操作成功完成。如果返回值小于 count
,则可能意味着磁盘空间不足或发生了其他写入错误。
举例看一下:
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;
}
运行程序后打开data.txt文件:
不用担心,我们的数据是以二进制形式存入文件的,
我们想要验证是否存入我们的数据可以通过 fread 让机器帮我们读懂,然后输出看一下存入数据。
接着我们来学习 fread 函数
fread:
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
ptr
:指向接收读取数据的内存块的指针。size
:每个数据项的大小(以字节为单位)。count
:要读取的数据项的数量。stream
:指向要读取的文件的指针。
fread
从文件指针 stream
所指向的文件中读取数据,并将数据存储到内存中的 ptr
指针指向的位置。它会读取指定数量的数据项,每个数据项的大小由 size
参数指定。读取的数据项数量由 count
参数指定。
fread
返回一个 size_t
类型的值,表示成功读取的数据项数量。如果返回值等于 count
,则表示读取操作成功完成。如果返回值小于 count
,则可能意味着文件结束或者发生了读取错误。
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;
}
输出结果:
struct S s = { 99, 6.18f, "bit" };
这次通过机器帮我们读懂文件中储存的二进制形式并输出到屏幕上,这样我们就看懂了写入的数据,与想要写入的数据对比,我们成功写入数据。
对了,如果你忘了6.18f这种形式,可以看看这篇文章解释C语言中 6.18f (浮点数常量后缀)。
5. 文件的随机读写
fseek:
int fseek(FILE *stream, long offset, int origin);
stream
是指向已打开文件的文件指针。
offset
是要移动的字节数,可以是正数、负数或零,具体取决于origin
参数。
origin
是一个整数值,用于指定相对于文件的哪个位置移动文件指针。它可以采用以下值之一:
SEEK_SET
:从文件的开头位置开始偏移。SEEK_CUR
:从当前文件指针位置开始偏移。SEEK_END
:从文件的末尾位置开始偏移。
fseek
的工作原理如下:
- 当你调用
fseek
时,它会将文件指针移动到指定的位置。 - 如果
origin
是SEEK_SET
,则文件指针将被设置为offset
指定的字节位置,相对于文件的开头。 - 如果
origin
是SEEK_CUR
,则文件指针将从当前位置开始移动offset
字节。 - 如果
origin
是SEEK_END
,则文件指针将从文件末尾位置开始移动offset
字节。
fseek
返回一个整数值,通常用于检查是否移动文件指针成功。如果成功,它返回0;如果出现错误,它返回非零值,你可以使用 errno
变量进一步了解错误的类型。
我们来看一个例子:
提前在文件data.txt中写入:后续例子中都要使用这个文件。
然后运行以下代码:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
fseek(pf, 5, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
成功读取到 pf 内偏移为5字节的字符:
下个例子我们从文件当前位置开始偏移:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
fseek(pf, 3, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
运行完两个fgetc之后,当前光标位置在第二个字符c,从字符c开始向后偏移三个字符,光标移动到 f 前,读取到 f ,输出结果:
,
下个例子我们从文件末尾开始偏移:
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
fseek(pf, -6, SEEK_END);
int ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
从末尾偏移六个字符光标正好停在 f 前面,输出结果:
ftell:
long ftell(FILE *stream);
stream
是指向已打开文件的文件指针。
ftell
返回一个 long
类型的整数值,表示当前文件指针相对于文件开头的偏移量。如果发生错误,它会返回 -1L
,并设置 errno
来指示错误的类型。
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
int ch = ftell(pf);
printf("%d\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
rewind:
void rewind(FILE *stream);
stream
是指向已打开文件的文件指针。
rewind
函数不返回任何值,它只是简单地将文件指针移动到文件的开头,使其指向文件的第一个字节位置。
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
6. 文本文件和二进制文件
- 根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
- 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
- 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
字符一律以 ASCII 形式存储,数值型数据既可以用 ASCII 形式存储,也可以使用二进制形式存储。如有整数 10000 ,如果以 ASCII 码的形式输出到磁盘,则磁盘中占用 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;
}
将text.txt添加到源文件中,但是直接打开text.txt会显示这样 :
解决办法:右键选择打开方式,我们使用VS的二进制编辑器打开。
我们可以看到以十六进制小端形式储存在文件中10000的正确显示:
7. 文件读取结束的判定
被错误使用的feof
int feof(FILE *stream);
stream
是指向已打开文件的文件指针。
feof
返回一个整数值,如果文件流 stream
已经到达了文件的末尾,则返回非零值(通常是1),否则返回0。
牢记:在文件读取过程中,不能用 feof 函数的返回值直接用来判断文件的是否结束。而是在我们已经知道发生错误的时候, 应用于当文件读取结束时,判断是读取失败结束,还是遇到文件尾结束 。
- 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("\nI/O error when reading");
else if (feof(fp))
puts("\nEnd of file reached successfully");
fclose(fp);
return 0;
}
顺便介绍一下ferror :
ferror
是一个标准C库函数,用于检查文件流的错误标志。它允许你检测文件操作是否发生了错误。通常,当文件操作遇到问题时,文件流的错误标志会被设置,你可以使用ferror
来检查这些标志,以确定是否发生了错误。
ferror
函数的基本语法如下:
int ferror(FILE *stream);
stream
是指向已打开文件的文件指针。
ferror
返回一个整数值,如果文件流 stream
的错误标志已经被设置(表示发生了错误),则返回非零值(通常是1),否则返回0。
再来看一个关于二进制文件的例子:
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
if (ret_code == SIZE) {
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
}
else { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
最后,检查 fread
的返回值 ret_code
是否等于 SIZE
,以确保成功读取了预期数量的数据。如果成功读取,就输出数组的内容。如果出现错误,通过 feof
和 ferror
函数来检查文件是否结束或发生了其他错误,并进行相应的错误处理。
8. 文件缓冲区
下面这个例子我希望大家可以自己试验一下哈。 通过这个例子可以更好地理解缓冲区的概念。
#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;
}
小结
希望这篇文章可以帮助你学习和复习文件相关知识,切记!!一定要动手操作!!
代码的理解从敲代码开始哦。只有自己实践过,知识才是属于你的!!!