一、文件的简单介绍
1.1 什么是文件
从广义上来说,磁盘上的文件是文件。在C语言阶段我们在执行一段程序过后,它的内容随着进程的结束而消失,而我们要想把数据持久化,数据存放在磁盘文件、存放到数据库等方式。今天简单介绍数据保存到文件这种方式。在程序设计中,我们一般谈的文件有两种:程序文件和数据文件(从文件功能的角度来分析).
1.2 程序文件
包括源程序文件(后缀为.c),目标文件(Windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
1.3 数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
1.4 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如:c:\code\test.txt为了方便起见,文件标识常被称为文件名。
二、文件的打开和关闭
2.1 文件指针
1、缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
2、每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
typedef struct _iobuf { char* _ptr; int _cnt; char* _base; int _flag; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; }FILE;
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量填充其中的信息,一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
比如我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针变量
该类型的意义是:
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说通过文件指针变量能够找到与它关联的文件。
2.2 文件的打开与关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
//打开文件 FILE* fopen(const char* filename,const char* mode); //关闭文件 int fclose(FILE* stream);
我们这里可以简单介绍一下 fopen 和 fclose函数。
①fopen
FILE* fopen(const char* filename,const char* mode);
打开一个文件以什么形式打开,mode就是打开的方式。如果打开成功就返回文件指针的起始地址,如果失败就返回NULL,而这也暗示我们要进行检查。
②fclose
int fclose(FILE* stream);
这个函数比较简单,就是关闭之前打开的文件。如果打开成功,就返回0,如果失败,就返回EOF.
上面对于mode(打开方式)介绍的比较含糊,那么具体的打开方式有如下这些方式:
常用的就是标红的那些,需要注意的是:
1.以"r"(只读的方式)来打开文件,需要打开一个原本已经存在的文本文件,否则会打开失败。
2.如果以"w"(只写的方式)来打开文件,无论文本文件是否存在,无论文本里面有没有内容,都会开辟一个文件,而且需要注意如果已经存在的文件,那么它会把文件里原本的内容清除,然后存入数据。如果不想清除原本文件内的数据,可以使用第三种方式
3."a"(追加的方式),向文本文件尾添加数据。
4.至于这些字母后面加个b,"wb","rb","ab"意思是以二进制的形式。上述是以文本的形式。
使用演示:
#include<stdio.h> #include<errno.h> #include<string.h> int main() { FILE* pf = fopen("test.txt", "r");//pf的类型是FILE* //文件打开可能失败,所以要判断 if (pf == NULL) { printf("%s\n", strerror(errno)); return 1; } //"r" "w" "a" "r+" "w+" "a+" "rb" "wb" //写文件 //关闭文件 fclose(pf); pf = NULL; return 0; }
这里是和之前我讲述的动态开辟比较相似,那里需要对malloc或者calloc开辟的动态内存进行判断是否为NULL,然后在结束的时候需要free掉动态内存,和这里的文件操作是比较相似的。可以类比着记忆。
2.3 文件的流程展示
通过我简单的介绍,大家对于文件有了基本了解,而具体是如何操作的,那是下面要介绍的内容,而这里笔者简单画了个图帮助大家理解:
三、文件的顺序读写
3.1 fputc(写字符)
int fputc ( int character, FILE * stream );
1、Charcater: 要编写的字符的整型升级。写入时,该值在内部转换为无符号字符。
2、Stream:指向标识输出流的 FILE 对象的指针。该函数的意义就是将字符character写入文件中去。
3、如果成功后,将返回写入的字符。如果发生写入错误,则返回 EOF 。
应用展示:
3.2 fgetc(读字符)
int fgetc ( FILE * stream );
1、在默认状态下,fgetc从stream指向的文件首个字符开始读取,当读取过后,自动指向下一个字符,如果指向末尾或者读取失败则返回EOF。
2、如果成功,则返回当前指向的字符,如果失败,则返回EOF。
应用展示:
①一个一个读取
注意:这里之所以能读出a b c d是因为在上个函数fpuc时笔者写入了一个txt文件里面是a到c的字符,所有各位朋友在使用这个函数的时候记得不是空的文件,否则会出错。
②全部读取
如果仅能一个一个读取,那么效率有点低了,如果我们想全部读取文件里的内容,那么我们根据fgetc函数的返回值可以设计,当指向文件尾时返回EOF。
展示:
3.3 fputs(写字符串到文件)
int fputs ( const char * str, FILE * stream );
1、意义:将 str 所指向的字符串写入流(stream)。
2、该函数开始从指定的地址 (str) 复制,直到到达终止空字符 ('0')。此终止空字符不会复制到流中。
3、请注意,之前fputc和putc以及fgetc和getc是非常相似的,而这里fputs 不仅与 puts 不同,因为可以指定目标流,而且 fputs 不会写入其他字符,而 puts 会自动在末尾追加换行符。
4、如果成功,则返回一个非0的数,如果失败,则返回EOF。
应用展示:
这里我之前写入的"abcdef..."已经消失了,是因为笔者以"w"(只写)的方式打开了文本,会自动清除里面原本的内容,要想保留下来原本的内容,可以以"a"(追加)的方式打开。
3.4 fgets(读字符串)
char * fgets ( char * str, int num, FILE * stream );
1、意义:从流(stream)中读取(num-1)字符并将其作为字符串存储到 str 中。
2、当读取 (num-1) 字符或达到换行符或文件末尾(以先发生者为准)时停止,需要注意换行符(\n)使fgets停止读取,但它被视为有效字符,并包含在复制到str的字符串中。
3、终止空字符('\0')会自动追加到复制到 str 的字符之后。
4、请注意,fgets 与 gets 相同之处在于:fgets和gets都会自动追加终止空字符,不同:fgets 不仅接受流参数(stream),还允许指定读取字符个数,并且fgets遇到换行符会把换行符当作字符拷贝到str中。
5、如果成功,返回str,如果失败,则返回NULL指针.
应用展示:
这里要求打印5个字符只打印了4个字符的原因是,它自动追加'\0'算作一个字符。
接下来这两个函数是针对所有输出输入流的: