1. 为什么使用文件
2. 什么是文件
3. 文件的打开和关闭
4. 文件的顺序读写
1. 为什么使用文件
我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数 据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯 录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。 我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。 这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据 库等方式。 使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
2. 什么是文件
磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境 后缀为.exe)。
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。
我们在之前所学的都是输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显 示器上。
所以其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理 的就是磁盘上文件。
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
没了文件名我们就很难找到这个文件!!!!
例如: c:\code\test.txt
上述是我们的绝对路径
在此我们引入绝对路径和相对路径以便后面内容学习
绝对路径和相对路径
绝对路径
直接在文件夹上面复制的地址是
E:\学习\编程\CSDN图片\c与excel
但是我们知道了绝对地址,但是我们要记住在c语言中要表述单斜杠需要用\\来表示单斜杠,
因为c语言标准规定\+什么,就是个转义字符,结果就不是我们想要的结果了。
正确方式:
E:\\学习\\编程\\CSDN图片\\c与excel
相对路径
一个斜杠 /
两个斜杠 \\ 均可
当前目录
.\\xxx ./
前一段目录
..\\ ../ 均表示前一级目录
..\\..\\ ../.. 均 表示前两级目录
3.文件的打开和关闭
文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。 每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名 字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统 声明的,取名FILE.
我们看看文件类型是怎么声明的,我们的文件类型是包含在stdio.h下的
#include <stdio.h> int main() { struct _iobuf { char* _ptr; int _cnt; char* _base; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; }; typedef struct _iobuf FILE; }
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。 每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息, 使用者不必关心细节。
打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变 量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联 的文件。
文件的打开和关闭函数
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指 针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件
//打开文件 FILE * fopen ( const char * filename, const char * mode ); //关闭文件 int fclose ( FILE * stream );
下面我们来使用下fopen
#include <stdio.h> int main() { FILE* pFile; //打开文件 pFile = fopen("D:\\code\\text.txt", "w");//因为fopen函数返回文件信息区域的地址 //文件操作 if (pFile != NULL)//我们在打开失败的时候会返回空,所以我们在这边判断不为空才能执行 { //因为我们返回空的时候得不到地址,在执行关闭文件时会出问题, //程序会崩溃,因为我们关闭的是空指针的地址区域 fputs("fopen example", pFile); //关闭文件 fclose(pFile); pFile=NULL; } return 0; } //我们也可以 //FILE* pflie=fopen("D:\\code\\text.txt", "w");
所以上述我们看到为什么要去判断是否为空的理由
所以我们看看没执行上述代码时
执行前
执行过后
4. 文件的顺序读写
我们来看看这些顺序读写的函数怎么使用。
在学使用之前我们得知道他的输入输出流
标准输入文件 stdin(表示键盘)//stdin和stdout这两个流默认是打开的,所以我们在scanf和printf时不需要去打开。
标准输出文件 stdout(表示显示器)
标准错误文件 stderr(表示显示器)是由系统打开的,可直接使用。
fegtc函数
fgetc()函数的功能是从文件指针指定的文件中读入一个字符,该字符的ASCII值作为函数的返回值,若返回值为EOF,说明文件结束,EOF是文件结束标志,值为-1。
在文件处理中,通过fgetc()函数,我们从输入流中获取下一个字符,并将文件指针加1
实现一下,看下串代码
1.
stdin流
#include <stdio.h> int main() { /*FILE* file = fopen("D:\\code\\text.txt", "w");*/ int ch = 0; while ((ch=fgetc(stdin))!=EOF)//这个意思是每次读取键盘上的字符, //读到没有为止,读到文件尾或者文件读写错误 // 会返回EOF //EOF=-1 { printf("%c", ch); } return 0; }
2.
文件流
#include <stdio.h> int main() { FILE* file = fopen("D:\\code\\text.txt", "w");//返回文件信息区域,所以file保存的是文件信息区域地址 if (NULL == file) { perror(fopen); return 1; } int i = 0; for(i=0;i<26;i++) { fputc('a' + i, file);//写入到文件信息区域(内存)然后在写入到D盘下的code里的text.txt文件 } int ch = 0; while ((ch=fgetc(file))!=EOF)//读取成功继续执行——继续读取(因为fgetc每次只能读取一个字符),因为每次执行时,文件指针+1 { //所以他会读取下一个字符,如果 //读取失败就退出while循环; printf("%c", ch); } fclose(file); file = NULL; return 0; }
仔细想想为什么没有输出
正确代码:
fputc函数
fputc()函数的功能是从文件指针指定的文件中写入一个字符,该字符的ASCII值作为函数的返回值,若返回值为EOF,说明文件结束,EOF是文件结束标志,值为-1。
C 库函数 int fputc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流stream中,并把位置标识符往前移动。
1.
stdout流
#include <stdio.h> int main() { FILE* pf = fopen("D:\\code\\text.txt","w"); int c = 65; while (fputc(c, stdout) != EOF) { c++; if (c > 90) { break; } } return 0; }
2.
文件流
那我们用fputc把数据写入到文本中
#include <stdio.h> int main() { FILE* pf = fopen("D:\\code\\text.txt","w"); int i = 0; for(i=0;i<26;i++) { fputc('a' + i, pf);//写入到文件信息区域(内存)然后在写入到D盘下的code里的text.txt文件 } return 0; }
执行后成功写入
fgets函数
char * fgets ( char * str, int num, FILE * stream );
str: char 字符型指针,指向存储读入数据的缓冲区的地址
num: 从流中读入n-1个字符
stream : 指向读取的流。
1. 当n<=0 时返回NULL,即空指针。
2. 当n=1 时,返回空串"".
3. 如果读入成功,则返回缓冲区的地址。
4. 如果读入错误或遇到文件结尾(EOF),则返回NULL.
那为什么会造成这种1和2这种现象呢?
原来是因为
fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets()将读入n-1个字符,因为会在最后放一个\0,或者读到遇到的第一个换行符为止,所以我们就会少读入一个字符.
我们来试验一下
已知我们的txt文件存了czcxzc
#include <stdio.h> int main() { FILE* pf = fopen("D:\\code\\text.txt","r"); if (NULL == pf) { perror(fopen); return 1; } char arr[] = "*******"; fgets(arr,7, pf); printf("%s", arr); return 0; }
在我们执行这个代码后
所以我们知道arr里面存了6个字符加一个\0
不过这里也要提醒一下小伙伴们,fgets函数会读取\n,不会丢弃!!!!
1.
stdin流
#include <stdio.h> int main() { char arr[10] = { 0 }; fgets(arr, 10, stdin); puts(arr); return 0; }
2.
文件流
#include <stdio.h> int main() { FILE* pf = fopen("D:\\code\\text.txt","r"); if (NULL == pf) { perror(fopen); return 1; } char arr[] = "*******"; fgets(arr,7, pf); printf("%s", arr); return 0; }
fputs函数
int fputs(const char* str,FILE* stream)
C 库函数 int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符
1.
stdout流
2.
文件流
#include <stdio.h> int main() { FILE* file = fopen("D:\\code\\text.txt", "w+"); FILE* cunfile = file; if (NULL == file) { perror(fopen); return 0; } char arr[] = "abcde0"; fputs(arr,file); char arr1[10] = { 0 }; fgets(arr1, 7, cunfile); printf("%s", arr1); fclose(file); file = NULL; return 0; }
fscanf()函数
int fscanf(FILE*stream, constchar*format, [argument...]);
其功能为根据数据格式(format)从输入流(stream)中写入数据(argument);
1. format str:如%d, %f, %c, %s等,分别表示读入一个整数,浮点数,字符,字符串。还可以加上控制,如%ld,表示读入一个长整型数,%20s表示最多读入20个字符。
2.返回值:在没有出错的情况下,fscanf 返回正确匹配和赋值的域的个数;如果出错,则返回EOF。
其实fscanf()函数与scanf()函数类似
1.
stdin流
2.
文件流
已知文本存了abcde0
fprintf()函数
fprintf()的返回值是输出的字符数,发生错误时返回一个负值.
可以看到,它与printf()函数相比多出来了第一个参数FILE *stream,其意义是将打印的内容输出到文件流指针stream指向的流.
1.
stdout流
#include <stdio.h> int main() { char arr[10] = { 0 }; FILE* file = fopen("D:\\code\\text.txt", "r+"); fscanf(file, "%s", arr); fprintf(stdout,"%s",arr); return 0; }
2.
文件流
#include <stdio.h> int main() { char arr[10] = "***"; FILE* file = fopen("D:\\code\\text.txt", "r+"); fprintf(file,"%s",arr); return 0; }