c语言文件操作

目录

一.程序文件数据文件区别

二.文件指针

文件的打开和关闭

什么是流

输入输出和读写的关系

三.文件的顺序读写

单个字符的输入输出函数 fgetc fgets

文本行(字符串)模式输入输出函数 fgets fputs

格式化输入输出函数(fscanf和fprtinf) 

二进制输入输出(fread,fwrite)

四.文件的随机读写

fseek函数

ftell函数 

rewind函数

 文件读取结束的判定

怎么判断文件是否结束

文件结束指示器,文件错误指示器


一.程序文件数据文件区别

c语言程序文件包括源文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。程序文件通常包含变量和常量的定义,以及对这些变量和常量进行操作的函数和控制结构。程序文件中的数据通常是用来进行计算、处理和操作的,而不是用来存储和持久化的。如果需要将数据保存到文件中,通常会使用数据文件来实现。

数据文件专门用于存储数据,可以使用不同的格式和扩展名来区分不同类型的数据,如文本文件、图像文件、音频文件等。这些数据文件可以被程序读取、修改和更新,以实现数据的持久化和存储。

c语言程序文件通常是以ASCII码形式保存的,当程序文件被编译时会被转换成二进制的目标文件或者可执行文件,但是源代码本身依旧是以ASCII码形式保存的文本文件

数据文件可以是二进制形式保存的,也可以是各种编码格式保存的文本文件(包括ASCII码)

比如一个整数10000,它如果以ASCII码形式存储,1会转变为对应的ASCII码值00110001,后面的0也会转变为对应的ASCII码形式00110000,所以1000的ASCII码存储为00110001 00110000 00110000 00110000 00110000 00110000

而10000的二进制形式为00000000 00000000 00100111 00010000 

二.文件指针

每个被使用的数据文件都会在内存中开辟一个相对应的文件信息区,用来存放文件的相关信息(文件名称,文件状态以及文件当前的位置等)。这些信息是保存在一个结构体变量当中的,被重命名为FILE,这个结构体是默认已经建好了的,不用我们重新写,一般很多对文件的操作是通过结构体指针FILE *来完成的。

文件的打开和关闭

文件在进行读写操作前,应该先打开文件,结束后关闭文件,打开是用fopen函数实现的,

c语言文件操作_第1张图片

const char *filename是指你现在要操作的文件名,const char * mode是指你要执行的操作模式,是读还是写。最后的返回值是FILE *结构体指针形式

如FILE * pf=fopen("data.txt","w")以写的形式的来打开文件data.txt,如果没有这个文件就重新建一个文件data.txt

FILE * pf=fopen("data.txt","r")以读的形式的来打开文件data.txt,如果没有这个文件会直接报错,不会重新建一个文件

常见的文件操作模式如下

“r”(只读) 为了读数据,打开⼀个已经存在的⽂本⽂件,如果这个文本文件不存在会直接报错

“w”(只写)    为了写数据,打开一个已经存在的文本文件,如果这个文件不存在那么就直接建一个这个文件名相同的文件

“a”(追加)向⽂本⽂件尾添加数据,如果这个文件不存在,建⽴⼀个新的⽂件
“rb”(只读)为了读数据,打开⼀个⼆进制文件,如果这个文件不存在会报错
“wb”(只写)为了写数据,打开⼀个⼆进制⽂件,如果这个文件不存在就建⽴⼀个新的⽂件
“ab”(追加) 向⼀个⼆进制⽂件尾添加数据,如果这个文件不存在就建⽴⼀个新的⽂件
“r+”(读写)意思是我即要读也要在后面追加新的数据,打开⼀个⽂本⽂件,如果这个文件不存在就直接报错
“w+”(读写)为了读和写,建议⼀个新的⽂件,如果文件不存在,那么就建⽴⼀个新的⽂件
“a+”(读写) 打开⼀个⽂件,在⽂件尾进⾏读写,如果文件不存在就建⽴⼀个新的⽂件
“rb+”(读写)为了读和写打开⼀个⼆进制⽂件,如果文件不存在就建⽴⼀个新的⽂件
“wb+”(读写)为了读和写,新建⼀个新的⼆进制⽂件,如果文件不存在就建⽴⼀个新的⽂件
“ab+”(读写)打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写,如果文件不存在就建⽴⼀个新的⽂件

总结一下只要是“r”形式,就算是“r+”,这种要追加新内容的,如果文件不存在那么都会报错。“wb”和“rb”这种后面有b的都是打开二进制文件。

