前言
今天分享的内容是和文件有关的操作。使用文件可以让我们的数据直接存存放到电脑的硬盘上,做到数据的 持久化,因此掌握对文件的操作,是一名程序员的必备技能,接下来就让我们进入今天的正题,看看到底如何对文件进行操作吧。
1. 什么是文件
磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
2.1 程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境
后缀为.exe)。
2.2 数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
2.3 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
2. 文件的打开和关闭
2.1 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
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*的指针变量:
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 example */ #include <stdio.h> int main () { FILE * pFile; //打开文件 pFile = fopen ("myfile.txt","w"); //文件操作 if (pFile!=NULL) { fputs ("fopen example",pFile); //关闭文件 fclose (pFile); } return 0; }
3. 文件的顺序读写
所谓顺序读写就是从文件中的第一个字符开始往后依次读取或写入。文件的顺序读写主要涉及以下几个函数:
输出、输入和读取、写入
在深入了解文件顺序读写函数之前,先要明确输出、输入和读取、写入这两组概念。输出和输入都是针对内存来说的,以内存作为输出和输入的对象,输出准确来说是从内存中输出,输入准确来说是往内存中输入。而读取和写入时针对文件来说的,读取准确来说是从文件中进行读取,写入准确来说是往文件中进行写入。程序是在内存中运行的,因此要把程序运行时产生的数据存储到文件中,其实就是内存输出数据写入到文件中。而程序运行时从文件中获取数据,其实就是从文件中读取数据输入到内存当中。注意,键盘和显示器本质上也是文件。因此我们每次通过scanf从键盘上键入数据,其本质上是从键盘文件中读取数据输入到内存当中供程序运行时使用。而使用printf将程序运行的结果打印到显示器上,其本质是将程序运行时产生的数据从内存中输出写入到显示器文件当中。
总结一下:输入是和读取绑定的,输出是和写入绑定的
输出流、输入流的
从上面的表格中我们可以看到,有些函数可以适用于所有的输出流或者输入流,而有的文件只适用于文件的输出流或者输入流,这里的流到底是什么意思呢?
流可以帮助我们简化输入输出操作,已输出为例,我们可能会将程序运行中产生的数据输出到不同的输出设备中(比如:屏幕、文件、网络等),针对不同的输出设备其写入方式也有所不同,作为程序员我们就需要掌握对不同输出设备的写入方式,这样才能把数据正确的写入到输出设备上,这无疑给程序员增添了许多麻烦。为了简化操作,于是就在内存和输出设备之间抽象出来了一个流的概念,此时程序员只需要知道如何把数据输出到流中去就可以,而从流中把数据写入到不同的输出设备这一过程C语言已经帮我们封装好了。流的类型是FILE*
任何一个C语言程序运行的时候会默认打开以下三个流:
stdin ---- 标准输入流(键盘)
stdout ----标准输出流(屏幕)
stderr ---- 标准错误流(屏幕)
这三个流的类型也都是FILE*,由于这三个流是在C语言程序运行的时候默认打开的,所以在一个C程序中我们可以直接使用scanf和printf。而对于一个文件,C语言程序在运行的时候不会默认打开,因此如果要对文件进行操作我们需要自己写代码来打开文件,例如:FILE* pf = fopen("test.txt", "r"); ,其本质上是获取一个文件流
所以上表中提到的适用于所有的输入流或输出流,就意味着此函数不仅可以从文件流里面获取或写入数据,以getc和putc为例:
int main() { char ret = fgetc(stdin);//从标准输入流中读取字符,输入到内存当中 fputc(ret, stdout);//从内存中输出一个字符,写入到标准的输出流里面 return 0; }
fgetc
int fgetc ( FILE * stream );
- 参数是一个文件类型的指针
- FILE结构体中有一个定位指针,指向文件中待读取的字符,最初一定是指向文件中的第一个字符
- 读取成功会返回定位指针当前所指向的字符,并且定位指针前进一位(这就是顺序读的实现原理),读取结束会返回EOF
- 因为是从文件中读取字符,因此文件一定是以读的方式打开
fputc
int fputc ( int character, FILE * stream );
- 第一个参数character是待写入的字符
- 第二个参数stream是与待写入文件所关联的文件指针(对应的文件流)
- 此时是向文件中写数据,所以文件必须是以写或追加的方式进行打开
- 向定位指针当前指向的位置写入一个字符,写入成功会返回所写入的字符,并且定位指针前进以为。写入失败会返回EOF
fgets
char * fgets ( char * str, int num, FILE * stream );
- 第一个参数str表示把读取到的字符串拷贝到str所指向的空间
- 第二个参数num表示读取num的字符,其中会包含一个’\0’,因此实际上只会读取num-1个字符
- 第三个参数stream是与待读取文件关联的文件指针(对应的文件流)
- 读取成功会返回str,读取结束会返回NULL
- fgets是文本行输入函数,因此当一行没有读取完的时候,下一次读取会从上一次读取结束的位置继续读取当前行的文本。如果待读取的字符个数num大于当前文本行的字符个数,那此次读取只会把当前行的所有字符读取出来,不会去读取下一行的字符。
fputs
int fputs ( const char * str, FILE * stream );
- 第一个参数str表示待写入的字符串
- 第二个参数stream表示与待写入文件关联的文件指针
- 写入成功会返回一个非负数,写入失败会返回EOF
fscanf
int fscanf ( FILE * stream, const char * format, ... );
- 格式化的输入函数,可以通过指定格式从文件中读取任意类型的数据
- 第一个参数stream是与待读取文件关联的文件指针(对应的文件流)
- 后面的…表示可变参数列表,与我们平时所使用的scanf中的参数一样,是根据自己的需求来传参的。通过下面的代码来演示fscanf的用法
struct Student { char name[10]; int age; float weight; }; int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("fopen"); return 1; } struct Student s1; struct Student s2; //读文件 //... fscanf(pf, "%s%d%f", s1.name, &s1.age, &s1.weight); fscanf(pf, "%s%d%f", s2.name,&s2.age,&s2.weight); //关闭文件 fclose(pf); pf = NULL; return 0; }
首先我们定义了一个Student
类型的结构体,然后在主函数中定义了两个Student类型的变量s1
和s2
,接着我们希望从与pf所关联的test.txt
文件中去读取信息,分别存放到s1和s2里面,通过下面的执行结果可以看出,成功的从文件中读取了信息。
C语言文件操作(二)+https://developer.aliyun.com/article/1384963