目录
前言
什么是文件操作呢?当我们打开自己计算机的存储硬盘,我们会看到各种类型的文件,这些文件中保存着一些数据,我们可以打开文本文件对其进行书写和更改,但是我们能否用自己写的程序来调用这些文件呢?换句话说,我们能否通过代码来实际的操作计算机上的数据文件呢?接下来我们一步一步的学习如何操作文件。
一、使用文件有啥用
我们在搭建自己的通讯录时,每次运行后的数据都在关闭程序的一瞬间全部销毁了,并不会保存到数据文件中。所以我们每次打开进行调试时还需要重新输入一些新的数据。现在我们就想能否让我们的通讯录也像其他程序那样,能够保存我们每次的数据呢?这时候我们就得使用文件进行存储,就需要用代码将我们输入程序中的数据保存到文件中,防止我们在关闭执行程序时将数据也一并删除了。使用文件后,数据会直接保存到我们的硬盘文件中,并不会自己销毁,从而做到数据的持久化。
二、什么是文件
存储到硬盘中的文件就是文件,文件可以是文本文档,图片,程序,一般都包含三个字母的文件扩展名,表示该文件的类型。
在程序设计中我们一般将文件分为两种,程序文件和数据文件。
2.1程序文件
程序文件包括源程序文件(.c),目标文件(.obj),可执行程序(.exe)
2.2数据文件
文件的内容并非是程序,而是程序执行过程中调用的一些数据,例如保存的用户信息,展示的图片等等,这些都需要源程序从这些文件中读取信息,然后在执行程序的功能,这些文件就叫做数据文件,当然,程序运行中的输出数据保存的文件也是数据文件。
2.3文件名
一个文件要有一个唯一的标识,这样我们才能识别,源程序才能找到文件。
文件名由三个部分组成:
该内容表示为:类型为.c的test文件存储在D盘的 “代码” 文件夹下。
三、文件的打开和关闭
介绍完文件是啥,我们接下里学习怎么操作文件。
3.1文件指针
正如我们在代码中操作一个结构体一样,我们需要结构体指针来对结构体中的数据进行修改,那我们操作一个文件同样也需要一个指针,也就是文件指针。
什么是文件指针呢?
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息。这些信息是保存在一个结构体中,该类型的结构体是系统给我们声明的,取名FILE,我们只需要直接调用即可。
至于具体怎么实现的,我们不需要关心,每个编译器都有自己的实现方式,内容大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息。
我们只需要用FILE类型的指针来操作即可。 pf就是一个文件指针变量,pf可以指向一个文件信息区,我们能通过pf找到与其关联的文件。
3.2文件的打开和关闭
了解了文件指针后,我们就需要知道文件指针具体怎么用。
3.2.1打开文件
文件在读写之前我们首先要做的就是打开文件。
在编写文件的时候,打开文件的同时会返回一个指向该文件的文件指针,相当于建立了指针和文件之间的联系。
我们打开文件使用的函数为fopen函数。
什么是fopen函数?MSDN中这样描述:
fopen函数是C语言提供给我们的一个操作文件的库函数。
该函数包含在头文件<stdio.h>中
其返回类型为 FILE* 类型的文件指针
函数包含两个参数,char* 类型的filename,和char* 类型的mode
其作用为,以mode的方式打开文件名为filename所指字符串的文件,并返回一个操作该文件的文件指针。
mode取值:
注意:
(1)以读取模式(mode以字符 'r' 开头)打开文件时,如果该文件不存在或者没有读取权限,则文件打开失败。
(2)以追加模式(mode以字符 'a' 开头)打开文件时,打开后的写入都是从文件末尾处开始。
(3)以只写模式(mode以字符 'w' 开头)打开文件时,若文件存在,则文件长度清0。
当打开文件失败时,返回空指针,因此在使用文件指针之前,先对其进行判定,不为空在进行操作。
下面是fopen函数的具体使用:
首先我们先以只写模式打开文件
文件不存在将在代码所在的文件夹中进行创建
此时我们便可以应用书写函数在将数据写入到文件中 。
3.2.2关闭文件
文件是打开了,我们在使用完还需要将文件关闭,就像使用malloc等内存申请函数一样,使用完空间,需要使用free函数进行释放,我们使用完文件也得将其关闭。
C语言提供给我们关闭文件的操作函数是fclose函数。
fclose函数:
fclose函数的头文件包含在<stdio.h>中
该函数含有一个参数,即指向文件的文件指针,返回类型为int
fclose函数的作用为刷新stream所指向的流,然后关闭与该流相关联的文件。流中留在缓冲区里面尚未写入的数据会被写入,缓冲区尚未读取的数据将会丢弃。
若成功关闭则返回0,检测到错误返回EOF。
简单点说,fclose函数和free函数的使用差不多,就是关闭文件。
四、文件的顺序读写
现在我们已经能够打开和关闭文件了,但是我们还是不知道如何将数据写入文件或者读取文件中的数据。下面我们学习一些顺序读写数据的操作函数。
4.1什么是流
在之前我们从键盘上输入一个字符或者一个参数都是使用scanf函数或者getchar函数,这些函数都是从标准输入流上进行数据的获取,而我们现在要从文件中获取,自然不能使用这些函数。
那什么是流呢?
针对文件,键盘,显示器,打印机等外部设备的数据读写操作都是通过流进行的。
流就是流淌着字符的河流,我们之前用键盘输入的数据会进入流中,然后scanf函数将其提取出来,并将其保存到变量中。也就是说scanf函数相当于打捞键盘输入的流的工具。而对文件中的数据进行读取,文件中的数据也会进入流中,就需要使用打捞文件输入流的工具,也就是其他的函数。
文件中的数据称为文件流。
在我们C语言进行启动的时候,会自动打开三个标准流。
stdin:标准输入流。用于读取普通输入的流,在大多数环境中是从键盘中进行输入。scanf,getchar等函数会从此流中进行字符读取。
stdout:标准输出流。用于写入普通输出的流,大多数环境中为显示器界面。也就是程序调试的黑框框。printf,puts,putchar函数会向这个流写入字符。
stderr:标准错误流。用于写出错误的流。在大多数环境中为输出至显示器界面。
4.2字符形式的输入输出
4.2.1字符输入函数fgetc
接下来我们就具体进行数据读取,现在我们想从文件"test.txt"中读取一些字符到程序中。
怎么做呢?
代码如例1所示:
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取文件
char ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
首先我们先进行文件的打开,创建文件指针pf,使用fopen函数以只读的方式打开文件"test.txt"。
判断pf是否为空,然后在进行下一步的操作。
因为我们文件夹中并没有这个文件,当程序运行时,会报错,所以我们先创建这个文件,并向里面输入一些数据。
然后使用fgetc函数对其进行数据读取,并将读取到的数据保存到字符变量ch中,然后使用printf函数将文件中的数据输出到屏幕中。
fgetc函数:
该函数的头文件为<stdio.h>
返回类型为int,函数包含一个参数,FILE*类型的文件指针。
该函数的功能为从stream指向的输入流中读取unsigned char型的下一个字符的值,并将它转换为int型。然后,若定义了流的文件位置指示符,则将其向前移动。
返回stream所指输入流中的下一个字符。若遇到文件末尾,则返回EOF,并设置该流的文件结束标志,如果读取错误,则返回EOF并设置该流的错误指示符。
所以该程序运行结束应该将文件中的ABCDEF全部打印在屏幕中。
可是我们运行后却出错了
显示没有这个文件,可是我们明明创建了呀?什么原因呢?
答案就是文件扩展名在搞鬼,我们文件显示的时候并没有显示出拓展名,导致我们创建的文件并非是test.txt,而是名为test.txt的txt类型的文件。
这也就导致程序根本找不到test.txt文件。
更改之后,正确显示为:
4.2.2字符输出函数fputc
了解如何从文件中读取数据后,我们还需要了解如何将数据写入到文件中,进行永久的保存。
例2:
int main()
{
//打开文件
FILE* pf = fopen("write.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
char ch = 0;
for(ch='a';ch <= 'z';ch++)
{
fputc(ch,pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
和读取一样,先进行文件的打开,使用fopen函数新建一个write.txt的文本,以只写的模式打开。
然后将a到z的字母写入到文件中。在这里使用是字符写入函数,fputc。
最后使用fclose函数关闭文件。
fputc函数:
该函数的头文件为<stdio.h>
该函数含有两个参数,int类型的c,文件指针stream
返回类型为int
该函数的作用为将c所指定的字符,写入到stream指向的流中。如果此时定义了流的文件位置指示符,就会向指示符指向的位置写入字符,并将文件位置指示符适当地向前移动。在不支持文件定位或者以追加模式打开流的情况下,总是以向输出流的末尾追加字符的方式进行字符输出。
返回写入的字符,如果发生写入错误,就设置该流的错误指示符并返回EOF
4.2.3文本输入函数fgets
当我们学会使用fgetc和fputc函数后,我们可以向文件中输入输出字符,但是我们只能一个一个的输入输出,不能一次性书写一句完整的句子,例如,文本文档中包含一句"hello world"的句子,那我们能否一次性的输入到数组arr中并且打印显示出来呢?
当然可以,那我们就得学习fgets函数。
fgets函数:
该函数的头文件为<stdio.h>
该函数含有三个参数,char*类型的string,int型的n,文件指针类型的stream
string为读取字符串后存储的位置
n为最大的读取字符数,最大为n-1
stream为字符串所在的流
返回类型为char*
该函数的作用为输入流参数stream中读取字符串,并将其存储在字符串string中。fgets 将当前流位置的字符读取到并包括第一个换行符,读取到流的末尾,或者直到读取的字符数等于 n – 1(以先到者为准)。存储在字符串中的结果将追加空字符。换行符(如果为已读)将包含在字符串中。
该函数返回一个保存读取字符串位置的指针,返回NULL以指示错误或者文件结束条件,可使用feof或者ferror来确定是否发生了错误。
所以我们读取字符串的代码如例3所示
例3:
int main()
{
//打开文件
FILE* pf = fopen("write.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[20] = "XXXXXXXXXXXXXXXXXXX";
char* ch =fgets(arr, 20, pf);
fgets(arr, 20, pf);
printf("%s\n",arr);
printf("%s\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
代码中我们包含了两种使用方式,一种使用了char*类型的指针,接收fgets函数的返回值,可以打印出hello world,第二种并没有接收函数的返回值,直接打印目标空间的起始位置也可以。
fgets函数将pf所是指向的文件write.txt中的字符串hello world取出,将其放到arr数组中,最多取出19个字符,然后将\0放入字符串末尾。
通过代码运行我们可以将文件中的hello world打印在屏幕中
4.2.4文本输出函数fputs
fput函数是将字符串输入到文件中的操作函数。
fput函数:
该函数的头文件为<stdio.h>
函数包含两个参数,char*string,文件指针类型的stream
string中存放的为字符串的起始地址,stream指向的是用来维护字符串存储的文件的文件指针。
该函数的作用为将string中的字符串写入到stream指向的文件中
返回类型为int,如果成功,则每个函数都返回一个非负值。出现错误时,fputs 返回 EOF,fputws 返回 WEOF
例4:将字符串写入到write.txt文件中
int main()
{
//打开文件
FILE* pf = fopen("write.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
char arr[256] = "you are my friends";
int ch = fputs(arr, pf);
printf("%d\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
当然,我们在使用fputs函数的时候可以直接将字符串内容作为参数,如下面这样书写
4.3输入输出函数的对比
以上学习的函数都是进行文件操作的函数,当然他们适用于所有流。
fputc和fgetc函数可以理解为升级版的putchar和getchar函数,我们将fgetc函数的文件流指针换为标准输入流,将fputc函数的输出流换为标准输出流,起作用和getchar,putchar一样。
当然,文件操作函数不止这些。
fscanf函数和fprintf函数,这两个函数和我们之前学习的函数scanf和printf函数几乎一样,唯一不同的就是适用于所有输入输出流,而不仅仅是标准输入输出。
sscanf函数和sprintf函数的作用也和scanf、printf函数类似,只不过是将其转化为字符串,或者将字符串中的内容提取出来。
fscanf函数:
头文件:<stdio.h>
说明:从stream所指向的流中读取数据,除此之外和scanf函数完全相同。
返回值:若没有执行任何转换就发生了输入错误,则返回宏定义EOF的值,否则返回成功赋值的输入项数。若在输入中发生匹配错误,则返回输入项数会少于转换说明符对应的实参个数,甚至为0。
fprintf函数:
头文件:<stdio.h>
说明:向stream指向的流进行写入数据。除此之外,与printf函数相同。
返回值:返回发送的字符数,当发生错误时,返回负值。
sscanf函数:
头文件:<stdio.h>
说明:从buffer所指向的字符串中读取数据,除此之外和scanf函数完全相同。
返回值:返回成功转换和分配的字段数;返回值不包括已读取但未分配的字段。返回值为 0 表示未分配任何字段。对于错误或在第一次转换之前到达字符串末尾,则返回值为 EOF。
sprintf函数:
头文件:<stdio.h>
说明:向buffer指向的字符串写入数据。除此之外,与printf函数相同。
返回值:sprintf 返回存储在缓冲区中的字节数,不计算终止空字符。
举个例子,下面代码将s1保存的数据使用fprintf函数写入到文件test2.txt中
例5:
typedef struct student
{
char name[20];
int age;
double s;
}stu;
int main()
{
//打开文件
FILE* pf = fopen("test2.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
stu s1 = { "张三",20,55.5 };
fprintf(pf, "%s %d %lf", s1.name, s1.age, s1.s);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
当然也可以从文件test2中读取数据
以只读模式打开文件test2.txt,然后使用fscanf函数将写入到文件中的s1的内容读取出来并放入到s2中,使用fprintf函数读取数据并输出到标准输出流中。
例6:sscanf函数和sprintf函数的应用
typedef struct student
{
char name[20];
int age;
double s;
}stu;
int main()
{
char buf[256] = { 0 };
stu s1 = { "李四",13,77.2 };
stu s2 = { 0 };
//输入,将结构体类型转换为字符串
sprintf(buf,"%s %d %lf", s1.name, (s1.age), (s1.s));
printf("%s\n", buf);
//输出,从字符串中提取结构体
sscanf(buf, "%s %d %lf", s2.name, &(s2.age), &(s2.s));
printf("%s %d %lf", s2.name, (s2.age), (s2.s));
return 0;
}
最后我们将scanf,fscanf,sscanf,printf ,fprintf,sprintf函数对比一下
4.4二进制输入输出
以上我们对文件的操作,不论是输入还是输出,均是以字符的类型将其保存文件或者读取文件内容。但是,通常程序再保存的时候不会这么保存,大多数情况下都是以二进制的方式进行保存或者读取。
数据以字符类型进行存储时,往往会比二进制存储占用更多的空间,甚至有时还会因为精度问题,导致数据保存和读取的并不一样。
例如,我们在文件中以字符保存10000这个数,将会耗费5个字节,分别保存的是字符 '1' '0' '0' '0' '0'
下面代码将会出现缺失精度的情况:
例7:
int main()
{
//打开文件
FILE* pf = fopen("PI.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写入文件
double pi = 3.14159265358979323846;
fprintf(pf, "%lf", pi);
//关闭文件
fclose(pf);
pf = NULL;
//读取文件
pf = fopen("PI.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
double p = 0;
fscanf(pf, "%lf", &p);
printf("存储文件中的圆周率为:%.23lf\n", pi);
printf("读取文件中的圆周率为:%.23lf\n", p);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果:
精度确实存在问题。
但是如果我们以二进制写入,在以二进制形式拿出就不会出现问题,还是上面的代码,我们使用fwrite和fread函数(下文将会介绍)进行操作。
结果:
输入输出结果一致,精度不会出现偏差。所以我们最好以二进制形式输入输出。
4.4.1二进制输入函数fread
fread函数是将文件中的数据输入到程序中。
fread函数
头文件:<stdio.h>
该函数包含4个参数:void*类型的buffer,指向读取数据后存放的指针;size_t类型的size,代表数据的长度;size_t类型的count,代表数据的个数;文件指针类型的stream,指向读取数据的流。
其作用为,从stream指向的流中最多读取count个长度为size的元素存放到buffer指向的数组中。若定义了流的文件指示符,则以成功读取的字符数为单位向前移动。当发生错误时,该流的文件位置指示符的值不可预测。只读取到某一元素的部分内容时,值不可预测。
返回类型为size_t类型,返回的是成功读取到的元素个数。当发生读取错误时或者达到文件末尾时,元素个数会少于count。若size或者count为0则返回0,这时数组内容和流的状态不会发生变化。
举个例子,下面代码是将文件中存储的结构体内容的二进制形式读取出来并将其打印在屏幕上。
例8:
typedef struct student
{
char name[20];
int age;
double s;
}stu;
int main()
{
//打开文件
FILE* pf = fopen("PI.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取文件
stu s2 = { 0 };
fread(&s2, sizeof(stu), 1, pf);
printf("%s %d %lf", s2.name, s2.age, s2.s);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4.4.2二进制输出函数fwrite
与fread函数对应的就是fwrite函数,起作用是将数据以二进制形式写入文件中。
fwrite函数:
头文件:<stdio.h>
与fread函数类似,该函数也包含4个参数:void*类型的buffer,指向输入文件数据的起始地址;size_t类型的size,代表数据的长度;size_t类型的count,代表数据的个数;文件指针类型的stream,指向输入数据的流。
其作用为,从buffer指向的地址中最多读取count个长度为size的元素存放到stream指向流中。若定义了流的文件指示符,则以成功读取的字符数为单位向前移动。当发生错误时,该流的文件位置指示符的值不可预测。
返回类型为size_t类型,返回的是成功读取到的元素个数。仅当发生写入错误时,元素个数会少于count。
例10:将圆周率以二进制形式写入文本并读取
//打开文件
FILE* pf = fopen("PI.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写入文件
double pi = 3.14159265358979323846 ;
fwrite(&pi,sizeof(double),1,pf);
//关闭文件
fclose(pf);
pf = NULL;
//读取文件
pf = fopen("PI.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
double p = 0;
fread(&p, sizeof(double), 1, pf);
printf("存储文件中的圆周率为:%.23lf\n", pi);
printf("读取文件中的圆周率为:%.23lf\n", p);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果已在前文给出。
五、具体实例:《通讯录》的改造
走到这里,文件操作的常用函数都学过了,现在我们具体应用一下。
还记得前面我们实现的通讯录吗,现在我们对其进行改造,让其变得更加完美一点,把我们输入的数据保存下来,而不会因为程序结束导致我们的数据丢失。
既然需要存储,我们就需要创建一个保存函数,将其放在EXIT选项后面,当用户选择退出程序后,先进行数据的存储,然后在释放空间退出程序。
接下来我们进行具体的实现。
5.1 RestoreContact函数的实现
存储数据操作的必然是通讯录本身,所以参数为结构体con的指针。
和前面一样,我们将数据保存到文件中,是对文件的写入操作,所以以只写的方式打开文件Contact.dat文件。如果目录不存在,函数会自动创建。
然后就是将通讯录中的数据写入,我们使用fwrite函数进行二进制写入操作。
最后进行关闭文件操作。
具体代码如下所示:
void RestoreContact(Contact* pc)
{
FILE* pf = NULL;
//打开文件
pf = fopen("Contact.dat", "wb");//以二进制形式写入文件
if (pf == NULL)
{
perror("ResoreContact::fopen");
return;
}
//写入文件
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->data+i, sizeof(PeoInfo), 1, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
printf("保存成功!\n");
}
5.2 初始化函数的修改
虽然现在数据进行了保存,但是我们每次打开通讯录并不会加载这些保存的数据,所以我们还需要在打开通讯录后对数据进行加载进来,也就是读取文件信息。
我们将加载信息操作放到初始化函数中。这样函数在进行通讯录初始化的时候就会将原有的信息加载到程序中。
其余内容并没有进行更改,只是在末尾增加了加载信息函数LoadContact
进入函数内部还是进行打开文件操作,因此我们必须要先确保文件中存在Contact.dat文件。
然后进行的就是读取操作,使用fread函数读取数据,每次从pf中读取一个数据,当读取不到数据时,由于fread函数返回的是读取的元素个数,读取不到,必然返回0,因此循环将结束。
读取到数据,进入循环内部,在进行数据存放时,我们必须要保证空间是否足够,所以调用增容函数进行判断增容。随后进行数据的保存,将每一个数据保存到data数组中,下标pc->sz自增1。
最后关闭文件。具体代码如下所示。
代码:
void LoadContact(Contact* pc)
{
FILE* pf;
//打开文件
pf = fopen("Contact.dat", "rb");
if (pf == NULL)
{
perror("InitContact::LoadContact::fopen");
return;
}
//读取文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf))
{
//先扩容
IncreaseCapacity(pc);
pc->data[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
5.3 一键删除功能的修改
由于我们对初始化函数进行了更改,所以一键全删功能就不能在调用初始化函数对其进行操作,我们只需要将更改前的初始化函数从新封装成一个新函数,进行调用即可。
代码:
//一键清空函数---(只初始化,不加载文件)
void ClsContact(Contact* pc)
{
assert(pc);//避免为空指针
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
PeoInfo* tmp = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo));
if (tmp == NULL)
{
perror("ClsContact::calloc");
printf("\n");
return;
}
pc->data = tmp;
}
所有代码更改后,通讯录便可以进行数据的读取和保存了。
完整代码路径:
Contact-22-3-29/Contact-22-3-29 · 冰冰棒/C语言初学者的小仓库 - 码云 - 开源中国 (gitee.com)
六、文件的随机读写
6.1 fseek函数
前面我们进行的文件读写操作都是顺序读写,那能不能随机读写呢?例如我想在一行字符中间填写数据。答案是肯定的,我们当然可以进行随机读写,使用的函数就是fseek函数。
fseek函数:
头文件:<stdio.h>
该函数含有三个参数。第一个参数为文件指针 stream,指向所操作的文件;第二个参数为long类型的offset,即相对于origin所指向位置的偏移量。第三个参数必须为下列中的一个
SEEK_CUR:文件指针当前位置
SEEK_END:文件末尾的位置
SEEK_SET:文件开始的位置
fseek 函数将与流关联的文件指针(如果有)移动到与源位置偏移字节的新位置。流上的下一个操作将在新位置进行。在打开进行更新的流上,下一个操作可以是读取或写入。
返回值为int类型,如果成功,fseek 返回 0。否则,它将返回非零值。在无法查找的设备上,返回值未定义。
举个例子,使用fseek来操作文件指针
例11:
int main()
{
//打开文件
FILE* pf = fopen("test3.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写入文件
fputs("abcdef", pf);
//关闭文件
fclose(pf);
pf = NULL;
pf = fopen("test3.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = fgetc(pf);//拿到的应该是a
printf("%c", ch);
fseek(pf, 3, SEEK_CUR);
ch=fgetc(pf);//拿到的应该是e
printf("%c", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
6.2 ftell函数
ftell函数的作用是返回文件指针相对于起始位置的偏移量,当我们不知道现在文件指针位于什么位置时,可以调用ftell函数来获得。
ftell函数:
头文件:<stdio.h>
该函数的参数为文件指针,传入文件指针,返回相对于起始位置,文件指针的偏移量。
注意:
ftell 返回当前文件位置。ftell 返回的值可能无法反映以文本模式打开的流的字节偏移量,因为文本模式会导致回车符换行转换。将 ftell 与 fseek 结合使用可正确返回到文件位置。出错时,ftell 返回 –1,并且 errno 设置为 ERRNO.H 中定义的两个常量之一。EBADF 常量表示流的参数不是有效的文件句柄值,或者不引用打开的文件。EINVAL 表示向函数传递了无效的流参数。在无法查找的设备(如终端和打印机)上,或者当 stream 不引用打开的文件时,返回值是未定义的。
6.3 rewind函数
rewind函数的作用就是让我们的文件指针回到起始位置。
rewind函数:
头文件:<stdio.h>
参数为想要回到起始位置的文件指针。
函数功能将与流关联的文件指针重新定位到文件的开头。
类似于(void) fseek( stream, 0, SEEK_SET );
七、文件读取结束的判定
在文件读取过程中,我们不能用feof函数的返回值直接来判断文件是否结束。 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
文本文件的读取结束判断返回值是否为EOF(fgetc)或者是否为NULL(fgets),二进制文件读取判断是否结束,判断返回值是否小于实际读取的个数。
feof函数判断文件指针是否是遇到了文件末尾结束,ferror函数判断文件是否是读取失败结束。
feof函数:
头文件:<stdio.h>
该函数接收需要判断的文件指针,并判断该文件指针是否是遇到了文件末尾而结束的,如果不是文件末尾则返回0,否则返回非0。
ferror函数:
头文件:<stdio.h>
该函数接收需要判断的文件指针,并判断该文件指针是否是读取失败而结束的,如果不是则返回0,否则返回非0。
总结
文件操作远不止这些,文件的操作C语言提供的并非很方便,当然比起指针,结构体等知识来说,文件操作用的是相对较少的,如有兴趣,大家可以自行查看更多的资料。