二十三、文件操作

👉 欢迎阅读这篇文章 👇

1、为什么使用文件

如果没有文件,我们写的程序是存储在电脑内存中的,当程序退出了以后,内存回收,数据就会丢失,等再次运行程序,看不到上次程序的数据,如果要将数据进行持久化的保存,我们就可以使用文件

2、什么是文件

磁盘(硬盘)上的文件是文件。

在程序设计中,从文件功能的角度来看,文件一般有两种:程序文件、数据文件。

2.1程序文件

程序文件包括源程序文件(后缀为.c),目标文件(windows后缀为.obj),可执行程序(windows后缀为.exe)。

2.2数据文件

文件的内容不一定是程序,而是程序运行的时候读写的数据,比如程序运行的时候需要从中读取数据的文件,或者输出内容的文件。

本次重点讨论的就是数据文件。

在之前C语言的学习过程中,所处理的数据的输入输出都是以终端为对象,即从终端的键盘输入数据,运行结果显示到显示器上。

其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使⽤,这⾥处理的就是磁盘上⽂件

2.3文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:

  • 文件路径
  • 文件名主干
  • 文件后缀

例如:c:\code\text.txt

文件标识常被称为文件名

文件名又可以分为绝对路径和相对路径

相对路径

  • 定义:相对于当前程序运行目录的路径,不用写完整地址。
  • 相对路径符号:
    • ./当前目录(可省略)
    • ../上一级目录
  • 示例:
    • text.txt 同级文件
    • ./text.txt 同级文件
    • ../date/text.txt 上一级文件

绝对路径

  • 定义:从磁盘根目录开始的完整路径。
  • 示例:
    • C:\Users\xxx\test.txt

3、二进制文件和文本文件

根据数据的组织形式,数据文件又可分为二进制文件文本文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂本⽂件

一个数据在文件中的存储:

  • 字符一律以ASCII码的形式存储
  • 数值型数据可以用ASCII码的形式存储也可以使用二进制形式存储

例如有整数10000,如果以ASCII码的形式存储到磁盘,则在磁盘中占5个字节,(1个字符占1个字节),如果以二进制的形式存储,则在磁盘上占4个字节(整型数据)。

代码演示:

#include <stdio.h>

int main()
{
	int i = 10000;
	FILE* p = fopen("text.txt", "wb");

	fwrite(&i, 4, 1, p);//⼆进制的形式写到⽂件中
	fclose(p);
	p = NULL;
	return 0;
}

10000在二进制文件中
在这里插入图片描述

4、文件的打开和关闭

4.1流和标准流

4.1.1流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了一次流的概念。

C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。

⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。

4.1.2标准流

那么为什么我们从键盘输入数据,向屏幕上输出数据,都没有打开流?

在C语言程序启动的时候,默认就打开了3个流:

  • stdin -标准输入流,大多数环境中从键盘输入,scanf函数就是从标准输入流读取数据
  • stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。
  • stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。

默认打开了这三个流,我们使⽤scanfprintf等函数就可以直接进⾏输⼊输出操作的。

stdinstdoutstderr三个流的类型是FILE*,称为文件指针。

C语⾔中,就是通过 FILE*类型的的⽂件指针来维护流的各种操作的。

4.2文件指针

缓冲⽂件系统中,关键的概念是⽂件类型指针,简称⽂件指针

每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如文件的名字,文件状态及文件当前位置),这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 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指向某个文件的文件信息区(是一个结构体变量)。通过文件信息区中的信息可以访问该文件,通过文件指针变量可以间接的找到与它关联的文件

在这里插入图片描述

4.3文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件

编写程序时,打开文件的同时,都会返回一个FILE*的指针变量指向该文件,建立了指针和文件的关系。

ANSI C 规定使⽤fopen函数打开文件,使用 fclose函数关闭文件。

4.3.1fopen

函数原型

FILE* fopen(const char* filename, const char* mode);
  • 功能: fopen函数是用来打开参数filename指定的文件,同时将打开的文件和一个流进行关联,后续对流的操作是通过fopen函数返回的指针维护。具体对流(关联的文件)的操作是通过参数mode来指定的。
  • 参数:
    • filename:表示被打开的文件的名字,可以是绝对路径,也可以是相对路径。
    • mode:表示对打开的文件的操作方式,具体见下方表格
  • 返回值:
    • 若文件打开成功,该函数返回一个指向FILE对象的指针,该指针可用于后续操作中标识对应的流。
    • 若文件打开失败,则返回NULL指针,所以一定要对fopen的返回值做判断,验证文件是否打开成功。

