【C语言进阶】文件操作
目录
为什么使用文件
什么是文件
程序文件
数据文件
文件名
文件的打开和关闭
文件指针
文件的打开和关闭
文件的顺序读写
函数介绍
字符输入函数:fgetc( )
字符输入函数:fputc( )
文本行输入函数: fgets( )
文本行输出函数: fputs( )
格式化输出函数fprintf()
格式化输入函数fscanf( )
二进制输出函数fwrite( )
二进制输入函数:fread( )
printf fprintf sprintf ; scanf fscanf sscanf 对比
sprintf()函数
sscanf()函数
文件的随机读写
fseek() 函数
ftell()函数
rewind()函数
文本文件和二进制文件
文件读取结束的判定
被错用的feof
文件缓冲区
为什么使用文件
我们都知道,通常情况下,程序的结束也就意味着里面存储的数据将会被销毁,但是有些情况下我们需要保存数据,那就要用到文件了,例如我们要做一个通讯录系统,我们肯定是想要输入的联系人的信息保存下来,这时候就要用文件来保存数据。使用文件我们可以将数据直接存放在电脑的硬盘上,做到数据的持久化。
什么是文件
硬盘上的文件就是文件。在程序设计中,我们一般讲的的文件有两种(从文件功能的角度来分类):程序文件、数据文件 。
程序文件
包括源程序文件(后缀为 .c),目标文件(windows环境后缀为 .obj),可执行程序(windows环境后缀为 .exe)
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件或者输出内容的文件 。本篇博客也是主要将数据文件。
文件名
一个文件要有唯一的标识,以便使用和识别。文件名包括3个部分:文件路径 + 文件名主干 + 文件后缀。例如:c:\code\test.txt 。(文件路径指的是从盘符到该文件所经历的路径中个符号名的集合)
文件的打开和关闭
文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称文件指针。
缓冲文件系统后面会讲,你若想要先了解可以翻到博客结尾。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件的状态、文件的位置等)。这些信息是保存在一个结构体变量中。该结构体类型是有系统声明的,取名为FILE。(不同的编译器的FILE类型包括的内容不完全相同,但是大同小异。)
每打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息。一般都是通过一个FILE的指针来维护这个FILE结构的变量。
例:
FILE* pf//文件指针变量
定义pf是一个指向FILE类型数据的指针变量,可以是pf指向某个文件的文件信息区(一个结构体变量)。通过该文件信息区的信息就能够刚问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
文件的打开和关闭
文件在读写之前先要打开文件,应用结束后也要关闭文件 。
打开文件使用的函数为 fopen()
函数原型:FILE* fopen( const char* filename, const char* mode)
filename:文件名。
mode: 文件使用的方式。
FILE* :返回一个文件指针。
关闭文件使用的函数 fclose( )
函数原型: int fclose( FILE* stream )
stream :指向文件结构体的指针。
例:
FILE* pf = NULL;//打开文件pf = fopen("test.txt", "w");//关闭文件fcolse(pf);pf = NULL;
mode的取值:
例:
#includeint main(){FILE* pf = NULL;//打开文件pf = fopen("test.txt", "w");//只读;当指定文件不存在时,自动创建一个新的文件if (NULL == pf)//判断是否打开失败{perror("fopen:");//出错原因return 1;}//关闭文件fclose(pf);pf = NULL;return 0;}
可以看出在程序执行后创建了一个.txt的文本文件。
文件的顺序读写
函数介绍
在C语言中,术语流(stream)表示任意输入的源或者任意输出的目的地。许多小型程序都是通过一个流(通常和键盘有关)获得全部的输入,并且通过另外一个流(通常和屏幕有关)写出全部的输出 。简单一点就是讲程序通过流来输出、出入。
我们的程序一般都要包含, 这个头文件里有三个标准流可以直接使用,不用声明,也不用打开和关闭,也就是说当程序运行起来这三个流是默认打开的。它们是:
字符输入函数:fgetc( )
函数原型:int fgetc( FILE* stream)
stream: 指向文件结构体的指针
返回类型为int 当读入错误或文件结尾返回EOF.
使用:
这里采用只读,所以要先创建一个文件。
#includeint main(){FILE* pf = NULL;//打开文件pf = fopen("test05.txt", "r");if (pf == NULL)//判断文件是否打开成功{perror("fopen:");return 1;}//读取文件int ch = 0;while ((ch = fgetc(pf)) != EOF){printf("%c ",(char)ch);}//关闭文件fclose(pf);pf = NULL;return 0;}
运行结果:
字符输入函数:fputc( )
函数原型:int fputc( int c, FILE*stream)
c : 写入的字符
stream: 指向文件结构体的指针
返回类型为int,当写入失败时返回EOF
使用:
这里采用只写:当文件使用方式为只写时如果没后制定的文件则会创建一个新的,如果有则会将文件里的内容清空,之后在写入。
int main(){FILE* pf = NULL;//打开文件pf = fopen("test05.txt", "w");if (pf == NULL)//判断文件是否打开成功{perror("fopen:");return 1;}//写入文件char ch = 'd';for (; ch <= 'z'; ch++){fputc(ch, pf);}//关闭文件fclose(pf);pf = NULL;return 0;}
程序运行前文件内容:
程序运行后:
文本行输入函数: fgets( )
函数原型 char* fgets( char* string, int n, FILE* stream )
string: 数据储存的地址
n : 要读取的最大字符数
stream:指向文件结构体的指针
返回类型为char*, 返回NULL表示错误或者读取结束。
使用:
这里使用只读的打文件打开方式
int main(){FILE* pf = NULL;//打开文件pf = fopen("test05.txt", "r");if (pf == NULL)//判断文件是否打开成功{perror("fopen:");return 1;}//读取文件char arr[20] = { 0 };while ((fgets(arr, 20, pf) != NULL)){printf("%s ", arr);}//关闭文件fclose(pf);pf = NULL;return 0;}
文件里存储内容:
运行结果:
文本行输出函数: fputs( )
函数原型 int fputs( const char* string, FILE* stream )
string:输出字符的地址
stream:指向文件结构体的指针
返回类型为int,成功返回一个非负值,错误返回EOF。
使用:
int main(){FILE* pf = NULL;//打开文件pf = fopen("test05.txt", "w");if (pf == NULL)//判断文件是否打开成功{perror("fopen:");return 1;}//写入文件const char* arr = "qwertyghy";fputs(arr, pf);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
格式化输出函数fprintf()
函数原型:int fprintf( FILE* stream, const char*format[, argument]...)
stream: 指向文件结构体的指针
format: 格式控制字符串
argument:可选参数
看到这里可能不太明白 我们可以看一下printf( )函数的原型:
int printf( const char* format[,argument]...)
两个函数对比一下fprintf()函数多了stream参数,我们知道printf()是输出在屏幕上,那fprintf()则是输出在文件里。
使用:
#includetypedef struct stu{char name[20];int age;char sex[10];}stu;int main(){stu s1 = { "liming", 20, "nan" };FILE* pf = NULL;//打开文件pf = fopen("text01.txt", "w");if (pf == NULL){perror("fopen:");return 1;}//写入文件fprintf(pf,"%s %d %s", s1.name, s1.age, s1.sex);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
格式化输入函数fscanf( )
函数原型:int fscanf( FILE* stream, const char*format[, argument]...)
stream: 指向文件结构体的指针
format: 格式控制字符串
argument:可选参数
这里我们也可以对比scanf()函数
int scanf( const char* format[,argument]...)
可以看出fscanf函数多出一个参数stream,区别在于scanf函数是从键盘上输入,而fscanf函数是从文件里输出。
使用:
#includetypedef struct stu{char name[20];int age;char sex[10];}stu;int main(){stu s1 = { 0};FILE* pf = NULL;//打开文件pf = fopen("text01.txt", "r");if (pf == NULL){perror("fopen:");return 1;}//读取文件fscanf(pf,"%s %d %s", s1.name, &s1.age, s1.sex); //打印读取的内容printf("%s %d %s", s1.name, s1.age, s1.sex);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
二进制输出函数fwrite( )
函数原型:size_t fwrite( const *char buffer, size_t size, size_t count, FILE*stream)
buffer:指向要写入的数据的指针。
size:写入数据的大小(以字节为单位)
count: 要写入的数据的最大个数。
stream:指向文件结构体的指针
使用 :
#includetypedef struct stu{char name[20];int age;char sex[10];}stu;int main(){stu* p = NULL;stu s1 = {"liming", 20, "nan"};p = &s1;FILE* pf = NULL;//打开文件pf = fopen("text01.txt", "wb");if (pf == NULL){perror("fopen:");return 1;}//写入文件fwrite(p, sizeof(stu), 1, pf);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
二进制输入函数:fread( )
函数原型:size_t fread( const *char buffer, size_t size, size_t count, FILE*stream)
buffer:数据的储存位置。
size:读出数据的大小(以字节为单位)
count: 要读出的数据的最大个数。
stream:指向文件结构体的指针
使用:
#includetypedef struct stu{char name[20];int age;char sex[10];}stu;int main(){stu* p = NULL;stu s1 = {0};p = &s1;FILE* pf = NULL;//打开文件pf = fopen("text01.txt", "rb");if (pf == NULL){perror("fopen:");return 1;}//读文件fread(p, sizeof(stu), 1, pf);//打印读出的内容printf("%s %d %s", s1.name, s1.age, s1.sex);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
printf fprintf sprintf ; scanf fscanf sscanf 对比
scanf函数---格式化的输入函数
printf函数---格式化的输出函数
fscanf函数---针对所有输入流的格式化的输入函数
fprintf函数---针对所有输出流的格式化的输出函数
sscanf函数---把一个字符串转化成格式化的数据
sprintf函数---吧一个格式化的数据转化为字符串
scanf printf函数大家都比较熟悉 fscanf fprintf 函数上文已经讲过,所以下面我们只讲sscanf函数和sprintf函数
sprintf()函数
函数原型: int sprint( char *buffer, const char* format[, argument]...)
buffer:输出的存储位置;
format: 格式控制字符串;
argument:可选参数.
使用:
#includetypedef struct stu{char name[20];int age;char sex[10];}stu;int main(){char arr[20] = { 0 };stu s1 = { "liming", 20, "nan"};sprintf(arr, "%s %d %s", s1.name, s1.age, s1.sex);printf("%s",arr);return 0;}
结果:
sscanf()函数
函数原型: int sscanf( char *buffer, const char* format[, argument]...)
buffer:数据的存储位置;
format: 格式控制字符串;
argument:可选参数.
使用:
#includetypedef struct stu{char name[20];int age;char sex[10];}stu;int main(){stu s1 = { 0 };char arr[20] = "liming 20 nan";sscanf(arr, "%s %d %s", s1.name, &s1.age, s1.sex);printf("%s %d %s", s1.name, s1.age, s1.sex);return 0;}
结果:
文件的随机读写
fseek() 函数
根据文件指针的位置和偏移量来定位文件指针。
函数原型: int fseek( FILE*stream, long offset, int oringin )
stream:指向文件结构体的指针;
offset: 偏移量;
origin: 初试位置。
origin有三个值:1. SEEK_CUP文件指针的当前位置;
2.SEEK_END文件末尾的位置;
3.SEEK_SET文件开始位置。
使用:
#includeint main(){FILE* pf = NULL;//打开文件pf = fopen("test02.txt", "w");if (pf == NULL){perror("fopen");return 1;}//操作fputs("this is an apple", pf);fseek(pf, 5, SEEK_SET);fputs(" change ", pf);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
ftell()函数
返回文件指针相对起始位置的偏移量
函数原型: long ftell( FILE*stream )
stream:指向文件结构体的指针
使用:
#includeint main(){FILE* pf = NULL;long size = 0;//打开文件pf = fopen("test02.txt", "w");if (pf == NULL){perror("fopen");return 1;}//操作fputs("this is an apple", pf);fseek(pf, 5, SEEK_SET);size = ftell(pf);printf("%ld", size);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
rewind()函数
让文件指针的位置回到文件的起始位置
函数原型:void rewind( FILE*stream )
stream:指向文件结构体的指针.
使用 :
#includeint main(){FILE* pf = NULL;long size = 0;//打开文件pf = fopen("test02.txt", "w");if (pf == NULL){perror("fopen");return 1;}//操作fputs("this is an apple", pf);fseek(pf, 5, SEEK_SET);size = ftell(pf);printf("%ld\n", size);rewind(pf);size = ftell(pf);printf("%ld\n", size);//关闭文件fclose(pf);pf = NULL;return 0;}
结果:
文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件,数据在内存中以二进制的形式储存,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式储存,则需要在储存前转换,以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎样存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如整数10000,如果以ASCII码的形式输入到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二级制形式输出,则在磁盘上只占4个字节.
int main(){int a = 10000;FILE* pf = fopen("test.txt","wb");fwrite(&a, 4, 1, pf);fclose(pf);pf = NULL;return 0;}
注:10000的二进制形式为:00000000 00000000 00100111 00010000
文件读取结束的判定
被错用的feof
函数原型: int feof ( FILE*stream)
返回值:feof 函数在第一次读取操作后返回一个非零值,该操作尝试读取文件末尾。如果当前位置不是文件的末尾,则返回 0。没有错误返回。
在文件读取过程中,不能使用feof函数的返回值直接判断文件是否结束 。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets);
例如:
fgetc判断是否为EOF;
fgets判断是否为NULL;
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读取的个数。
例如:
fread判断返回值是否小于实际要读的个数。
文件文本的例子 :
#includeint main(){int a = 0;//注意:要处理EOF所以是in他而不是charFILE* pf = fopen("test.txt","r");if (pf == NULL){perror("fopen:");return 1;}while ((a = fgetc(pf)) != EOF){putchar(a);}if (ferror(pf)){printf("\n读取错误\n");}else if (feof(pf)){printf("\n读取到文件尾\n");}fclose(pf);pf = NULL;return 0;}
二进制文本的例子:
#inlcudeenum{SIZE= 5};int main(){double a[SIZE] = { 1.0, 2.0, 3.0, 4.0, 5.0 };FILE* pf = fopen("test1.txt", "wb");if (pf == NULL){perror("fopen:");return 1;}fwrite(a, sizeof(double), SIZE, pf);fclose(pf);double b[SIZE] = { 0 };pf = fopen("test1.txt", "rb");if (pf == NULL){perror("fopen2:");return 1;}size_t ret_cod = fread(b, sizeof(double), 5, pf);if (SIZE == ret_cod){printf("成功读取数据\n");for (int i = 0; i < SIZE; i++){printf("%lf ",b[i]);}puts("\n");}else{if (feof){printf("未读取到文件结尾\n");}else if (ferror(pf)){printf("读取失败\n");}}return 0;}
文件缓冲区
ANSIC标准采用“缓冲文件系统” 处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存想磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上,如果从磁盘想计算机读取数据,则从磁盘文件中读取数据输入到内存缓冲区,充满缓冲区后,再从缓冲区逐个地将数据送得到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定。
#include#includeint main(){FILE* pf = fopen("test2,txt","w");fputs("abcd", pf);printf("睡眠10秒-已经写好数据了,打开文件,发现文件里没有内容\n");Sleep(10000);fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)printf("在睡眠10秒-此时再打开文件,文件里有内容\n");Sleep(10000);fclose(pf);//关闭文件时也会刷新缓冲区;pf = NULL;return 0;}
结论:因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束时关闭文件。如果不做可能导致读写文件的问题。
本篇博客就到这里了,以上内容若有错误恳请指正!!!感激不尽^_^
与50位技术专家面对面 20年技术见证,附赠技术全景图