文件的关闭操作是通过fclose函数来实现的,但是你得先打开了才有关闭

c语言文件操作_第2张图片

FILE * stream是指文件指针流,fclose函数用于关闭一个已打开的文件流,并释放与该流相关的资源。fclose函数的作用是关闭文件流,而不是释放指针所指的空间。当调用fclose函数关闭文件流后,文件指针pf仍然存在,只是不再指向任何有效的文件流。将文件指针置为NULL的目的是为了避免程序在之后误用已经关闭的文件指针,因为使用已经关闭的文件指针可能导致未定义的行为,所以要在后面pf=NULL

所以文件打开和关闭的操作基本框架如下

int main()
{
	FILE* pf = fopen("data.txt", "w");//以写的形式打开文件data.txt
	if (pf != NULL)
	{
		//各种操作。。。。。。//


		fclose(pf);//完成操作后关闭文件
			pf = NULL;//文件指针置为空
	}
}

值得注意的是文件路径data.txt是相对路径,是相对于当前这个工作文件的路径,如果data.txt文件在桌面上 ,那么就得把根目录什么的都写上。比如C:\Users\86177\Desktop

什么是流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作C语言中的流是一个抽象概念,它代表了数据的流动,可以是输入流或输出流。流的概念并不直接涉及到空间的划分,而是将数据的流动抽象为一个逻辑上的概念。在C语言中,可以使用流来进行文件的读写操作,但并不意味着流本身划分了一块空间。实际上,流是通过文件指针来操作文件的,而文件指针则是指向文件在磁盘上的位置,而不是直接划分了一块空间。

实在不明白就直接把文件指针当作流吧,在C语言中,流(stream)通常是通过FILE类型的指针来表示的。因此,当我们使用fopen函数打开一个文件时,返回的指针实际上是一个指向FILE类型的流的指针。所以在这种情况下,可以将流视为指针。

FILE* pf = fopen("data.txt", "w")这行代码打开了一个名为"data.txt"的文件,并将其设置为写入模式。返回的指针pf是一个指向FILE类型的流,可以用于向文件中写入数据。那么问题来了不是说流⾥写数据,或者从流中读取数据,都是要打开流吗,不是说键盘和屏幕输入输出也是要通过流去操作吗,FILE* pf = fopen("data.txt", "w")这个我明白打开了流,我的printf和scanf这些直接输入输出在屏幕上也没见打开流啊?

  • stdout是C语言中的标准输出流。它是一个指向FILE类型的指针,代表了程序的标准输出设备。通常情况下,stdout指向屏幕,这意味着使用printf函数输出的数据会显示在屏幕上。

  • stdin 标准输入流,在大多数环境中从键盘输入,scanf就是从标准输入流中读取数据的

  • stderr 标准错误流,和stdout类似都是直接输出到屏幕上

这三个标准流不用打开,C语言直接默认打开了它们三个流,程序文件也是文本文件因此这三个流的类型也是FILE *类型,其实也可以叫做文件指针

输入输出和读写的关系

比如scanf用键盘输入一个数,保存在内存中这叫输入。但是对于内存来说这也可以叫读,内存从键盘中读入数据到内存中。虽然对于我们来说scanf输入数应该是写啊,但是对于内存来说scanf输入数据对应读

printf输出一个数据到屏幕上,相对于内存来说其实是把内存中的数据写到屏幕上,所以输出printf其实对应的是写

这两个不能搞混了,输入是读,输出是写

三.文件的顺序读写

单个字符的输入输出函数 fgetc fgets

fputc是字符输出函数

c语言文件操作_第3张图片

int character是指你需要输出(写)到文件当中的字符ASCII码值,FILE * stream是指你要在哪个打开的流进行操作

 fputc('A',pf)和fputc(65,pf)打开文件data.txt都是A,因为A的ASCII码值是65,所以它会把65对应的符号打印在你操作的文件中

#include 
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf != NULL)
	{
		fputc(65, pf);


		fclose(pf);//完成操作后关闭文件
			pf = NULL;//文件指针置为空
	}
}

c语言文件操作_第4张图片

pf是我要进行操作的文件data.txt的流,stdout是屏幕的流,如果你要打印到屏幕上,把pf改成stdout就行了

 c语言文件操作_第5张图片

