对文件的各种操作详解

C/C++
198
0
0
2024-04-19

一、什么是流?

在讲之前,我们得先将一下流(英文为stream)数据从设备读入内存和数据从内存写入磁盘的过程很像是数据在流动一样,所以就“形象” 地把这个过程称为“流” 我们平常使用scanf对程序中的变量进行赋值的时候就是用到了标准输入流,通过printf将内容打印到屏幕上时则是用到了标准输出流,而在c语言中,程序一旦运行起来就会自动的帮你打开三个流,标准输入流(stdin),标准输出流(stdout)和标准错误流(stderr) 所以也从来没有什么打开键盘,关闭键盘这种操作。但程序在编译的时候它总不能知道你要打开什么文件吧,因此我们需要自己打开所需要的文件,并在用完之后关闭。

二、文件的打开和关闭

1.认识fopen和fclose

函数定义均取自cplusplus.com - The C++ Resources Network

fopen函数的作用为打开文件,它有两个参数,两个都是指针指向内容不可修改的字符型指针, 在使用它的时候传的第一个参数是文件的名字,第二个参数是打开的方式。它返回的值为你打开文件所在的地址。如果文件打开失败它会返回一个空指针(NULL)。
fclose函数的作用为关闭文件,参数为一个文件指针,传参的时候就将你要关闭的那个文件的地址(通过fopen函数获得的那个地址)传过去就行 (其实本质是传对应的流过去,但这么说太抽象了) 它就会帮你关闭文件,如果关闭成功它会返回0,否则返回EOF(-1)

2.不同的打开方式

取自比特就业课

3.使用fopen和fclose

#include<stdio.h>
int main()
{
	FILE*a=fopen("abcd.txt", "r");
	//以只读的方式打开当前目录下名叫abcd.txt的文件
    //并通过文件指针a接收地址
	if (a == NULL)
	{
		perror("fopen");//错误提示
	}
	fclose(a);//将打开的文件关闭
	a = NULL;//将野指针置为空指针
}

在我的文件夹中现在是没有一个叫做abcd.txt的文件的也就是说,这一次打开注定是失败的,我们来试一下

果然失败了,接下来我们试一下,用w的模式来打开文件,因为w的模式在文件不存在的时候也会创建一个文件
#include<stdio.h>
int main()
{
	FILE*a=fopen("abcd.txt", "w");
	//以只写的方式打开当前目录下名叫abcd.txt的文件
    //并通过文件指针a接收地址
	if (a == NULL)
	{
		perror("fopen");//错误提示
	}
	fclose(a);//将打开的文件关闭
	a = NULL;//将野指针置为空指针
}

运行没有问题,接下来就让我们看下目录中是否创建出了这么一个叫abcd.txt的文件

果然创建出来了,这不是在变魔术,也不是笔者自导自演,这是真实发生的,就是通过我们这个程序创建出来的。

三、文件的读写操作

1.fgetc和fputc

1.1认识fgetc和fputc

fgetc的作用是从对应流中取出字符来,并返回对应字符的ASCII值,读取失败则是会返回EOF,fputc则是将所给的字符传入到对应的流中,如果成功获得字符则其返回值为对应的字符。
1.2使用fgetc和fputc
在使用fgetc之前,我们先用fputc将一个字符写进文件中,这样才能使用fgets从非空的文件中读取字符。
#include<stdio.h>
int main()
{
	FILE* pf=fopen("abcd.txt","w");
	//以只写的方式打开一个叫abcd.txt的文件如果文件不存在则创建该文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputc('a',pf);
	//将字符a放进名字叫abcd.txt的文件中
    fclose(pf);
    pf=NULL;
}

可以看出,在运行程序之前abcd.txt这个文件中是什么都没有的,让我们运行一下

运行结束之后,a字符确实写进了abcd.txt这个文件中。
接着我们试一下fgetc,试着从流中拿字符出来,并赋值给程序中的一个变量,要注意的是fgetc在读取完字符后会自动地将文件的指针指向所读取字符的下一个字符 我们可以通过代码演示一下,这串代码的意义是从pf所指向的文件指针的内容中读取四个字符,并将它们依次打印出来。为此,我们先在abcd.txt这个文件中写一些字符,以免到时读取不到四个字符

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "r");
	//以只读的方式打开一个叫abcd.txt的文件
	//如果文件不存在则报错
	if (pf == NULL)
	{
		perror("fopen");//错误警告
		return 1;
	}
	char ch=fgetc(pf);
	//从pf所指向的流(文件)中读取一个字符并赋给ch
	//要注意的是,当fgetc读取完字符后它会自动地将文件指针指向文件中的下一个字符
	//也就是说,你在下一次读取字符的时候会直接读到下一个字符
	printf("%c\n", ch);
	//将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;//将野指针置为空指针
}

运行结果如下:

果然依次读出了4个字符。

2.fgets和fputs

2.1认识fgets和fputs

