1.什么是文件
如果没有了文件,我们在电脑上所写的程序会存储在电脑的内存中,程序退出,内存回收,我们所写的东西就丢失了,要将数据进行持久保存,就使用了文件
1.1什么是文件
存放在磁盘(硬盘)上的·文件就是文件
但在程序设计中,我们谈论的文件一般有两种:程序文件和数据文件(根据文件功能分类)
1.1.1程序文件
包括源程序文件(.c后缀),目标文件(.obj后缀),可实行程序(.exe后缀)
1.1.2数据文件
文件包含的内容不肯定是程序,还大概是数据运行时读写的数据,在本文中讨论的是数据文件
在之前各讲中处置惩罚数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,然后将效果返回到表现器上
但是我们有时间会把信息输出到磁盘上,当必要的时间再从磁盘中把数据读取到内存中使用,这里处置惩罚的就是磁盘上的文件
1.2二进制文件和文本文件
根据数据的组织形式,数据文件被分为二进制文件和文本文件
数据在内存中以二进制的形式存储,如果不加转化地输出到外存中,就是二进制文件
如果要求在外存中以ASCII码形式进行存储,就必要在存储前进行转化,以ASCII码形式存储的文件就是文本文件
数据的存储方式
那么一个数据在文件中是怎么存储的呢?
字符:同等按照它的ASCII码进行存储
数值:即可以使用ASCII码进行存储,也可以使用二进制进行存储
比如整数10000,如果以ASCII码形式进行存储,必要占用5个字节,而以二进制形式进行输出,在磁盘上只占用4个字节
2.流和标准流
2.1流
我们程序的数据必要输出到各种外部设备,也必要不绝从外部设备获取数据,不同外部设备之间输入和输出方式各不雷同,为了方便程序员对各种设备进行方便的操作,我们引用了流的概念,也就是说,流的概念允许开发者以统一的方式处置惩罚不同范例的输入输出操作,而不必要关心数据的泉源或目标地的详细细节
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的
⼀般环境下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作
2.2标准流
C语言系统在启动的时间,会自动打开三个流:
1.stdin:标准输入流,大多数环境下从键盘输入,scanf函数就是从标准输入流中读取数据
2.stdout:标准输出流,大多数环境输出到表现器界面,printf函数就是将信息输出到标准输出流中
3.stderr:标准错误流,大多数环境输出到表现器界面,与标准输出流不同,标准错误流常常发送必要立即注意的信息
这三个流的范例是:FILE*,通常称为文件指针
C语言中,就是通过FILE*来维护流的各种操作的
3.文件指针
想要对文件进行操作,文件指针就发挥着重要作用
每个被使用的文件都在内存中开辟了一个文件信息区,用来存放文件的相关信息(文件名称、文件状态、文件所在位置等),布局体有着这种存储能力,该布局体由系统命名,为FILE
如:VS2013中就有雷同的声明:
- struct _iobuf {
- char *_ptr;
- int _cnt;
- char *_base;
- int _flag;
- int _file;
- int _charbuf;
- int _bufsiz;
- char *_tmpfname;
- };
- typedef struct _iobuf FILE;
复制代码 既然有了这个布局体,那么就可以使用一个指针来指向它,甚至访问、维护它,那么我们就来创建一个布局体指针变量:
4.文件的打开与关闭
文件在使用之前必要先打开,使用之后必要记得关闭
ANSI C规定,使用fopen来打开文件,使用fclose来关闭文件
而在打开文件的同时,都会返回一个FILE*范例的指针变量指向该文件,也相当于建立了指针和变量的关系
- //打开⽂件
- FILE* fopen(const char* filename, const char* mode);
- //关闭⽂件
- int fclose(FILE* stream);
复制代码 mode表现文件的打开模式,打开模式如下:
我们来看一个示例:
- //示例
- int main()
- {
- FILE* pa = fopen("myfile.txt", "w");//打开文件
- if (pa != NULL)
- {
- fputs("fopen example", pa);
- //使用完要关闭文件
- fclose(pa);
- }
- return 0;
- }
复制代码 5.文件次序读写
5.1次序读写函数先容
上面说的适用于所有输⼊流⼀般指适⽤于标准输⼊流和其他输入流(如文件输入流);
所有输出流⼀般指适⽤于标准输出流和其他输出流(如文件输出流)
5.2打开和关闭函数
它们都被包含在头文件<stdio.h>中
5.2.1fopen函数
函数原型:
- FILE *fopen( const char *filename, const char *mode );
复制代码 函数参数:
filename:要打开的文件的名称
mode:打开模式,在上面有
函数返回值:
乐成时返回一个指向FILE对象的指针(returns a pointer to the open file)
失败时返回NULL,并设置errno全局变量
函数使用:
- //函数使用
- int main()
- {
- FILE* pa = fopen("myfile.txt", "w");
- //对于文件,要提供正确的文件名称,保证文件是能够被打开的
- //使用之前要注意检查返回值是否为空
- if (pa == NULL)
- {
- perror("fopen");//如果为空,可以使用perror函数来进行报错处理
- return 1;
- }
- else
- {
- fputs("fopen example", pa);
- }
- //最后要记得关闭文件
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 5.2.2fclose函数
函数原型:
- int fclose( FILE *stream );
复制代码 函数参数:
stream:指向FILE范例的指针,这个FILE对象通常由fopen函数返回
函数返回值:
如果流乐成关闭,返回0
如果堕落,返回EOF表现
函数使用:
- //函数使用
- int main()
- {
- FILE* pa = fopen("myfile.txt", "w");
- //对于文件,要提供正确的文件名称,保证文件是能够被打开的
- //使用之前要注意检查返回值是否为空
- if (pa == NULL)
- {
- perror("fopen");//如果为空,可以使用perror函数来进行报错处理
- return 1;
- }
- else
- {
- fputs("fopen example", pa);
- }
- //最后要记得关闭文件
- if (fclose(pa))
- {
- perror("Error Closing File");//最好有错误检测,看是否成功关闭了文件
- return -1;
- }
- //fclose(pa); //多次关闭文件是未定义的
- pa = NULL;
-
- return 0;
- }
复制代码 5.3fgetc函数和fputc函数
它们都包含在头文件<stdio.h>中
5.3.1fgetc函数
函数原型:
- int fgetc( FILE *stream );
复制代码 函数参数:
stream:指向FILE布局的指针,表现要读取的文件流
函数返回值:
乐成时,返回读取的字符,然后,将函数相关的文件指针加1,也就是让它指向下一个字符,如果流位于文件末端,则设置流的文件结束指示符,也就是返回EOF
失败时,返回EOF
函数使用:
- //fgetc函数
- int main()
- {
- //肯定要先打开一个文件
- FILE* pa = fopen("myfile.txt", "r");
-
- //先检查pa是否为空
- if(pa != NULL)
- {
- int a;
- while ((a = fgetc(pa)) != EOF)//这时可以进行检查是否为EOF看是否到文件末尾或出错
- {
- printf("%c ", a);
- }
- //记得关闭文件
- fclose(pa);
- pa = NULL;
- }
- return 0;
- }
复制代码 5.3.2fputc函数
函数原型:
- int fputc( int c, FILE *stream );
复制代码 函数参数:
c:表现要写入的字符,作为int范例传递
stream:表现要写入的文件流
函数返回值:
乐成时,返回写入的字符,并将文件指针的位置向后移动一个字符,以指向下一个字符
失败时,返回EOF
函数使用:
- //fputs函数使用
- int main()
- {
- FILE* pa = fopen("myfile.txt", "w");
- if (pa == NULL)
- return 1;
- else
- {
- int a = fputc('w', pa);
- if (a != EOF)
- putchar(a);
- a = fputc('x', pa);
- if (a != EOF)
- putchar(a);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 5.4fputs和fgets函数
5.4.1fputs函数
函数原型:
- int fputs( const char *string, FILE *stream );
复制代码 函数参数:
string:要写入的字符串
stream:表现要写入的文件流
函数返回值:
乐成时,返回非负值(可以为0,且一般为0)
失败时,返回EOF
函数使用:
- int main()
- {
- FILE* pa = fopen("example.txt", "w");
- if (pa == NULL)
- {
- perror("fopen");
- return 1;
- }
- else
- {
- fputs("abcdef", pa);
- fputs("ghi", pa);
- //文件中存储的是abcdefghi,它们是连在一起的,如果想要它们在两行上,可以进行更改:
- fputs("abcdef\n", pa); //要自己添加\n换行符
- fputs("ghi\n", pa);
- }
- fclose(pa);
- 也可以对此进行判断
- //if (fclose(pa) == EOF)
- //{
- // perror("fclose");
- // return -1;
- //}
- pa = NULL;
- return 0;
- }
复制代码 5.4.2fgets函数
函数原型:
- char *fgets( char *string, int n, FILE *stream );
复制代码 1.函数参数:
string:指向存储读取出的字符串的指针,用于存储从文件中读取出的字符串
n:指定最多可以从文件中读取出的字符数,包括空字符\0
stream:表现目标文件
返回值:
乐成时,返回str的头指针,如果在读取过程中碰到了文件末端,那么会设置eof指示符
失败时,也就是当未读取字符串时碰到文件结尾,或发生读取错误时,返回NULL
函数使用:
使用1
- //函数的一般使用
- int main()
- {
- //文件中存储的是hello world
- FILE* pa = fopen("example", "r");
- char arr[20];
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- fgets(arr, 10, pa);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 当要文件中的总字符数大于设置读取的字符数时,读取的字符数为n-1,因为要存储一个\0
使用2,当文件中这样存储时:
- int main()
- {
- FILE* pa = fopen("example", "r");
- char arr[20];
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- fgets(arr, 10, pa);
- printf("%s", arr);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 当我们要读取10个字符时,函数只会将一整行的数据读取,这也是为什么它叫文本行输出函数了,然后它会读取\n,所以,当我们进行打印时,打印出的效果也会加上\n
所以对于多行,我们必要通过循环多次使用函数来读取:
- int main()
- {
- FILE* pa = fopen("example", "r");
- char arr[20];
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- while (fgets(arr, 10, pa) != NULL)
- printf("%s", arr);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 5.5fprintf和fscanf函数
5.5.1fprint函数
函数原型:
- int fprintf( FILE *stream, const char *format , ...);
复制代码 函数参数:
stream:要写入的目标文件流
formate:格式字符串,%d,%s,%c等等都是格式
…:可变参数列表,包含的是要进行输出的数据,包含变量指针
函数返回值:
乐成时,返回写入的字符的个数
失败时,返回一个负数
函数的使用:
- int main()
- {
- FILE* pa = fopen("example", "w");
- char arr[20];
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- fprintf(pa, "Example:%s", "hello world");//文件中存储的是:Example:hello world
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 当然,我们可以通过布局体更显着地看特殊式化输出的作用:
- struct Stu
- {
- char name[20];
- int age;
- float score;
- };
- int main()
- {
- struct Stu S;
- FILE* pa = fopen("example.txt", "w");
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- fprintf(pa, "%s %d %f", "zhangsan", 20, 66.6);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 5.5.2fscanf函数
函数原型:
- int fscanf( FILE *stream, const char *format , ... );
复制代码 函数参数:
stream:要读取的文件流
formate:格式字符串
…:可变参数列表,包含变量的指针
函数返回值:
乐成时,返回读取的数据个数
失败是,也就是没有读取任何数据之前,碰到了文件末端,或者读取发生错误(非法格式或读取错误),返回EOF
函数使用:
- int main()
- {
- struct Stu S = { 0 };
- FILE* pa = fopen("example.txt", "r");
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- int ret = fscanf(pa, "%s %d %f", S.name, &S.age, &S.score);//此处要记得&
- printf("%s %d %f\n", S.name, S.age, S.score);
- printf("%d", ret);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 效果如下:
5.6fwrite和fread函数
5.6.1fwrite函数
函数原型:
函数参数:
buffer:要写入数据的指针
size:接受输出的每个数据大小
count:要输出的数据的数量
stream:要写入的文件流
函数返回值:
乐成时,返回写入数据项的个数
如果返回值小于count,表现操作大概发生了错误或未完成
函数使用:
- int main()
- {
- FILE* pa = fopen("example.txt", "wb");//注意这里要使用wb,因为写入的是二进制数据
- char arr[] = "hello world";
- if (pa == NULL)
- {
- perror("FILE OPEN FILED");
- return 1;
- }
- else
- {
- fwrite(arr, sizeof(char), strlen(arr), pa);
- }
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 使用布局体:
- struct S
- {
- int age;
- char name[20];
- char sex[5];
- };
- int main()
- {
- struct S L = { 20,"李四","女" };
- FILE* pf = fopen("example.txt", "wb");//二进制输出时用wb
- if (pf == NULL)
- {
- perror("fopen");
- return 1;
- }
- fwrite(&L, sizeof(struct S), 1, pf);
- if (fclose(pf) == EOF)
- {
- //关闭失败
- perror("fclose");
- return 1;
- }
- pf = NULL;
- return 0;
- }
复制代码 5.6.2fread函数
函数原型;
函数参数:
1.buffer:要存储数据的指针
2.size:每个元素的大小,单位是字节
3.count:要读取的元素数量
4.stream:文件指针,指向要读取的文件
函数返回值:
函数返回读取乐成的元素的数量,如果返回值小于count,大概是由于已经达到了文件末端或发生错误
函数使用:
- struct S
- {
- int age;
- char name[20];
- char sex[5];
- };
- int main()
- {
- struct S L = { 20,"李四","女" };
- FILE* pa = fopen("text.txt", "rb");//二进制输入时用rb
- if (pa == NULL)
- {
- perror("fopen");
- return 1;
- }
- fread(&L, sizeof(struct S), 1, pa);
- //使用fread函数能够读取二进制值
- printf("%d %s %s", L.age, L.name, L.sex);
- if (fclose(pa) == EOF)
- {
- perror("fclose");
- return 1;
- }
- pa = NULL;
- return 0;
- }
复制代码 拓展延申:5.7sprintf和sscanf函数
我们直接将两个函数对比着看:
函数原型:
1.sprintf函数用于将格式化的数据写入到字符串中
2.sscanf函数用于从字符串中按照指定的格式解析数据
函数返回值:
1.sprintf函数返回乐成写入到字符串中的字符数,不包括字符换末端的\0,但是,对于字符串的写入,要碰到\0才制止写入,所以\0还是很有必要的
2.sscanf函数返回乐成解析的项数,如果返回值小于请求的项数,大概是由于格式不匹配或达到字符串末端
函数使用:
- struct S
- {
- int age;
- char name[20];
- float point;
- };
- int main()
- {
- struct S s = { 15, "lisi", 88.8f };
- char arr[20];
- //将各种数据转换成字符串
- int ret = sprintf(arr, "%d %s %f", s.age, s.name, s.point);
- //int sprintf(char* buffer, const char* format[, argument] ...);
- //函数参数:
- //buffer:用于存储格式化后的字符串
- //formate:参数的格式
- //...:可变参数列表,包含了要被格式化的数据
- printf("%s\n", arr);
- printf("%d\n", ret);
- //将字符串中的数据按照格式化传入一个新的结构体变量中
- struct S m;
- sscanf(arr, "%d %s %f", &m.age, m.name, &m.point);//注意:要加上&符号
- //int sscanf( const char *buffer, const char *format [, argument ] ... );
- //函数参数:
- //buffer:指向包含输入数据的字符串指针
- //formate:格式化字符串,定义如何分析字符串
- //...:可变参数列表
- printf("%d %s %f", m.age, m.name, m.point);
- return 0;
- }
复制代码 运行效果:
6.文件的随机读写
6.1fseek函数
函数原型:
- int fseek( FILE *stream, long offset, int origin );
复制代码 该函数是用来在文件中移动文件指针的位置
函数参数:
1.stream:文件指针
2.offset:要移动的字节数,可正可负,分别表现向前\后进行移动
3.origin:表现offset参数的起始点,有三种范例:
SEEK_SET:表现从文件开头开始计算
SEEK_CUR:表现从当前位置开始计算
SEEK_END:表现从文件末端开始计算
函数使用:
假设我们的目标文件为:example.txt,文件中存储的数据为:
那么我们看下面的代码:
- int main()
- {
- FILE* pa = fopen("example.txt", "r");
- if (pa == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ret1 = fgetc(pa);
- printf("ret1 = %c\n", ret1);//结果为a
- fseek(pa, 2, SEEK_SET);
- int ret2 = fgetc(pa);
- printf("ret2 = %c\n", ret2);//结果为c
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 6.2ftell函数
函数原型:
- long ftell( FILE *stream );
复制代码 该函数是返回文件指针相对于起始位置的偏移量的函数
函数参数:
该函数只有一个参数,就是文件指针
函数返回值:
乐成时,返回一个非负的长整形值,表现文件指针距起始位置的偏移量
失败时,返回-1L
函数使用:
- int main()
- {
- FILE* pa = fopen("example.txt", "r");
- if (pa == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ret1 = fgetc(pa);
- printf("ret1 = %c\n", ret1);
- fseek(pa, 4, SEEK_SET);
- int re = ftell(pa);
- printf("re = %d\n", re);//结果为4
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 6.3rewind函数
函数原型:
- void rewind( FILE *stream );
复制代码 该函数是让文件指针的位置返回到起始位置的函数
函数参数:
很简单,只有一个文件指针
函数返回值:
函数没有返回值
函数使用:
- int main()
- {
- FILE* pa = fopen("example.txt", "r");
- if (pa == NULL)
- {
- perror("fopen");
- return 1;
- }
- int ret1 = fgetc(pa);
- printf("ret1 = %c\n", ret1);//结果是a
- fseek(pa, 4, SEEK_SET);//改变文件指针的位置
- rewind(pa);//再将文件指针的位置指向起始位置
- int ret2 = fgetc(pa);
- printf("ret2 = %c\n", ret2);//结果还应该是a
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 7.文件读取结束的判断
在C语言中,我们可以使用feof函数来判断文件读取结束的缘故原由是否是碰到文件结尾
函数原型:
- int feof( FILE *stream );
复制代码 函数参数:
文件指针
函数返回值:
如果stream指向的文件流已经到达文件结尾,返回非0值(通常为1)
如果没有到达文件末端,返回0
函数使用:
- int main()
- {
- FILE* pa = fopen("example.txt", "r");
- if (pa == NULL)
- {
- perror("fopen");
- return 1;
- }
- int c; // 注意:int,⾮char,要求处理EOF
- //fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF
- while ((c = fgetc(pa)) != EOF) // 标准C I/O读取⽂件循环
- {
- putchar(c);
- }
- //判断是什么原因结束的
- if (ferror(pa))//ferror函数参数和feof函数相同,如果检测到错误,函数返回非0值,否则返回0
- puts("I/O error when reading");
- else if (feof(pa))
- puts("End of file reached successfully");
- fclose(pa);
- pa = NULL;
- return 0;
- }
复制代码 对于ferror函数,检测到错误后会将错误标志设置为非0,如果必要再次使用ferror函数检测后续操作的错误,必要先调用clearerr函数来清除错误标志
8.文件缓冲区
ANSIC标准接纳”操作文件系统“来处置惩罚数据文件,其功能其实就是系统会自动地为程序中每一个正在使用的文件开辟一块”文件缓冲区“,从内存向磁盘输入的数据都会先送到内存中的缓冲区,缓冲区填满后才将缓冲区中的数据写入磁盘
如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(布满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。原理如下:
我们可以写一个程序观察缓冲区的存在:
- // VS2022 WIN11环境测试
- int main()
- {
- FILE* pf = fopen("test.txt", "w");
- fputs("abcdef", pf);//先将代码放在输出缓冲区
- printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
- Sleep(10000);
- printf("刷新缓冲区\n");
- fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
- //注:fflush 在⾼版本的VS上不能使⽤了
- printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
- Sleep(10000);
- fclose(pf);
- //注:fclose在关闭⽂件的时候,也会刷新缓冲区
- pf = NULL;
- return 0;
- }
复制代码
所以我们可以得出一个结论:
因为有缓冲区的存在,C语言在操作文件的时间,必要做刷新缓冲区或者在文件操作结束的时间关闭文件。
如果不做,大概导致读写文件的问题。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |