文件操作管理
目录
1.为什么使用文件
2.什么是文件?
2.1 程序文件
2.2 数据文件
2.3 文件名
3. 二进制文件和文本文件
4.文件的打开和关闭
4.1流和标准流
4.1.1 流
4.1.2 标准流
4.2 文件指针
4.3 文件的打开和关闭
4.3.1 fopen
4.3.2 flose
5. 文件的顺序读写
5.1 fputc
5.2 fgetc
5.3 feof 和 ferror
5.4 fputs
5.5 fgets
5.6 fprintf
5.7 fscanf
5.8 fwrite
5.9 fread
5.10 对比一组函数
5.10.1 sprintf
5.10.2 sscanf
6. 文件的随机读写
6.1 fseek
6.2 ftell
6.3 rewind
7. 文件缓冲区
7.1 fflush
8. 更新文件
1.为什么使用文件
我们知道数据是存储在内存中的,当程序退出之后,申请到的内存空间就回收了,数据就丢失了。但如果我们将数据存储到文件中,就可以将数据“永久”保存。
2.什么是文件?
我们所说的文件时存储在电脑硬盘上的,只要硬件不损坏就可以一直存在。
但是在程序中,我们一般谈的文件由两种:程序文件、数据文件。
2.1 程序文件
程序文件包含源程序文件(后缀为.c),目标文件(windows环境后缀时.obj),可执行程序(windows环境的后缀时.exe)。
2.2 数据文件
所谓数据文件就是要么提供数据给程序,要么可以将数据存储到这里。后面所讨论的都是数据文件。
2.3 文件名
既然它是一个文件,那是不是应该需要一个名字,并且要有一个唯一的文件标识,以便用户识别和引用。一个文件名包含3个部分:文件路径+文件主干名+文件后缀
比如:c :\ code \ data.txt 文件路径是c :\ code \,文件主干名是data,文件后缀是.txt。
为了方便起见,文件标识常被称为文件名
3. 二进制文件和文本文件
根据数据的组织形式,数据文件被分成文本文件和二进制文件。
数据在内存中以二进制的形式存储,若不加转换输出到外存(硬盘)上,就是二进制文件;若转换成ASCII字符的形式输出到外存上就是文本文件。
那这里有个小小的问题,一个数据在文件中是怎么存储的呢?
如果是字符,那只能是以ASCII形式存储,如果是数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而以二进制的形式输出,则在磁盘上只占用4个字节。
4.文件的打开和关闭
4.1流和标准流
4.1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外部设备中获取数据,不同的外部设备的输入输出操作各不相同,但是为了方便程序员对各种设备进行方便的操作,我们抽象出流的概念。
C语言程序针对文件,画面,键盘等的数据输入输出操作都是通过流操作的。程序员从流中读取数据,或者是想把数据写入流里,都是要打开流,然后进行操作。
4.1.2 标准流
写到这里,不知道是否由小伙伴会有疑问?那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语言程序在启动的时候,就默认打开了3个流:
stdin - 标准输入流(键盘),在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
stdout - 标准输出流(屏幕),大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
stderr - 标准错误流,大多数环境中输出到显示器界面(屏幕)。
这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作。并且stdin,stdout,stderr三个流的类型是: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;
每打开一个文件,系统会自动创建一个FILE类型的结构变量,FILE类型结构变量自动与文件关联,这个变量用来存放文件信息。
那我们该怎么找到这个文件信息区呢?那就需要我们找到其地址,将其地址存放在FILE*的指针变量里面:
1 FILE * pf ;//文件指针变量
pf是指向FILE类型数据的指针变量。可以是pf指向某个文件的文件信息区。通过该文件信息区中的信息就可以访问文件。换句话来说,通过文件指针变量能够间接找到与它关联的文件。
4.3 文件的打开和关闭
文件在读写之前应该先打开文件,在使用完之后再关闭文件。
在编写程序的时候,打开文件的同时,都会返回一个FILE*的指针变量指向该文件。
再ANSI C规定使用fopen函数来打开文件,fclose函数来关闭文件
4.3.1 fopen
//打开文件
FILE* fopen ( const char * filename , const char * mode );
功能:fopen函数是用来打开参数filename指定的文件,同时将打开的文件和一个流进行关联,打开文件的同时,都会返回一个FILE*的指针变量指向该文件,后续对流的操作是通过返回的FILE*类型的指针进行的。具体对流(关联的文件)的操作是通过mode来指定的。
参数:
filename: 表示被打开的文件的名字,这个名字可以是相对路径,也可以是绝对路径。例如fopen("data.txt")中的data.txt就是相对路径,表示在当前的工程目录下的data.txt文件。
在文件操作中,我们一般 “ . ”表示当前路径,“ .. ”表示上一级路径,绝对路径是从根上开始写,例如:c:/.../.../.../data.txt,这就是绝对路径
mode:表示对打开文件的操作方式。
返回值:
若文件打开成功,该函数返回一个FILE*类型的指针,该指针可用于后续操作中标识对应的流。
若打开失败,则返回NULL指针,所以对于fopen函数的返回值一定要做判断,防止对空指针进行操作。
mode表示对文件的操作方式:
注意:以“w”和“w+”形式打开文件,若没有文件,则创建文件;如果该文件存在且里面有内容,则会将文件里面的内容清除 。
代码演示:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");//以“r”的形式打开文件if (pf == NULL) {perror("fopen");return 1;}printf("成功打开\n");//不在使用的时候,关闭文件return 0;
}
4.3.2 flose
//函数原型
int flose( FILE * stream ) ;
功能:关闭参数stream关联的文件,并取消其关联关系。任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区的内容将会被舍弃。
输出缓冲区:
参数:
stream:指向要关闭的流的FILE*的指针。
返回值:成功关闭stream指向的流会返回0,否则返回EOF。
代码演示:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");//以“r”的形式打开文件if (pf == NULL) {perror("fopen");return 1;}printf("成功打开\n");//不在使用的时候,关闭文件flose(pf);pf = NULL;return 0;
}
当f使用flose的时候,会将缓冲区中未写入文件的数据一次性全部写入文件。flose函数不会主动的将指针pf置为NULL指针,所以需要我们手动置为NULL指针。
5. 文件的顺序读写
在进行文件读写的时候,会涉及下面的这些函数,接下来让我们一一学习:
上面所说的适用于所有输入流一般是指适用于标准输入流和其他输入流(如文件输入流),所谓标准输入流也可以简单的认为是键盘,标准输入流是stdin;所有输出流一般是指适用于标准输出流和其他输出流(如文件输出流),所谓标准输出流可以简单的认为是屏幕,标准输出流是stdout
5.1 fputc
fputc 函数原型(写入数据)
int fputc (int character , FILE * stream );
功能:将参数character指定的字符写入到stream指向的输出流中写入字符,输出流就是文件和标准输出流(stdout 屏幕)。在写入字符之后,还会调整指示器(光标)。字符被写入流内部位置指示器当前指向的位置,随后该指示器自动向前移动一个位置。简单来说就是写入一个字符光标往后自动跳一个。
参数:
character:被写入的字符
stream:是一个FILE*类型的指针,指向了输出流(通常是指文件或者标准输出流stdout).
返回值:
成功时返回写入的字符(以int 形式,ASCII码)失败是返回EOF(-1),错误指示器被设置,可通过ferror()检查具体错误。
代码演示:
//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);fputc('d', pf);//关闭文件fclose(pf);pf = NULL;return 0;
}//向stdout写入
int main()
{fputc('a', stdout);fputc('b', stdout);fputc('c', stdout);fputc('d', stdout);return 0;
}
5.2 fgetc
fgetc函数的原型(读取数据)
int fgetc( FILE * stream );
功能:从参数stream指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符,读取这个字符后,文件指示器自动前进到下一个字符。
参数:
stream : FILE*类型的文件指针,可以是stdin,也可以是其他输入流的指针。如果是stdin就从标准输入流(键盘)读取数据。如果是文件流指针,就从文件读取数据。
返回值:
成功读取是返回读取的字符(以int形式)。
若调用时流已处于文件末尾,函数返回EOF,并设置流的文件结束指示器(可以用feof函数检测);若发生读取错误,函数返回EOF并设置流的错误指示器(可以用ferror函数检测)。
代码演示:
//使用fgetc读取文件数据
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int ret = fgetc(pf);printf("%c\n", ret);fclose(pf);pf = NULL;return 0;
}//向stdin中读取数据
int main()
{int ch = fgetc(stdin);fputc(ch, stdout);return 0;
}
5.3 feof 和 ferror
//检测stream指针指向的流是否遇到文件末尾
int feof ( FILE * stream ) ;
//检测stream指针指向的流是否发生读/写错误
int ferror ( FILE * stream );
如果在读取文件的过程中,遇到了文件末尾,文件读取就会结束。这时读取函数就会在对应的流上设置一个文件结束指示符,这个指示符可以通过feof函数检测到。如果feof函数检测到文件结束指示符已经被设置,则返回一个非0的值,如果没有设置则返回0.
如果在读/写文件的过程中,发生了读/写错误,文件读取就会结束。这是读/写函数就会在对应的流上设置一个错误指示器,这个错误指示器可以通过ferror函数来检测。如果ferror函数检测到错误指示器已经被设置,则会返回一个非0的值,如果没有被设置,则会返回0。
使用场景:如果返回了EOF,那是什么原因返回了EOF呢?就需要feof和ferror函数进行检测。
代码演示:
feof:
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int i = 0;for (i = 0; i <= 10; i++){int ch = fgetc(pf);if (ch == EOF){if (feof(pf))printf("遇到文件末尾\n");else if (ferror(pf))printf("读取错误\n");}elsefputc(ch, stdout);}fclose(pf);pf = NULL;return 0;
}
ferror:
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}int i = 0;for (i = 0; i <= 10; i++){int ch = fgetc(pf);if (ch == EOF){if (feof(pf))printf("遇到文件末尾\n");else if (ferror(pf))printf("读取错误\n");}elsefputc(ch, stdout);}fclose(pf);pf = NULL;return 0;
}
以“w”的形式读取文件,很明显时一个读取错误。
5.4 fputs
fgets函数原型(写入数据)
int fputs( const char * str , FILE * stream );
功能:将参数str指向的字符串写入到参数stream指定的流中(不包含结尾的\0),适用于文件流或标准输出流(stdout) 。
参数:
str:str是指针,指向了要写入的字符串(必须以\0结尾)
stream:是一个FILE*的指针,指向了要写入字符串的流。
返回值:
成功时返回非负整数;
失败时返回EOF(-1),同时会设置流的错误指示器,可以使用ferror函数检测错误原因。
代码演示:
//向文件中写入
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputs("I love you", pf);fclose(pf);pf = NULL;return 0;
}//向标准输出流中写入
int main()
{fputs("I love you", stdout);return 0;
}
注意:fputs函数遇到 \0 ,\0即后面的都不写入。
5.5 fgets
fgets函数原型(读取数据)
char * fgets ( char * str , int num, FILE * stream );
功能:从stream指定输入流中读取字符串,至读取到换行符\n、文件末尾(EOF)或者达到指定字符数(包含结尾的\0),然后将读取到的字符串存储到str指向的空间中。
参数:
str:时指向字符数组的指针,str指向的空间用于存储读取到的字符串。
num :最大读取字符数(包含结尾的\0,实际上最多读取num-1个字符)。
stream :输入流的文件指针(如文件流或stdin)。
返回值:
成功时返回str指针。
若在尝试读取字符时遇到文件末尾,则设置文件结束指示器,并返回NULL,需通过feof()检测。
若发生读取错误,则设置流错误指示器,并返回NULL,通过ferror()检测。
使用细节:
若读取到换行符\n,会将其包含在字符串中(除非超过num-1限制),然后以\0结尾。
文件末尾没有换行符时,字符串以\0结尾,不包含\n。
代码演示:
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}/*char* p = (char*)malloc(5 * sizeof(char));*/char p[5] = { 0 };fgets(p, 5, pf);fputs(p, stdout);fclose(pf);pf = NULL;return 0;
}
5.6 fprintf
int fprintf ( FILE * stream , const char * format , ... );//写入数据
//类比 printf 学习 int printf ( const char * format , ... );
功能:fprintf时将格式化数据写入指定文件流的函数。它与printf 类似,但可以输出到任意文件(如磁盘文件、标准输出、标准错误等,而不仅限于控制台)。
参数:
stream :指向FILE对象的指针,表示要写入的文件流(ut、文件指针)。
format :格式化字符串,包含要写入的文本和格式说明符(如%d,%c等)。
... :提供与格式符对应的数据。
返回值:
成功时,返回写入的字符总数。
失败时,先设置对应流的错误指示器,再返回负值,可以通过ferror()检测。
代码演示:
struct s
{char name[20];int age;char grade;
};
int main()
{struct s stu = { "zhangsan",20,'A' };FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fprintf(pf, "%s %d %c", stu.name, stu.age, stu.grade);fclose(pf);pf = NULL;return 0;
}
5.7 fscanf
int fscanf (FILE * stream , const char * format , ... );//读取数据
//类比scanf学习
功能:fscanf时从指定文件流中读取格式化数据的函数。类似于scanf,但可以指定输入源(如文件,标准输入流等),而非仅限于控制台输入。适用于从文件解析结构化数据。
参数:
stream :指向FILE对象的指针,表示要读取的文件流in、文件指针)。
format :格式化字符串,定义如何解析输入数据(如%d,%c等)。
... :提供存储数据的变量地址。
返回值:
成功时,函数返回成功填充到参数列表中的项数。该值该值可能与预期项数⼀致,也可能因以下原因少于预期(甚⾄为零):◦ 格式和数据匹配失败;
◦ 读取发⽣错误;
◦ 到达⽂件末尾(EOF)。
• 如果在成功读取任何数据之前发⽣:
◦ 发⽣读取错误,会在对应流上设置错误指⽰符,则返回EOF
◦ 到达⽂件末尾,会在对应流上设置⽂件结束指⽰符,则返回 EOF
代码演示:
struct s
{char name[20];int age;char grade;
};
int main()
{struct s stu = {0};FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}fscanf(pf, "%s %d %c", stu.name, &stu.age, &stu.grade);fprintf(stdout, "%s %d %c", stu.name, stu.age, stu.grade);fclose(pf);pf = NULL;return 0;
}
5.8 fwrite
函数原型(写入数据,以二进制的形式)
size_t fwrite ( const void * ptr , size_t size , size_t count , FILE * stream );
功能:将ptr所指向的数据块写入stream指向的文件流中,这里时以二进制的形式。
参数:
ptr: 指向要写入数据的指针。(简单来说就是存放数据的)
size:要写入每个数据块的大小(单位时字节)。
count: 要写入的数据项的数量。
stream: 指向FILE类型结构体的指针,指定了要写入数据的文件流。
返回值:
实际写入的数据项数量。如果发生错误,返回值可能小于count。
注意事项:
需要包含<stdio.h>头文件。
在使用fwrite()函数之前,需要保证文件时以二进制可写方式打开(“wb”)。
fwrite()通常用于二进制数据的写入,如果写入文本数据,谨慎处理换行符和编码问题。
代码演示:
#include<stdio.h>
int main()
{int arr[] = { 1,2,3,4,5,6 };FILE* pf = fopen("data.txt", "wb");if (pf == NULL){perror("fopen");return 1;}fwrite(arr,sizeof(arr[1]),6, pf);fclose(pf);pf = NULL;return 0;
}
5.9 fread
函数原型(读取数据,从二进制文件中读取数据,以二进制形式读取出来)
size_t fread( void * ptr , size_t size , size_t count , FILE * stream );
功能:函数从stream指向的文件流中读取数据块,并将其存储到ptr指向的内存缓冲区中(简单来说就是一个存储数据的地址)。
参数:
ptr: 指向内存区域的指针,用于存储从文件中读取到的数据。
size: 要读取的每个数据块的大小(以字节为单位)。
count: 要读取的数据块的数量。
stream: 指向FILE类型结构体指针,指定了要从中读取数据块的文件流。
返回值:返回实际读取的数据块数量。
注意事项:
在使用fread()函数之前,需要确保文件是以二进制可读方式打开(“rb”)。
ptr指向的内存区域必须足够大,以便于存储指定数量和大小的数据块。
如果fread()成功读取到指定数量的数据块,则返回值等于count; 如果读取数量少于count,可能已经到达文件末尾或者发生读取错误。
在二进制文件读取时,fread()是常用的函数,但对于文件文本读取,通常使用fgets()/fscanf()。
代码演示:
#include<stdlib.h>
int main()
{FILE* pf = fopen("data.txt", "rb");if (pf == NULL){perror("fopen");return 1;}int* ptr = (int*)malloc(10 * sizeof(int));fread(ptr, sizeof(int), 6, pf);for (int i = 0; i < 6; i++){printf("%d ", *(ptr + i));}fclose(pf);pf = NULL;free(ptr);ptr = NULL;return 0;
}
5.10 对比一组函数
scanf / fscanf / sscanf
printf / fprintf / sprintf
scanf/printf,fscanf/fprintf这些函数我们前面都已经学过,那sscanf/sprintf这两个函数是干什么的?接下来让我们来学习一下:
5.10.1 sprintf
函数原型(不是对文件进行操作)
int sprintf ( char * str , const char * format , ... );
功能:将格式化数据写如字符数组(字符串),简而言之就是将格式化的数据转换成字符串。 常用于动态生成字符串、拼接数据或转换数据格式。将...中的格式化数据转换成字符串然后存放在str所指向的字符数组中。
参数:
str: 指向字符数组的指针,用于存储生成的字符串(需足够大,防止溢出)。
foemat: 格式化字符串,定义输出格式(例如%d、%c等);
...:提供与格式字符串中说明符对应的数据。
返回值:
成功时:返回写入str的字符数(不包含结尾的\0);失败时返回负值
代码演示:
struct s
{char name[20];int age;char grade;
};
int main()
{struct s stu = { "zhangsan",20,'A' };char str[100] = { 0 };sprintf(str, "%s %d %c", stu.name, stu.age, stu.grade);printf("%s\n", str);return 0;
}
5.10.2 sscanf
函数原型
int sscanf ( const char * str , const char * format , ...);
功能:从字符串中读取格式化数据。常用于解析字符串中的结构化数据(如提取数字、分割文本等)。
参数:
str: 要解析的源字符串(输入数据来源)。
format: 格式化字符串,定义如何解析数据(如%d、%c等)。
... : 提供存储数据的变量地址(需要与格式字符串中的说明符匹配)。
注意:从str中读取格式化数据,然后存放在变量地址里面。
返回值:
成功时返回成功解析并赋值的参数数量(非负值)。失败或者未匹配任何数据:若输入结束或解析失败,返回EOF。
代码演示:
struct s
{char name[20];int age;char grade;
};
int main()
{struct s stu = { "zhangsan",20,'A' };char str[100] = { 0 };struct s stu2 = { 0 };sprintf(str, "%s %d %c", stu.name, stu.age, stu.grade);sscanf(str, "%s %d %c", stu2.name, &(stu2.age), &(stu2.grade));printf("%s %d %c", stu2.name, stu2.age, stu2.grade);return 0;
}
对比:
scanf | 针对标准输入(stdin)的格式化输入函数 |
printf | 针对标准输出(stdout)的格式化输出函数 |
fscanf | 针对所有输入流(文件流和stdin)的格式化输入函数 |
fprintf | 针对所有输出流(文件流和stdout)的格式输出函数 |
sprintf | 将格式化的数据转换成字符串 |
sscanf | 从字符串中读取格式化的数据 |
既然学习了文件的顺序读写,那如果不按照顺序读写,那该怎么做呢?
6. 文件的随机读写
所谓文件的随机读写那就是要定位文件指针(简单来说就是光标)
6.1 fseek
我们可以根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)。
int fseek ( FILE * stream , long int offset , int origin ) ;
参数:
origin: SEEK_SET表示文件开始位置;SEEK_CUR表示文件指示器(文件内容的光标)的当前位置;SEEK_END表示文件末尾。
offset : 是相对于 origin 的偏移量,左偏移为负值,右偏移为正值
代码演示:
int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, -3, SEEK_END);//光标从末尾向左偏移三个位置ch = fgetc(pf);printf("%c\n", ch);fclose(pf);pf = NULL;return 0;
}
6.2 ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream ) ;
代码演示:
int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}fseek(pf, -3, SEEK_END);int count = ftell(pf);printf("%d", count);fclose(pf);pf = NULL;return 0;
}
fseek(pf, 0, SEEK_END); int count = ftell(pf); printf("%d", count); 可以求出文件中的数据个数。
6.3 rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream ) ;
代码演示:
int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}fseek(pf, 0, SEEK_END);//先让光标来到末尾rewind(pf);//再让光标返回起始位置int count = ftell(pf);printf("%d", count);fclose(pf);pf = NULL;return 0;
}
7. 文件缓冲区
我们向文件中输入数据,并不是我们输入一个数据,计算器就给我们输出一个数据到硬盘中,如果我们从内存向磁盘输出数据会先送到输出缓冲区,等到装满输出缓冲区后才一起送到磁盘上。如果是从磁盘向计算机读取数据,从磁盘数据中读取数据输入到内存输入缓冲区,等到缓冲区满了之后,才一起送到程序数据区。
7.1 fflush
int fflush ( FILE * stream ) ;
功能:强制刷新缓冲区,将写入的数据强制写入文件中。
对输出流:将缓冲区中未写入的数据立即写入文件。
对输入流:行为由具体实现决定。
参数为NULL时:刷新所有打开的输出流。
参数:
stream :指向⽂件流的指针(如 stdout 、⽂件指针等)
返回值:成功返回 0 ,失败返回 EOF
注意事项:
1.仅对输出流或更新流(最后一次操作为输出)有明确刷新行为。
2.输入流的刷新行为不可移植
3.程序正常终止(exit)或者调用fclose时自动刷新,但程序崩溃时缓冲区数据可能丢失。
结论:因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件,如果不做,可能导致读写文件的问题。
注意:fflush()函数执行后,文件指示器(文件内容的光标)到达末尾。
8. 更新文件
上面三种打开方式需要注意,以“r+”的方式打开文件时,如果文件不存在就会出错了;以“w+”的方式打开文件,如果文件不存在,则新建文件,如果文件存在且文件内有内容则重置内容。
关键要点:
1.在写完文件后,要继续读文件的时候,在读取之前一定要使用fflush()刷新文件缓冲区,或者使用fseek(),rewind()重新定位文件指示器的位置。
2.在读完文件后,需要继续写文件之前,使用fseek(),rewind()重新定位文件指示器的位置。
代码演示:
int main()
{FILE* pf = fopen("test.txt", "w+");if (pf == NULL){perror("fopen");return 1;}//将abcdef写到文件中fputs("abcdef", pf);//写完之后,我要继续读文件fflush(pf);rewind(pf);int ch = fgetc(pf);fputc(ch, stdout);//读完文件后,我想读取cfseek(pf, 2, SEEK_CUR);ch = fgetc(pf);fputc(ch, stdout);读取c之后,我想在文件末尾加上hellofseek(pf, 0, SEEK_END);fputs("hello", pf);读取全部fflush(pf);rewind(pf);char str[20];fgets(str, 20, pf);printf("\n");fputs(str, stdout);关闭文件fclose(pf);pf = NULL;return 0;
}