当前位置: 首页 > news >正文

详解C语言—文件操作

目录

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语言程序,只要运行起来,就默认打开三个流。

  1. 标准输入流 stdin   
  2. 标准输出流 stdout
  3. 标准错误流 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 中读取字符,直到满足以下条件之一:

  1. 读取了 n-1 个字符。
  2. 遇到换行符('\n')。
  3. 到达文件的末尾(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 函数的返回值直接用来判断文件的是否结束。
而是在我们已经知道发生错误的时候, 应用于当文件读取结束时,判断是读取失败结束,还是遇到文件尾结束
1. 文本文件读取是否结束,判断返回值是否为 EOF fgetc ),或者 NULL fgets
例如:
  • fgetc 判断是否为 EOF .
  • fgets 判断返回值是否为 NULL .
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
  • fread判断返回值是否小于实际要读的个数。

我们来看这个例子:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{int c; // 注意:int,非char,要求处理EOFFILE* fp = fopen("test.txt", "r");if (!fp) {perror("File opening failed");return EXIT_FAILURE;}//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOFwhile ((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 handlingif (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. 文件缓冲区

ANSIC 标准采用 缓冲文件系统 处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“ 文件缓冲区 。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据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;
}

小结

希望这篇文章可以帮助你学习和复习文件相关知识,切记!!一定要动手操作!!

代码的理解从敲代码开始哦。只有自己实践过,知识才是属于你的!!!

相关文章:

详解C语言—文件操作

目录 1. 为什么使用文件 2. 什么是文件 3. 文件的使用 文件指针 文件的打开和关闭 三个标准的输入/输出流&#xff1a; 4. 文件的顺序读写 对字符操作&#xff1a; fputc&#xff1a; fgetc&#xff1a; 练习复制整个文件&#xff1a; 对字符串操作&#xff1a;…...

IntelliJ IDEA 常用快捷键一览表

目录 1-IDEA的日常快捷键 第1组&#xff1a;通用型 第2组&#xff1a;提高编写速度&#xff08;上&#xff09; 第3组&#xff1a;提高编写速度&#xff08;下&#xff09; 第4组&#xff1a;类结构、查找和查看源码 第5组&#xff1a;查找、替换与关闭 第6组&#xff1a…...

cola 架构简单记录

cola 是来自张建飞&#xff08;Frank&#xff09;的偏实现的技术架构&#xff0c;里面的业务身份和扩展点也被MEAF引用&#xff0c;cola本身由java 实现、但其实可以是一种企业通用的技术架构。 业务身份来源 https://blog.csdn.net/significantfrank/article/details/8578556…...

FFmpeg常用结构体分析

目录 1.AVFormatConext 2.AVInputFormat 3.AVStream 4.AVCodecContext 5.AVPacket 6.AVCodec 7.AVFrame 8.AVIOContext 9.URLProtocol 10.URLContext 1.AVFormatConext AVFormatConext是一个贯穿全局地数据结构&#xff0c;AVFormatConext结构包含很多信息&#xff0c…...

ChatGPT 学习笔记 | 什么是 Prompt-tuning?

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 Prompt-tuning is an efficient, low-cost way of adapting an AI foundation model to new downstream tasks without retraining the model and upd…...

[红明谷CTF 2021]write_shell %09绕过过滤空格 ``执行

