赞
踩
文件,容易被我们忽视的一块知识。在以后的项目,工程中用到的概率不大,通常使用在写日志,以及要实现数据的持久化的存储。
目录
我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。
使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj,Linux环境后缀为.o),可执行程序(windows环境后缀为.exe)。
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
在这里就要讲讲什么是标准输入输出流:
执行一个shell命令行时通常会自动打开三个标准文件,即标准输入文件(stdin),通常对应终端的键盘;标准输出文件(stdout)和标准错误输出文件(stderr),这两个文件都对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。
C语言程序,只要运行起来,就默认打开了3个流
1、stdin --- 标准输入流
2、stdout --- 标准输出流
3、stderr --- 标准错误流
文件有一个属于自己的流,叫文件流。在对文件进行输入和输出的时候,文件流并没有打开,所以我们对文件操作的时候,就需要先将文件打开,对文件操作结束后,要关闭文件。而我们平时使用的 scanf 和 printf ,是标准输入输出流下的,终端是键盘和屏幕,在程序运行时默认打开,所以就没有打开,关闭这一说法。
了解什么是输入,什么是输出,以下内容需要对此理解
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSI C 规定使用fopen函数来打开文件,fclose来关闭文件。
//打开文件 FILE * fopen ( const char * filename, const char * mode ); //关闭文件 int fclose ( FILE * stream );
int main() { //打开文件 FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { printf("打开文件失败\n"); return 0; } //写文件 //...... //关闭文件 fclose(pf); pf = NULL; return 0; }
补充两个:sscanf, sprintf
接下来详解这10个库函数:
fputc 的第二个参数是流,上述例子是将字符输出到文件中,还可以将字符直接输出到屏幕上。pf指向的文件流改为标准输出流 stdout 即可。(如下)
fputc 和 fgetc 的综合运用:
int main() { //实现一个代码将data.txt 拷贝一份 生成data2.txt FILE* pr = fopen("data.txt", "r"); if (pr == NULL) { printf("open for reading: %s\n", strerror(errno)); return 0; } FILE* pw = fopen("data2.txt", "w"); if (pw == NULL) { printf("open for writting: %s\n", strerror(errno)); //如果data,txt打开成功,data2.txt打开失败,也可以将打开的文件关闭 fclose(pr); pr = NULL; return 0; } //拷贝文件 int ch = 0; while ((ch = fgetc(pr)) != EOF) { fputc(ch, pw); } fclose(pr); pr = NULL; fclose(pw); pw = NULL; return 0; }
根据文件指针的位置和偏移量来定位文件指针。
什么是相对 origin 的偏移量?
一张图片清晰解释
返回文件指针相对于起始位置的偏移量
让文件指针的位置回到文件的起始位置
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。
int main() { int a = 10000; FILE* pf = fopen("test.txt", "wb"); fwrite(&a, 4, 1, pf);//二进制的形式写到文件中 fclose(pf); pf = NULL; return 0; }如果直接打开文档读取二进制文件,看不懂
下面操作可以读取二进制文件
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:fgetc 判断返回值是否为 EOF .
fgets 判断返回值是否为 NULL.
文本文件的例子:
int main(void) { int c; // 注意:int,非char,要求处理EOF FILE* fp = fopen("test.txt", "r"); if (!fp) { perror("File opening failed"); return EXIT_FAILURE; } //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环 { putchar(c); } //判断是什么原因结束的 if (ferror(fp)) //读写错误返回非0 puts("I/O error when reading"); else if (feof(fp)) //读写正常结束返回非0 puts("End of file reached successfully"); fclose(fp); fp = NULL; }
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。二进制文件的例子:
enum { SIZE = 5 }; int main(void) { double a[SIZE] = { 1.0,2.0,3.0,4.0,5.0 }; FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式 fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组 fclose(fp); double b[SIZE]; fp = fopen("test.bin", "rb"); size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组 if (ret_code == SIZE) { puts("Array read successfully, contents: "); for (int n = 0; n < SIZE; ++n) { printf("%f ", b[n]); } putchar('\n'); } else { // error handling if (feof(fp)) { printf("Error reading test.bin: unexpected end of file\n"); } else if (ferror(fp)) { perror("Error reading test.bin"); } } fclose(fp); }
ANSI C标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
#include <windows.h> //VS2013 WIN10环境测试 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; }以上例子就是想说明文件缓冲区是真真实实存在的,内存中的数据要存储的硬盘,亦或是硬盘上的数据要输入到内存,都有一个"中介"---文件缓冲区。操作系统要处理的事情很多,当采用文件缓冲区这种形式,可以提高办事效率。我们也可以通过刷新文件缓冲区的方式,来更快的得到我们想要的结果。
当把数据集中起来一起写入到内存中,CPU访问内存一次,效率高;数据一个一个写入内存,CPU访问内存次数较多,效率低。所以,文件缓冲区在一定程度上,会提高程序执行的效率。
文件这方面的库函数很多,全部学完不太现实,也没有必要。要有自己去学的意识,碰到不会的,不熟悉的,可以自己去 cplusplus.com - The C++ Resources Network 搜索,学习。feof 是容易被误解的,它是文件读取结束以后判断结束的原因是什么的函数。要知道二进制文件和文本文件的区别,字符在内存中是以ASCII码的方式存储的,整数既可以ASCII码方式存储,也可以二进制方式存储。以及文件缓冲区是什么,有什么作用。师傅领进门,修行看个人,要想学好,我一定要多多学习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。