需要包含的头文件:stdio.h

代码演示

#include <stdio.h>

//fopen使用演示
int main()
{
	FILE* pr = fopen("text.txt", "w");//以w的形式打开文件

	//判断是否打开成功
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//操作文件
	return 0;
}

mode表示对打开的⽂件的操作⽅式:

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)为了输出数据,打开一个文本文件建立一个新文件
“a”(追加)文本文件尾添加数据建立一个新文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新文件
“ab”(追加)向一个二进制文件尾追加添加数据建立一个新文件
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,打开一个文本文件建立一个新文件
“a+”(读写)打开⼀个文本⽂件,在⽂件尾进⾏读写建⽴⼀个新⽂件
“rb+”(读写)为了读和写,打开一个二进制文件出错
“wb+”(读写)为了读和写,打开一个二进制文件建立一个新文件
“ab+”(读写)打开⼀个二进制⽂件,在⽂件尾进⾏读写建⽴⼀个新⽂件

注意:w方式会清理掉文件里的旧数据。

4.3.2fclose

函数原型

int fclose(FILE* stream);
  • 功能: 关闭参数stream关联的文件,并取消与其关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃。
  • 参数: stream:指向要关闭的流的FILE对象指针。
  • 返回值: 成功关闭stream指向的流返回0,否则返回EOF

需要包含的头文件:stdio.h

代码演示

#include <stdio.h>

//fclose函数使用演示

int main()
{
	//打开文件
	FILE* pr = fopen("text.txt", "w");
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}
	//操作文件

	//关闭文件
	fclose(pr);
	pr = NULL;//避免pr变成野指针
	return 0;
}

5、文件的顺序读写

进行文件读写的时候,会涉及下面这些函数

函数名功能适用于
fgetc从输入流中读取一个字符所有输入流
fputc向输出流中写入一个字符所有输出流
fgets从输入流中读取一个字符所有输入流
fputs向输出流中写入一个字符串所有输出流
fscanf从输入流中读取带有格式的数据所有输入流
fprintf向输出流中写入带有格式的数据所有输出流
fread从输入流中读取一块数据文件输入流
fwrite向输出流中写入一块数据文件输出流

所有输入流一般包括标准输入流和其他输入流(如:文件输入流)

所有输出流一般包括标准输出流和其他输出流(如:文件输出流)

5.1fputc

函数原型

int fputc(int character, FILE* stream);
  • 功能: 将参数character指定的字符写入到stream指向的输出流中,通常用于向文件或标准输出流中写入字符。在写入字符后,会调整指示器。字符会被写入流内部位置指示器当前指向的位置,随后该指示器自动向前移动一个位置。
  • 参数:
    • character:被写入的字符
    • stream:是一个FILE*类型的指针,指向了输出流(通常是文件流或stdout
  • 返回值:
    • 成功时返回写入的字符(以int形式)。
    • 失败时返回EOF(通常是-1),并且设置错误指示器,可以通过ferror() 检查具体错误。

代码演示

#include <stdio.h>

//fputc函数使用演示

int main()
{
	//以写的形式打开文本文件
	FILE* fp = fopen("text.txt", "w");

	//检查
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}

	//向输出流写入数据
	fputc('a', fp);
	fputc('b', fp);
	fputc('c', fp);

	//关闭文件
	fclose(fp);
	fp = NULL;

	return 0;
}

在这里插入图片描述

5.2fgetc

函数原型

int fretc(FILE* stream);
  • 功能: 从参数stream指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符,读取这个字符后,文件指示器自动前进到下一个字符。
  • 参数: streamFILE*类型的文件指针,可以是stdin,也可以是其他输入流指针。如果是sidin就是从标准输入流中输入数据。如果是文件流指针,就从文件读取数据。
  • 返回值:
    • 成功时返回读取的字符(以int形式)
    • 若调用时流已处于文件末尾,函数返回EOF并设置流的文件结束指示器,可以通过feof检查,若发生读取错误,函数返回EOF并设置流的错误指示器,可以通过ferror检查。

代码演示

//fgetc使用演示(从文件中读取10个字符)

int main()
{
	//以读的形式打开文本文件
	FILE* pr = fopen("text.txt", "r");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读取文件
	int ch = 0;
	
	while ((ch = fgetc(pr))!= EOF)
	{
		fputc(ch, stdout);//将读取的字符以putc的方法在标准来输出流上打印
	}

	//关闭文件
	fclose(pr);
	pr = NULL;
	return 0;
}

text.txt
在这里插入图片描述

程序运行结果

5.3feof和ferror

函数原型

int feof(FILE* stream);
//检测stream指向的流是否遇到文件末尾

int ferror(FILE* stream);
//检测stream指向的流是否发生读/写的错误

如果在读取文件的过程中,遇到了文件末尾,文件读取就会结束。这时读取函数会在对应的流上设置一个文件结束的指示符,这个文件结束指示符可以通过feof函数检测。如果 feof 函数检测到⽂件结束指示符已经被设置,则返回⾮0的值,如果没有设置则返回0。

如果在读/写文件的过程中,发生了读/写的错误,文件读/写就会结束。这时读/写函数会在对应的流上设置一个错误指示符,这个错误指示符可以通过ferror函数检测,如果 ferror 函数检测到错误指示符已经被设置,则返回⾮0的值,如果没有设置则返回0。

代码演示

#include <stdio.h>

//feof函数演示
int main()
{
	//以读的形式打开文本文件
	FILE* pr = fopen("text.txt", "r");

	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读取文件
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		int ch = fgetc(pr);
		if (ch == EOF)
		{
			if (feof(pr))
				printf("读取到文件末尾了\n");
			else if (ferror(pr))
				printf("文件读取错误\n");
		}
		else
		{
			fputc(ch, stdout);
		}
	}

	//关闭文件
	fclose(pr);
	pr = NULL;
	return 0;
}

在这里插入图片描述

#include <stdio.h>

//ferror函数演示
//以写的形式打开文件,就直接去读就会出错
int main()
{
	//以写的形式打开文本文件
	FILE* pr = fopen("text.txt", "w");

	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读取文件
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		int ch = fgetc(pr);
		if (ch == EOF)
		{
			if (feof(pr))
				printf("读取到文件末尾了\n");
			else if (ferror(pr))
				printf("文件读取错误\n");
		}
		else
		{
			fputc(ch, stdout);
		}
	}

	//关闭文件
	fclose(pr);
	pr = NULL;
	return 0;
}

在这里插入图片描述

5.4fputs

int fputs(const char* str, FILE* stream);
  • 功能: 将参数str指向的字符串写入到参数stream指定的流中(不包含结尾的\0),适用于文件流或标准输出(stdout)。
  • 参数:
    • str:str是指针,指向了要写入的字符串(必须以\0结尾)
    • stream:是一个FILE*的指针,指向了要写⼊字符串的流。
  • 返回值:
    • 成功时返回非负整数
    • 失败时返回EOF,同时设置流的错误指示器,可以使用ferror检查错误原因。

代码演示

//fputs函数使用演示

int main()
{
	//以写的形式打开文件
	FILE* pr = fopen("text.txt", "w");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//向文件流写入一段字符串
	fputs("hello", pr);
	fputs("world", pr);

	//关闭文件
	fclose(pr);
	pr = NULL;
	return 0;
}

在这里插入图片描述

5.5fgets

函数原型

char* fgets(char* str, int num, FILE* stream);
  • 功能:stream指定输入流中读取字符串,至读取到换行符、文件末尾(EOF)或达到指定字符数(包含结尾的空字符\0),然后将读取到的字符串存储到str指向的空间中。
  • 参数:
    • str:是指向字符数组的指针,str指向的空间用于存储读取到的字符串。
    • num:最大读取字符数(包含结尾的\0,实际最多读取num-1个字符)。
    • stream:输入流的文件指针(如文件流或stdin)。
  • 返回值:
    • 成功时返回str指针。
    • 若在尝试读取字符时遇到文件末尾,则设置文件结束指示器,并返回NULL,需通过feof检测。
    • 若读取错误,则设置流错误指示器,并返回NULL,通过ferror检测。