为什么fputc返回值是int类型呢, 具体来说,fputc函数返回的是写入的字符的unsigned char类型的值,或者在发生错误时返回EOFEOF是一个表示文件结束或者出现错误的特殊值,它通常被定义为一个负数。因此,为了能够同时返回写入的字符值和表示错误的EOF值,fputc的返回类型被定义为int,以便能够区分这两种情况。

写26个字母写进文件里


#include 
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf != NULL)
	{
		int i = 0;
		for (i = 0; i < 26; i++)
		{
			fputc('A' + i,pf);
			
		}

		fclose(pf);//完成操作后关闭文件
			pf = NULL;//文件指针置为空
	}
}

c语言文件操作_第6张图片

fgetc字符输入函数(读) 

c语言文件操作_第7张图片

我们上面已经写了26个英文字母进去,用fgetc挨个读出来放到屏幕上

c语言文件操作_第8张图片

现在是读模式,所以首先“w”要改成“r”,fgetc读完文件结束标志是EOF,所以用EOF作为判断结束标志

文本行(字符串)模式输入输出函数 fgets fputs

c语言文件操作_第9张图片

const * str 是你要输出(写)到文件中的字符串

#include 
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf != NULL)
	{
		const char* str = "abcdefg";
		fputs(str, pf);
		fclose(pf);//完成操作后关闭文件
		pf = NULL;//文件指针置为空
	}
}

fgets文本行输入函数

c语言文件操作_第10张图片

int num是指你要读出几个数据,char * str是存放读出数据的容器(字符串数组) 

值得注意的是虽然你规定的是读出num个数据,但实际上显示的只有num-1个数据,因为最后一个默认为‘\0’c语言文件操作_第11张图片

格式化输入输出函数(fscanf和fprtinf) 

fprintf和printf非常相似

c语言文件操作_第12张图片

c语言文件操作_第13张图片 唯一的区别是fprintf多了FILE * stream,printf是默认输出到屏幕上,fprintf是输出到FILE * stream 对应的文件夹里

printf("%d",m) m是个变量,对应的fprintf应该为fprintf(pf,"%d",m);

#define _CRT_SECURE_NO_WARNINGS
#include 
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf != NULL)
	{
		int m = 5;
		fprintf(pf,"%d", m);
		fclose(pf);//完成操作后关闭文件
		pf = NULL;//文件指针置为空
	}
}

c语言文件操作_第14张图片

对于有多个类型成员的结构体来说,printf函数是这样打印到屏幕里的 

 c语言文件操作_第15张图片

而fprintf函数是这样打印输出的


#include 
struct student
{
	const char* name;
	int age;
	int sno;

};
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf != NULL)
	{
		struct student st = { "zhangsan",20,2023 };
		fprintf(pf,"%s %d %d", st.name, st.age, st.sno);
		fclose(pf);//完成操作后关闭文件
		pf = NULL;//文件指针置为空
	}
}

 文件里的情况是这样的

c语言文件操作_第16张图片

同样fscanf和scanf也只是多了一个流pf而已 

scanf是这样写的

c语言文件操作_第17张图片

fscanf是这样写的

#define _CRT_SECURE_NO_WARNINGS
#include 
struct student
{
	char name[20];
	int age;
	int sno;

};
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf != NULL)
	{
		struct student st = {0};
		fscanf(pf,"%s %d %d", st.name, &(st.age),&(st.sno));
		printf("%s %d %d", st.name,st.age, st.sno);
		fclose(pf);//完成操作后关闭文件
		pf = NULL;//文件指针置为空
	}
}

 c语言文件操作_第18张图片

区别在于scanf是键盘输入的打印到屏幕上,而fscanf是将原先文件中已经有了的读出来打印到屏幕上

二进制输入输出(fread,fwrite)

之前的所有读写函数适用于所有输入输出流,而 fread和fwrite只适用于二进制输入输出流

c语言文件操作_第19张图片

 const void * ptr是你要写的数据元素的首地址,size_t size是你要写的一个数据元素的大小,size_t count是你要写几个元素的数量,FILE * stream是写进的内容对应的文件夹

#define _CRT_SECURE_NO_WARNINGS
#include 

int main()
{
	FILE* pf = fopen("data.txt", "wb");
	if (pf != NULL)
	{
		int arr[6] = {6,8,3,4,5,6};
		fwrite(arr, sizeof(arr[0]), 4, pf);
		fclose(pf);//完成操作后关闭文件
		pf = NULL;//文件指针置为空
	}
}

 c语言文件操作_第20张图片