fgets的作用是从流中读取字符串,并将其存放到指定的字符数组中 它有三个参数,第一个是用来储存从流中读取到的字符串的变量,第二个参数num的含义指的是最多只能够从中取出num-1个字符,这里笔者认为是因为fgets这个函数它会自动地在取得的字符后加'\0',因为没有'\0'的话,这个也就不能被称为字符串了,它在打印的过程中就会打印出一系列的随机值直到遇到'\0'才会停下。所以把'\0'也算上的话就是取出num大小的字符串。第三个参数则是对应的流,你想从哪儿取数据出来,就传哪儿的流。fgets返回的值类型为一个字符型指针,如果传字符串成功它会返回str的地址,失败则返回NULL
fputs的作用则是将字符串放进对应的流中 它的参数很简单就两个,一个是你要放进去的字符串的地址,一个是被放入的流fputs如果成功写入数据,它的返回值为一个正数,如果写入失败,它的返回值则为一个负数。
2.2使用fgets和fputs
老样子,我们先用fputs往文件中存放一个字符串,存放之前文件已被清空。

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("今天天气真好", pf);
	//将这个字符串写进abcd.txt中
	fclose(pf);
	pf = NULL;
}

运行结果如下:

由于一个汉字占两个字节,不好展示fgets,所以我们在这个文件中随便存放一些字母,并通过fgets把它们取出,在abcd.txt中存放I am a student,并使用fgets从中取6个字符(不算'\0'),并将其打印

运行结果如下:

确实取出了六个字符,给str写很多个x,并通过监视观察

确实是将'\0'也存放进去了。

3.fread和fwrite

3.1认识fread和fwrite

fread的作用是以二进制的形式从流中读取count个大小为size的内容并将内容存放到ptr中。 fwrite的作用是以二进制的形式将count个大小为size的内容从ptr中取出,并放进对应的流中。
3.2使用fread和fwrite

老样子,先使用fwrite对文件的内容进行修改,文件中是没有任何内容的

代码如下:

#include<stdio.h>
struct abc
{
	int a;
	float b;
};//创建一个名字为struct abc类型的结构体
int main()
{
	FILE* pf = fopen("abcd.txt","wb");
	if (pf == NULL)
	{
		perror("fopen");
		//错误警告
		return 1;
	}
	struct abc a1 = { 100,3.14f };
	fwrite(&a1,sizeof(struct abc),1,pf);
	//将1个大小为struct abc的写进pf中
	fclose(pf);//关闭文件
	pf = NULL;//将野指针置为空指针
}

运行程序,结果如下:

我去,这是啥啊这是,看不懂啊,我们存的不是100和3.14吗。不要大惊小怪,之所以会看不懂,那是因为我们储存的信息是二进制信息,我们将这其中的信息以二进制的方式存放到了abcd.txt这个文件中。

使用fread,以二进制的形式读取数据,文件中的内容还是之前存放的二进制数据

代码如下:

#include<stdio.h>
struct abc
{
	int a;
	float b;
};//创建一个名字为struct abc类型的结构体
int main()
{
	FILE* pf = fopen("abcd.txt", "rb");
	//以二进制只读的方式打开一个名为abcd.txt的文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct abc a1 = {0};
	fread(&a1, sizeof(struct abc), 1, pf);
	//以二进制的方式将1个大小为struct abc的内容从pf所指向的指针内容中取出,放在a1这个结构体中
	printf("%d %f", a1.a, a1.b);
	//将内容打印出来
	fclose(pf);
	pf = NULL;
}

运行结果如下:

fread成功地将二进制信息转换成了我们需要的内容。

4.fscanf和fprintf

4.1认识fscanf和fprintf

fscanf的作用是将流中的数据以格式的方式输入到程序中,它有一个变量为流,另一个带...的则是参数个数不定的意思,scanf,printf函数也同样是参数个数不定的。

fprintf的作用则是将程序中的数据以格式的方式输出到流中,变量和fscanf一致。

4.2使用fscanf和fprintf
fscanf,fprintf的使用方式和scanf,printf非常相似,从变量上都可以看的出来,只是多了一个流的形式罢了,不过我可以这么跟你说,scanf,printf能做的事,fscanf和fprintf也能做,但fscanf和fprintf能做的事scanf和printf不一定可以做 因为scanf和printf仅仅只适用于标准输入流和标准输出流。而fscanf和fprintf则是可以适用于所有输入流和所有输出流。

上代码:

老样子,我们还是先使用fprintf将程序中的内容输出到文件中,文件依然清空

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "w");
   //以只写的方式打开abcd.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int a = 3; int b = 10;
	fprintf(pf,"%d %d",a,b);
	//将a,b以int的方式输入到pf所指向的文件中
	fclose(pf);
	pf = NULL;
}

运行结果:

使用fwrite将流中的内容取出,并对程序中的变量进行修改。

文件内容如下:

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "r");
	//以只读的方式打开abcd.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int a = 0; int b = 0;
	fscanf(pf, "%d %d", &a, &b);
	//将pf所指向的文件的内容以整型的方式输入到a,b中
	printf("a的值为%d\nb的值为%d", a,b);
	fclose(pf);
	pf = NULL;
}

运行结果如下:

四、什么才是输入输出?

有的小伙伴因为输入输出的问题会听的很难受,完全无法理解,这里笔者就来详细的叙述一下什么才是真正的输入输出。 在很多人眼中,输入输出仅仅限于打印在屏幕上是输出,在键盘上写东西是输入,这是浅薄的认识。在我们看来,scanf的作用是通过键盘对程序中的变量进行修改,这是输入。printf的作用是打印出我们想要的东西,是输出。gets的作用是从键盘上读取字符串,是输入。puts的作用是将字符串打印在屏幕上,是输出。这些理解都没有错,但是大部分这样理解的人久而久之就会错误地认为写东西就是输入,打印东西就是输出,这太片面了。在我们c语言编程中,输入输出是相对于程序而言的,我通过程序向外面(如屏幕)输送东西叫输出,我用外面的东西(如通过键盘输入字符)向程序输入东西叫输入 这个才是输入输出的真正含义,再举一个例子,我通过程序向文件中输送数据,这叫输出(而不是你们理解的输入),我通过读写文件中的内容,输送到程序中叫输入(而不是你们理解的输出),最后再强调一遍,输入输出是站在程序的角度看的。

五、文件的随机读写

1.fseek函数

1.1认识fseek函数

fseek函数的作用是将流指向位置拨动到你想要的位置,三个参数,一个是流,一个代表着你要的偏移量,一个代表着你要从哪里开始偏移,只有知道了你要从哪里开始偏移你才能够知道偏移量是多少,才能知道如何才能偏移到想要的位置。返回值类型为整型,在fseek正常使用的情况下它会返回0,出错的话则返回非0值。

偏移的位置一共有三种,SEEK_SET的含义是从文件开始的位置开始偏移,SEEK_CUR的意思是从当前的流的位置开始偏移,比方说我用fgetc对一个文件成功地取出了两个字符,这个时候偏移量为0时取一个,偏移量为1时再取一个,指针也因此在此时指向了偏移量为2的位置。我这时使用SEEK_CUE就会直接从文件的这个位置开始偏移。SEEK_END的含义则是从文件的最后开始偏移(指的是从最后一个字符开始偏移)
1.2使用fseek函数
在文件中存放一串字母

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "r");
	//以只读的方式打开abcd.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf,3,SEEK_SET);
	char ch=fgetc(pf);
	//从pf所指向的文件开头偏移3的位置处取一个字符出来,并赋给ch
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
}

运行结果如下:

果然取出了偏移量为3的字母d

2.ftell函数

2.1认识ftell函数

ftell函数的作用很简单,就是告诉你此时相对于最开始文件位置的偏移量是多少
2.2使用ftell函数
文件中放一串字母

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "r");
	//以只读的方式打开abcd.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf,-5,SEEK_END);
	//将指针从文件最后一个字符偏移-5
	char ch = fgetc(pf);
	//取一个字符,偏移+1,偏移相对最后一个字符为-4
	//根据文件内容可知此时偏移量为7
	int a = ftell(pf);
	printf("偏移量为%d的字符为%c\n",a,ch);
	fseek(pf, 2, SEEK_CUR);
	//将指针从文件当前位置偏移2
	ch = fgetc(pf);
	//取一个字符,偏移+1
	//故此时偏移量为10
	a = ftell(pf);
	printf("偏移量为%d的字符为%c\n", a, ch);
	
	fclose(pf);
	pf = NULL;
}

运行结果如下:

和我们分析的一致。

3.rewind

3.1认识rewind函数

它的作用是将流的位置设置为开头
3.2使用rewind函数

还是用之前那个文件中的内容

代码如下:

#include<stdio.h>
int main()
{
	FILE* pf = fopen("abcd.txt", "r");
	//以只读的方式打开abcd.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf,-5,SEEK_END);
	//将指针从文件最后一个字符偏移-5
	char ch = fgetc(pf);
	//取一个字符,偏移+1,偏移相对最后一个字符为-4
	//根据文件内容可知此时偏移量为7
	int a = ftell(pf);
	printf("偏移量为%d的字符为%c\n",a,ch);
	fseek(pf, 2, SEEK_CUR);
	//将指针从文件当前位置偏移2
	ch = fgetc(pf);
	//取一个字符,偏移+1
	//故此时偏移量为10
	a = ftell(pf);
	printf("偏移量为%d的字符为%c\n", a, ch);
	rewind(pf);//将位置设置为开头
	ch=fgetc(pf);//从开头取一个字符给ch
	printf("%c\n",ch);
	fclose(pf);
	pf = NULL;
}

运行结果如下:

从结果来看,rewind函数确实将流的位置设置成了开头。

今天的分享到这里就结束了,感谢各位友友的来访,祝各位友友前程似锦O(∩_∩)O