代码演示

#include <stdio.h>

int main()
{
	char ptr[10] = { 0 };
	//以读的形式打开一个文本文件
	FILE* pr = fopen("text.txt", "r");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//从文件流中读取字符串

	while (fgets(ptr, 10, pr) != NULL)
	{
		printf("%s", ptr);
	}
	

	//关闭文件
	fclose(pr);
	pr = NULL;
	return 0;
}

注意:

  • 若读取到换行符\n,会将其包含在字符串中(除非超过num-1的限制),然后以\0结尾。
  • 文件末尾无换行符时,字符串以\0结尾,不包含\n

5.6fprintf

函数原型

int fprintf(FILE* stream, const char* format, ...);
//类比printf
  • 功能: fprintf是将格式化的数据写入指定文件流的函数。它与printf函数类似,但可以输出到任意输出流(如磁盘⽂件、标准输出、标准错误等),不仅限于控制台。
  • 参数:
    • stream:指向FILE对象的指针,表示要写⼊的⽂件流(如 stdout 、⽂件指针等)。
    • format:格式化字符串,包含要写入的文本和格式说明符(%d%s等)。
    • ...:可变参数列表,提供与格式字符串中说明符对应的数据。
  • 返回值:
    • 成功时,返回写入的字符总数(非负值)。
    • 失败时,先设置对应流的错误指示器,再返回负值,可以通过ferror检测。

代码演示

#include <stdio.h>

//fprintf函数使用演示

struct stu
{
	char name[15];
	int age;
	float score;
};