直接打开文件查看结果为什么是这么奇怪的符号,因为fwriter是以二进制的形式直接写进去的,而记事本是直接通过ASCII码或者UTF-8等一系列编码后的解码结果,所以才会看上去那么奇怪。可以通过二进制读形式fread读出来

c语言文件操作_第21张图片

fread的形式看上去和fwrite很像,但是所表达的含义却大有不同。

FILE * stream是只从这个流里面取出size_t count个size_t size大小的数据元素放到ptr首元素地址的容器中,这个ptr类型是void* 所以容器可以是数组也可以是结构体

c语言文件操作_第22张图片

这就把原先fwrite写进data.txt文件中的6 8 3 4取出来放到arr中了,而且也不是乱码,所以二进制写进去的要用二进制读出来

四.文件的随机读写

顺序读写是指按照文件中数据的存储顺序进行读写。在顺序读写时,我们通常从头到尾或从尾到头地读取或写入数据。这种方式的优点是简单易懂,不需要关心数据的具体位置,只需要按照顺序读取或写入即可。但是,如果需要读取或写入的数据不连续,顺序读写可能会浪费大量的时间和资源。随机读写是指按照指定的位置或偏移量对文件进行读写。在随机读写时,我们需要知道数据的具体位置,然后直接定位到该位置进行读取或写入。这种方式的优点是可以快速地定位到需要的数据,避免顺序读写时的大量时间和资源浪费。但是,随机读写需要更多的内存和计算资源,同时还需要考虑数据的位置和偏移量等问题。

fseek函数

fseek函数是根据文件指针的位置和偏移量来定位文件指针的函数

c语言文件操作_第23张图片

offset:从whence位置开始的偏移量。偏移量可以是正数或负数,表示向前或向后移动

origin:起始位置,它有三个可能的值

   SEEK_SET:文件开始处(即0位置)。

   SEEK_CUR:当前位置

   SEEK_END:文件结尾。

#include 
int main()
{
	int i = 0;
	FILE* pf = fopen("data.txt", "w");
		if (pf != NULL)
		{
			fputs("this is banaancn", pf);
			fseek(pf, 9, SEEK_SET);
			fputs("呵呵", pf);
			fclose(pf);
			pf = NULL;
		}
}

在我第一次输入完 this is banaancn后光标本来是应该在cn后面接着输入的,按顺序输出本应该输出这样的结果

c语言文件操作_第24张图片

 可是现在却是这样的结果

c语言文件操作_第25张图片

fseek就是改变输出光标位置的函数,从而改变输出结果。fseek(pf, 9, SEEK_SET);这个的意思就是说从开头位置算起挪动到第九个位置也就是b的后面,所以后面打印sw是在b字母的后面

ftell函数 

 返回文件指针相对于起始位置的偏移量

c语言文件操作_第26张图片

举个例子

 c语言文件操作_第27张图片

为什么最后偏移量是18呢,上一个this字符串总共有16个字符,加上sw共18个字符 ,光标相对于起始位置是18,所以ftell偏移量为18

而不把fseek屏蔽结果如下

c语言文件操作_第28张图片

虽然输出完第一个this字符串光标在字符串末尾第16位,但是通过fseek函数把光标挪到距离起始位置偏移量为9的位置上了,然后再输出第二个字符串sw,所以此时光标在偏移量为11的位置上

rewind函数

让文件指针的位置直接回到文件的起始位置

和ftell用法差不多,直接rewind(pf)就可以了

#define _CRT_SECURE_NO_WARNINGS
#include 
int main()
{
	int i = 0;
	FILE* pf = fopen("data.txt", "w");
	if (pf != NULL)
	{
		fputs("this is banaancn", pf);
		rewind(pf);
		fputs("sw", pf);
		long ret=ftell(pf);
		printf("%ld", ret);
		fclose(pf);
		pf = NULL;
	}
}

c语言文件操作_第29张图片

 文件读取结束的判定

文件读取结束有两种情况,一种是真的已经读完了文件所以结束,另一种是读取过程中出了错误直接强行停止

feof函数的作用是:当文件读取结束时,判断是否读取结束的原因是遇到文件尾结束的,因为读取过程中遇到文件尾也会结束

ferror函数的作用是在文件读取结束后,判断是否因为错误而停止

当使用诸如 fgetcfgetsfscanf 等函数从文件中读取数据时,如果发生了错误(如文件不存在、权限问题、文件被占用等),这些函数会返回 EOF,同时会自动设置文件错误指示器为活动状态。你可以通过调用 ferror 函数来检查文件错误指示器的状态。