目录 1.正常短标签 2.短标签配合内联执行 看看代码 <?php error_reporting(0); highlight_file(__FILE__); function check($input){if(preg_match("/| |_|php|;|~|\\^|\\|eval|{|}/i",$input)){ 过滤了 木马类型的东西// if(preg_match("/| |_||php/&quo…...

JVM学习笔记

JVM学习笔记 复习之前学的内容&#xff0c;同时补充以下知识点&#xff1a;JVM的双亲委派机制、伊甸区与老年代相关知识&#xff1b; 双亲委派机制 首先介绍Java中的类加载器 Java中的类加载器 Bootstrap ClassLoader&#xff08;启动类加载器&#xff09;&#xff0c;默认…...

使用 gst-element-maker 创建一个完全透传的 videofilter 插件

系列文章目录 创建 gstreamer 插件的几种方式 使用 gst-template 创建自己的 gstreamer 插件 使用 gst-plugins-bad 里面的 gst-element-maker 工具创建gstreamer 插件 使用 gst-element-maker 创建一个完全透传的 videofilter 插件 文章目录 系列文章目录前言一、使用gst-ele…...

华为ensp单臂路由及OSPF实验

单臂路由及OSPF实验 1.1实验背景 在这个实验中&#xff0c;我们模拟了一个复杂的网络环境&#xff0c;该网络环境包括多个子网和交换机。这个实验旨在帮助网络工程师和管理员了解如何配置单臂路由和使用开放最短路径优先&#xff08;OSPF&#xff09;协议来实现不同子网之间的…...

Android LiveData 介绍

Android LiveData 介绍 系列文章目录前言一、LiveData是什么&#xff1f;二、简单使用依赖测试数据准备1.创建可观察的livedata2.观察它3.更新它 总结 系列文章目录 Android LiveData 介绍&#xff08;本文&#xff09; 前言 本系列根据官网介绍Jetpack中的数据通信组件&…...

好看的货架效果(含3D效果)

搭配thymeleaf layui合成 货架一 1. css #gudinghuojia2F .layui-row { display: flex; justify-content: space-between; height: 100%;} #gudinghuojia2F .layui-col-xs10 {margin-right: 4%;} #gudinghuojia2F .layui-col-xs10:last-child {margin-right: 0;} .inner-ti…...

【每日一题】1498. 满足条件的子序列数目

1498. 满足条件的子序列数目 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums 和一个整数 target 。 请你统计并返回 nums 中能满足其最小元素与最大元素的 和 小于或等于 target 的 非空 子序列的数目。 由于答案可能很大&#xff0c;请将结果对 109 7 取余后…...

Go语言数据类型实例讲解 - Go语言从入门到实战

Go语言数据类型实例讲解 - Go语言从入门到实战 基础数据类型 bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte rune float32 float64 complex64 complex128类型描述bool布尔型&#xff08;bool&#xff09;&#xff1a;可以是true或f…...

RocketMQ 事务消息发送

目录 事务消息介绍 应用场景 功能原理 使用限制 使用示例 使用建议​ 事务消息介绍 在一些对数据一致性有强需求的场景&#xff0c;可以用 RocketMQ 事务消息来解决&#xff0c;从而保证上下游数据的一致性。 应用场景 分布式事务的诉求 分布式系统调用的特点为一个核…...

后端-POST请求中只需要两个参数,后端不想创建对象时

问题&#xff1a; 在前后端对接中&#xff0c;很多前端的规范是POST&#xff0c;参数放body里面&#xff0c;媒体类型是json&#xff0c;后端就需要用RequestBody去接收&#xff0c;但是后端只用接收两个对象&#xff0c;这时候后端不想创建对象&#xff0c;使用RequestParm&a…...

UG\NX二次开发 通过点云生成曲面 UF_MODL_create_surf_from_cloud

文章作者:里海 来源网站:《里海NX二次开发3000例专栏》 感谢粉丝订阅 感谢 Rlgun 订阅本专栏,非常感谢。 简介 有网友想做一个通过点云生成曲面的程序,我们也试一下 效果 代码 #include "me.hpp" /*HEAD CREATE_SURF_FROM_CLOUD CCC UFUN */...

Linux常用指令(二)

目录 一、 删除空目录&#xff08;rmdir&#xff09; 二、ln 硬链接与软链接 三、新建空文件或更新文件的时间戳&#xff08;touch&#xff09; 四、比较文件内容的差异&#xff08;diff&#xff09; 五、显示当前时间或设置系统时间&#xff08;date&#xff09; 六、显…...

【HUAWEI】单臂路由

目录 ​ &#x1f96e;写在前面 &#x1f96e;2.1、拓扑图 &#x1f96e;2.2、操作思路 &#x1f96e;2.3、配置操作 &#x1f363;2.3.1、LSW4配置 &#x1f363;2.3.2、R2配置 &#x1f363;2.3.3、测试网络 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &…...

安全学习_开发相关_Java第三方组件Log4jFastJSON及相关安全问题简介

文章目录 JNDI&#xff1a;(见图) Java-三方组件-Log4J&JNDILog4J&#xff1a;Log4j-组件安全复现使用Log4j Java-三方组件-FastJsonFastJson&#xff1a;Fastjson-组件安全复现对象转Json(带类型)Json转对象Fastjson漏洞复现&#xff08;大佬文章 JNDI&#xff1a;(见图) …...

零代码编程:用ChatGPT批量自动下载archive.org上的音频书

http://archive.org 是一个神奇的网站&#xff0c;可以下载各种古旧的软件、书籍、音频、视频&#xff0c;还可以搜索各个网站的历史网页。 比如说&#xff0c;一些儿童故事音频就可以在http://archive.org下载到&#xff0c;可以用来做英语听力启蒙用。 举个例子&#xff0c…...

力扣用队列实现栈

自己写的栈&#xff0c;再让其他函数去调用自己写的栈 typedef int QDataType; typedef struct QueueNode {struct QueueNode* next;//单链表QDataType data;//放数据 }QNode;typedef struct Queue {QNode* phead;//头节点QNode* ptail;//尾节点QDataType size; //统计有多少节…...

一朵华为云,如何做好百模千态?

点击关注 文丨刘雨琦、郝鑫 2005年华为提出网络时代的“All IP”&#xff0c;2011年提出数字化时代的“All Cloud”&#xff0c;2023年提出智能时代的“All Intelligence”。 截至目前&#xff0c;华为的战略升级经历了三个阶段。 步入智能化&#xff0c;需要迎接的困难依然…...

华为云云耀云服务器L实例评测 | 实例使用教学之软件安装:华为云云耀云服务器环境下安装 Docker

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之软件安装&#xff1a;华为云云耀云服务器环境下安装 Docker 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云…...

小程序编译器性能优化之路

作者 | 马可 导读 小程序编译器是百度开发者工具中的编译构建模块&#xff0c;用来将小程序代码转换成运行时代码。旧版编译器由于业务发展&#xff0c;存在编译慢、内存占用高的问题&#xff0c;我们对编译器做了一次大规模的重构&#xff0c;采用自研架构&#xff0c;做了多线…...

FFmpeg 命令:从入门到精通 | ffmpeg 命令分类查询

FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg 命令分类查询 FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg 命令分类查询ffmpeg -versionffmpeg -buildconfffmpeg -formatsffmpeg -muxersffmpeg -demuxersffmpeg -codecsffmpeg -decodersffmpeg -encodersffmpeg -bsfsffmpeg…...

Linux学习记录——삼십일 socket编程---TCP套接字

文章目录 TCP套接字简单通信1、服务端1、基本框架2、获取连接 2、客户端3、多进程4、多线程5、线程池6、简单的日志系统7、守护进程8、其它 TCP套接字简单通信 本篇gitee 学习完udp套接字通信后&#xff0c;再来看TCP套接字。 四个文件tcp_server.hpp&#xff0c; tcp_serve…...

【学习笔记】深度学习分布式系统

深度学习分布式系统 前言1. 数据并行&#xff1a;参数服务器2. 流水线并行&#xff1a;GPipe3. 张量并行&#xff1a;Megatron LM4. 切片并行&#xff1a;ZeRO5. 异步分布式&#xff1a;PATHWAYS总结参考链接 前言 最近跟着李沐老师的视频学习了深度学习分布式系统的发展。这里…...

【数据结构】树、二叉树的概念和二叉树的顺序结构及实现

目录 前言&#xff1a;一、树的概念及结构1.树的概念2.树的相关概念3.树的存储4.树在实际中的运用 二、二叉树概念及结构1.概念2.特殊的二叉树&#xff08;1&#xff09;满二叉树&#xff08;2&#xff09;完全二叉树 3.二叉树的性质4.二叉树的存储(1)顺序存储(2)链式存储 三、…...

rust学习-string

介绍 A UTF-8–encoded, growable string(可增长字符串). 拥有string内容的所有权 A String is made up of three components: a pointer to some bytes, a length, and a capacity. The length is the number of bytes currently stored in the buffer pub fn as_bytes(&…...

No167.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…...

北京网站建设公司册/电商运营入门基础知识

路径&#xff1a;在顶部菜单栏小眼睛按钮 全局变量跟环境变量作用域不同。全局变量不受当前环境的影响&#xff0c;只要是在同一个项目下&#xff0c;该变量都会生效 环境变量和全局变量的调用方法都是一样的 {{变量名}} 定义 目前通过脚本定义全局变量&#xff08;由预/后执…...

人大网站信息化建设报告/网站维护收费标准

1、概述 过去&#xff0c;程序员通常以像素为单位设计计算机用户界面。例如&#xff1a;图片大小为8032像素。这样处理的问题在于&#xff0c;如果在一个每英寸点数&#xff08;dpi&#xff09;更高的新显示器上运行该程序&#xff0c;则用户界面会显得很小。在有些情况下&…...

wordpress支付表单/数据分析软件工具有哪些

1、加挂硬盘Shell代码 sudo hdparm -I /dev/sdb 硬盘硬件安装后&#xff0c;此命令测试linux系统是否能找到挂载的未分区硬盘sudo hdparm -I /dev/sdb 硬盘硬件安装后&#xff0c;此命令测试linux系统是否能找到挂载的未分区硬盘2、创建分区Shell代码 sudo fdisk /dev/sd…...

仿网站的ppt怎么做/网上有免费的网站吗

不是狗托不是狗托不是狗托&#xff01; 只是用着Nice&#xff0c;推荐一波 这是官网链接&#xff0c;直接点这里 敲黑板&#xff0c;看重点 1、极致的时间管理&#xff0c;值得拥有 2、独立下载安装&#xff0c;没有全家桶的捆绑 先来介绍一下它的主要功能 1、 一键自动文件…...

招网站建设人员/推广方式营销方案

韩霞闻言心中一颤&#xff0c;试探的问道&#xff1a;“怎么了&#xff1f;为什么不取了&#xff1f;” 林萧看母亲的表情&#xff0c;就知道母亲肯定误解了自己的意思&#xff0c;出言解释道&#xff1a;“放心吧&#xff0c;妈。我没反悔。只是我觉得这一家有点不太靠谱&…...

浙江建设特种证书查询/优化神马排名软件

什么是RootMessageId&#xff1f; 为了理解RootMessageId先简单介绍一下CAT的数据结构设计。CAT客户端会将所有消息都封装为一个完整的消息树&#xff08;MessageTree&#xff09;&#xff0c;消息树可能包括Transaction、Event、Heartbeat、Metric等类型的消息。具体如下&…...