int main()
{
	struct stu s = { "zhangsan",18,90.0 };

	//以写的形式打开一个文本文件
	FILE* ptr = fopen("text.txt", "w");

	//检查
	if (ptr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//将结构体中的格式化数据写入文件
	fprintf(ptr, "姓名:%s 年龄:%d 成绩:%.2f", s.name, s.age, s.score);

	//关闭文件
	fclose(ptr);
	ptr = NULL;
}

在这里插入图片描述

5.7fscanf

函数原型

int fscanf(FILE* stream, const char* format, ...);
//类比scanf函数
  • 功能: fscanf是从指定文件流中读取格式化数据的函数。它类似于scanf,但可以指定输入源(如文件、标准输入等),而非仅限于控制台输入。适用于从文件解析结构化数据(如整数、浮点数、字符串等)。
  • 参数:
    • stream:指向FILE对象的指针,表示要读取的⽂件流(如 stdin 、⽂件指针等)。
    • format:格式化字符串,定义如何解析输入数据(%d%s等)。
    • ...:可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。
  • 返回值:
    • 成功时,函数返回成功填充到参数列表中的项数。该值可能与预期项数一致,也可能因以下原因少于预期:
      • 格式和数据匹配失败;
      • 读取发生错误;
      • 到达文件末尾。
    • 如果在成功读取任何数据之前发生:
      • 发生读取错误,返回NULL,在对应流上设置错误指示符。
      • 到达文件末尾,返回NULL,在对应流上设置文件结束指示符。

代码演示

//fscanf函数使用演示
struct stu
{
	char name[15];
	int age;
	float score;
};

int main()
{
	struct stu s = { 0 };

	//以读的形式打开一个文本文件
	FILE* ptr = fopen("text.txt", "r");

	//检查
	if (ptr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//从文件中读取格式化数据
	fscanf(ptr, "姓名:%s 年龄:%d 成绩:%f", s.name, &s.age, &s.score);

	fprintf(stdout, "姓名:%s 年龄:%d 成绩:%.2f", s.name, s.age, s.score);//以格式化数据的方式打印到屏幕上

	//关闭文件
	fclose(ptr);
	ptr = NULL;
	return 0;
}

在这里插入图片描述

5.8sprintf和sscanf

对比:
printf/fprintf/sprintf
scanf/fscanf/sscanf

5.8.1sprintf

函数原型

int sprintf(char* str, const char* format, ...);
  • 功能: sprintf是将格式化的数据写入字符数组(字符串)。它与printf函数类似,但输出目标不是控制台或文件,而是用户指定的内存缓冲区。常用于动态生成字符串、拼接数据或转换数据格式。简而言之就是将格式化的数据转换成一个字符串。
  • 参数:
    • str:指向字符数组的指针,用于存储生成的字符串(需确保足够大防止溢出)。
    • format:格式化字符串,包含要写入的文本和格式说明符(%d%s等)。
    • ...:可变参数列表,提供与格式字符串中说明符对应的数据。
  • 返回值:
    • 成功时,返回写入buffer的字符总数(不包含结尾的空字符\0)。
    • 失败时,返回负值

代码演示

#include <stdio.h>

//sprintf函数使用演示

struct stu 
{
	char name[15];
	int age;
	float score;
};

int main()
{
	struct stu s = { "zhangsan",18,90.0 };
	
	char arr[50] = { 0 };

	sprintf(arr, "姓名:%s 年龄:%d 成绩:%.2f", s.name, s.age, s.score);

	printf("%s", arr);

	return 0;
}

在这里插入图片描述

5.8.2sscanf

函数原型

int sscanf(const char* str, const char* format, ...);
  • 功能: sscanf是从字符串中读取格式化数据。它与scanf函数类似,但输入源是内存中的字符串而非控制台或文件。常用于解析字符串中的格式化数据(提取数字、分割文本等)
  • 参数:
    • str:要解析的源字符串(输入数据来源)
    • format:格式化字符串,定义如何解析数据(%d%s等)。
    • ...:可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。
  • 返回值:
    • 成功时,返回成功解析并复制的参数数量
    • 失败或未匹配任何数据:若输⼊结束或解析失败,返回 EOF (通常是 -1 )。

代码举例

#include <stdio.h>

//sscanf使用演示
struct stu
{
	char name[15];
	int age;
	float score;
};

int main()
{
	char arr[] = "姓名:zhangsan 年龄:18 成绩:90.00";

	struct stu s = { 0 };
	//使用sscanf解析arr中的格式化数据,存到结构体变量s中的成员中
	sscanf(arr, "姓名:%s 年龄:%d 成绩:%f", s.name, &s.age, &s.score);

	//打印结构体变量s中的成员
	fprintf(stdout, "姓名:%s 年龄:%d 成绩:%.2f", s.name, s.age, s.score);

	return 0;
}

在这里插入图片描述

总结:

函数功能
scanf针对标准输入(stdin)的格式化输入函数
printf针对标准输出(stdout)的格式化输出函数
fscanf针对所有输入流(文件流、stdin) 的格式化输入函数
fprintf针对所有输出流(文件流、stdout)的格式化输出函数
sscanf从字符串中提取格式化的数据
sprintf将格式化的数据转换为字符串

5.9fwrite

函数原型

size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
  • 功能: 将ptr指向的内存块中的的数据指定几个写入stream指向的文件流中,是以2进制的形式写入的
  • 参数:
    • ptr:指向要写⼊的数据块的指针。
    • size指定要写入的单个连续内存块的大小(以字节为单位)
    • count:要写入的内存块的数量。
    • stream:指向FILE类型结构体的指针,指定了要写入数据的文件流。
  • 返回值: 返回实际写⼊的内存块数量。如果发⽣错误,则返回值可能⼩于 count

使用注意事项:

  • 需要包含stdio.h头文件。
  • 在使用fwrite之前,需要确保文件已经以二进制可写方式打开。
  • fwrite 通常⽤于⼆进制数据的写⼊。

代码演示

#include <stdio.h>

//fwrite使用演示

struct stu
{
	char name[15];
	int age;
	float score;
};

int main()
{
	struct stu s = { "zhangsan",18,90.0 };

	//以写的方式打开二进制文件
	FILE* ptr = fopen("text.txt", "wb");

	//检查
	if (ptr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//将结构体变量s中的值以二进制的形式写进1个到文件内
	fwrite(&s, sizeof(struct stu), 1, ptr);

	//关闭文件
	fclose(ptr);
	ptr = NULL;
	return 0;
}

5.10fread

函数原型

size_t fread(void* str, size_t size, size_t count, FILE* stream);
  • 功能: 用于从stream指向的文件流中读取几个内存块中的数据,并将其存储到ptr指向的内存缓冲区内。
  • 参数:
    • ptr:指向内存区域的指针,用于存储从文件中读取的数据。
    • size:要读取单个连续内存块的大小(以字节为单位)。
    • count:要读取的内存块的数量。
    • stream:指向FILE类型结构体的指针,指定了要读取数据的文件流。
  • 返回值: 返回实际读取的的内存块数量。如果发⽣错误,则返回值可能⼩于 count

注意事项

  • 使用需要包含头文件stdio.h
  • 在使⽤ fread之前,需要确保⽂件已经以⼆进制可读⽅式打开。
  • ptr指向的内存区域必须足够大,以便存储指定数量和大小的数据块。
  • 如果fread成功读取了指定数量的数据块,则返回值等于 count ;如果读取数量少于count,则可能已经到达⽂件结尾或者发⽣了错误。
  • 在⼆进制⽂件读取时, fread 是常⽤的函数,但对于⽂本⽂件读取,通常使⽤ fgetsfscanf

代码演示

#include <stdio.h>

//fread函数使用演示

struct stu
{
	char name[15];
	int age;
	float score;
};

int main()
{
	struct stu s = { 0 };
	//以读的形式打开一个二进制文件
	FILE* ptr = fopen("text.txt", "rb");

	//检查
	if (ptr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//从文件中读取数据存储到结构体变量s中
	fread(&s, sizeof(struct stu), 1, ptr);

	fprintf(stdout, "%s %d %.2f", s.name, s.age, s.score);

	//关闭文件
	fclose(ptr);
	ptr = NULL;
	return 0;
}

在这里插入图片描述

6、文件的随机读写

6.1fseek

函数原型

int fseek(FILE* stream, long int offset, int origin);
  • 功能: 根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)
  • 参数:
    • stream:指向FILE类型结构体的指针,指定了要定位文件指针的文件流。
    • offset:偏移量,这个偏移量要根据第三个参数来确定取值,可以是正数(向文件末尾方向移动)、负数(向文件开头方向移动)或0(不移动)。
    • origin:偏移的起始点(基准位置),这个参数有三个取值:
      • SEEK_SET:文件开始位置
      • SEEK_CUR:文件指针当前的位置
      • SEEK_END:文件末尾位置
  • 返回值:
    • 成功:如果文件位置指针被成功移动,fseek返回0
    • 失败:如果发生错误(试图移动到一个不存在的负偏移位置),则返回非零值(-1)。

使用注意事项

  • 二进制文件vs文本文件
    • 对于二进制文件,fseek的行为非常直观,可以精确地移动到任何字节位置
    • 对于文本文件,fseek的行为在某些环境下可能不确定,只有以下两种操作,在所有环境下的文本文件中都是安全的
      • 移动到文件开头:将offset设置为0,origin设置为SEEK_SET
      • 回到ftell记录的位置:将offset设置为ftell的返回值,origin设置为SEEK_SET

代码演示

#include <stdio.h>

//fseek函数使用演示

int main()
{
	//以读的形式打开一个文本文件(以文本文件演示更直观,实际应用过程应该应用到二进制文件)
	FILE* pr = fopen("text.txt", "r");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//定位文件指针
	fseek(pr, 4, SEEK_SET);


	fputc(fgetc(pr), stdout);

	//关闭文件
	fclose(pr);
	pr = NULL;
}

6.2ftell

函数原型

long int ftell(FILE* stream);
  • 功能: 返回文件指针相对于起始位置的偏移量
  • 参数:
    • stream:指向一个已打开文件的文件指针。
  • 返回值:
    • 成功:返回当前文件位置指针相对于文件开头的偏移量(字节数),类型为long int
    • 失败:如果发生错误(文件流无效或文件不支持定位),返回-1L。

注意事项

  • 对于⼆进制⽂件, ftell 的返回值就是相对于⽂件开头的精确字节数,⾮常可靠。
  • 对于⽂本⽂件,由于不同系统对换⾏符的处理不同(如Windows是\r\n,Linux是\n),ftell 的返回值可能不直接等于从⽂件开头计数的字符数。但在⼤多数实现中,它仍然可以安全地与 fseek 配合使⽤来记录和恢复位置。

代码演示

#include <stdio.h>

//ftell函数使用演示

int main()
{
	//以读的形式打开一个文本文件
	FILE* pr = fopen("text.txt", "r");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		pr = NULL;
	}

	//记录当前位置
	fputc(fgetc(pr), stdout);//a
	int pos = ftell(pr);//1

	//定位位置
	fseek(pr, 5, SEEK_SET);
	fputc(fgetc(pr), stdout);//f

	fseek(pr, pos, SEEK_SET);
	fputc(fgetc(pr), stdout);//b

	//关闭文件
	fclose(pr);
	pr = NULL;

	return 0;
}

6.3rewind

void rewind(FILE* stream);
  • 功能: 让⽂件指针的位置回到⽂件的起始位置。
  • 参数: stream :指向⼀个已打开⽂件的⽂件指针。

使⽤注意事项

  • 清除错误标志:
    • rewind 不仅移动⽂件指针,还会清除⽂件的错误标志和⽂件结束标志。
    • 这意味着即使在读取⽂件时遇到了错误或到达了⽂件末尾,调⽤ rewind 后,⽂件状态会被重置,可以重新开始操作。
  • fseek 的区别:
    • fseek(fp, 0, SEEK_SET) 只移动⽂件指针,不清除错误标志。
    • rewind(fp) 移动⽂件指针并清除错误标志。

代码演示

#include <stdio.h>

//rewind函数使用演示

int main()
{
	//以读的形式打开一个文本文件
	FILE* pr = fopen("text.txt", "r");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		pr = NULL;
	}

	//记录当前位置
	fputc(fgetc(pr), stdout);//a
	int pos = ftell(pr);//1

	//定位位置
	fseek(pr, 5, SEEK_SET);
	fputc(fgetc(pr), stdout);//f

	//回到起始位置
	rewind(pr);
	fputc(fgetc(pr), stdout);//a

	//关闭文件
	fclose(pr);
	pr = NULL;

	return 0;
}

7、文件缓冲区

ANSI C 标准采⽤“缓冲⽂件系统” 处理数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。
在这里插入图片描述

7.1fflush

函数原型

int fflush(FILE* stream);
  • 功能: 强制刷新参数 stream 指定流的缓冲区,确保数据写⼊底层设备。
    • 对输出流:将缓冲区中未写⼊的数据⽴即写⼊⽂件。
    • 对输⼊流:⾏为由具体实现决定,⾮C语⾔标准⾏为(可能清空输⼊缓冲区)。
    • 参数为 NULL 时:刷新所有打开的输出流。
  • 参数:
    • stream :指向⽂件流的指针(如 stdout 、⽂件指针等)
    • 返回值:成功返回 0 ,失败返回 EOF

注意事项

  • 仅对输出流更新流(最后⼀次操作为输出)有明确刷新⾏为
  • 输⼊流的刷新⾏为不可移植(如清空输⼊缓冲区是⾮标准特性)
  • 程序正常终⽌( exit )或调⽤ fclose 时会⾃动刷新,但程序崩溃时缓冲区数据可能丢失

因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件

如果不做,可能会出现读写⽂件的问题。

8、更新文件

在⽂件的打开模式中有三种⽅式值得注意,分别是: “r+”, “w+”, “a+”,分别是什么意思?

在这里插入图片描述

关键要点:

  • 在写完⽂件后,要继续读⽂件的时候,在读取之前⼀定要使⽤ fflush 刷新⽂件缓冲区,再使⽤ fseek 或者 rewind重新定位⽂件指示器的位置。
  • 在读完⽂件后,需要继续写⽂件之前,在写⽂件之前,使⽤ fseekrewind 重新定位⽂件指示器的位置。

代码演示

#include <stdio.h>

//更新文件演示

int main()
{
	//以可读可写的方式打开一个文本文件
	FILE* pr = fopen("text.txt", "w+");

	//检查
	if (pr == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写
	fputs("abcdefgh", pr);

	//刷新缓冲区
	fflush(pr);

	//重新定位文件指示器的位置
	fseek(pr, 0, SEEK_SET);
	//读
	int c = fgetc(pr);
	printf("%c", c);//a

	//定位,为了从b开始写入hello
	fseek(pr, 1, SEEK_SET);
	fputs("hello", pr);

	//关闭文件
	fclose(pr);
	pr = NULL;
}

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值