对于 feof 函数,它用于检测文件结束指示器的状态,以确定是否已经到达了文件的末尾。feof 函数没有返回值,它的作用是返回一个非零值(通常是1)或零(0),表示文件结束指示器的状态。

具体来说,当调用 feof 函数时,如果文件结束指示器处于文件结束的状态,函数会返回非零值1,表示已经到达了文件末尾。否则,如果文件结束指示器不处于文件结束的状态,函数会返回零0,表示尚未到达文件末尾。

怎么判断文件是否结束

对于fgetc来说光是检查是否返回EOF是不够的,因为fgetc读取失败或者文件结束的都会返回EOF,所以还得检查错误指示器的状态,两者都没有问题才代表真的是因为文件读取结束而结束

对于fgets来说读取失败和文件读取结束都会返回NULL,所以还得检查错误指示器的状态,两者都没有问题才代表真的是因为文件读取结束而结束

二进制文件中读取结束判断是判断返回值是否小于实际要读的个数,fread 函数是用于从文件中读取数据块的函数,其返回值是成功读取的数据块数量。

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • ptr:指向要读取数据的缓冲区的指针。
  • size:每个数据块的字节数。
  • count:要读取的数据块的数量。
  • stream:指向要读取的文件的指针。

所以直接拿fread返回值和count要读的数量相比是否相等就可以判断是否文件读取结束

文件结束指示器,文件错误指示器

文件错误指示器和文件结束指示器实际上是通过一些标志位来表示的。标准库中的文件类型 FILE 结构体通常会包含一个用于表示文件错误的标志位(比如 ferror)和一个用于表示文件结束的标志位(比如 feof)。这些标志位的具体实现和维护是由C标准库来处理的,你无需直接操作这些标志位。相反,你可以使用相应的函数(feof和ferror)来查询这些标志位的状态。

需要注意的是,文件结束指示器和文件错误指示器是相互独立的。即使在没有发生错误的情况下,当读取操作到达文件末尾时,文件结束指示器也会被设置为活动状态。因此,在处理文件读取时,通常需要先检查文件错误指示器的状态,再检查文件结束指示器的状态

文本文件的例子

#define _CRT_SECURE_NO_WARNINGS
#include 
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf != NULL)
	{
		char c = 0;
		while ((c = fgetc(pf) )!= EOF)
		{
			printf("%c ", c);
		}
		if (ferror(pf))
			printf("出错了,没打印完");
		else if (feof(pf))
			printf("文件读取完毕");
		fclose(pf);
		pf = NULL;
	}
}

二进制文件的例子

#define _CRT_SECURE_NO_WARNINGS
#include 
int main()
{
	FILE* pf = fopen("data.txt", "wb");
	if (pf != NULL)
	{
		int arr[10] = { 1,2,3,4,5,6,7,8,9,8 };
		fwrite(arr, sizeof(arr[0]), 5, pf);
		fclose(pf);
		pf = NULL;
	}
	FILE* pp = fopen("data.txt", "rb");
	if (pp != NULL)
	{
		int brr[10];
		int ret = fread(brr, sizeof (brr[0]), 5, pp);
		if (ret == 5)//如果相等说明文件读取结束了,看一下现在里面的内容
		{
			for (int i = 0; i < 5; i++)
			{
				printf("%d", brr[i]);
			}
		}
		else
		{
			if (ferror(pp))
				printf("出错了,没打印完");
			else if (feof(pp))
				printf("文件读取完毕");
		}
		fclose(pp);
	}
}

ret == 5 是通过判断 fread 函数的返回值来确定读取的元素数量是否正确。如果返回值等于 5,表示成功读取了 5 个元素,即文件读取完毕。这是一种更准确的判断方法。

而 feof(pp) 则是通过判断文件结束指示器的状态来确定文件是否读取完毕。如果 feof(pp) 返回非零值,表示文件已经到达了末尾,即文件读取完毕

如果你只关心文件是否读取完毕,而不需要进一步区分错误类型,那么你可以省略 feof(pp) 的判断,只使用 ret == 5 来判断文件读取完毕即可,feof(pp) 和 ret == 5 都可以用来判断文件是否读取完毕。它们的含义是相同的,都表示文件已经到达了末尾,没有更多的数据可读取了

         

你可能感兴趣的:(c语言,开发语言)