本文介绍了C语言中关于文件操作的内容知识,内容较为生涩,没有理解可以多次观看
一、为什么使用文件
我们在写代码的过程中,有的时候某一些数据我们是想把它保存下来的,而不是说只有在程序运行的时候,这些数据才能被我们录入或显示出来,我们总会遇到数据持久化的场景,所以这时我们需要将数据保存下来,一般数据持久化的方式有,把数据存到磁盘文件里,或存放到数据库里面
以后我们如果想导出这部分的数据时,只要打开磁盘的文件就行了,就可将数据导入到我们所期望的程序当中
二、 数据、程序、文件
2.1 数据
数据是事实或观察的结果,是对客观事物的逻辑归纳,是用于表示客观事物的未经加工的原始素材。数据可以是连续的值,比如声音、图像、温度、压力等都称为模拟数据,也称为模拟量,相对于数字量而言,指的是取值范围是连续的变量或者数据。模拟数据是指在某个区间产生的连续值。当然数据也可以是离散的,如符号、文字称为数字数据
在计算机系统中,数据以二进制信息单元0、1 的形式表示
在计算机科学中,数据是指所有能输入计算机并被计算机程序处理的符号的介质的总称,是用于输入电子计算机进行处理,具有一定意义的数字、字母、符号和模拟量等的通称。计算机存储和处理的对象十分广泛,表示
这些对象的数据也随之变得越来越复杂。
对于逻辑归纳,比如电路,开和关分别对应着二进制数字1和0,对于一个电路,这是一个事实,而对于电路中开路断路的描述,则是通过数据来实现的。这就是逻辑归纳的数据
2.2 程序
计算机程序是一组计算机能识别和执行的指令(说的简单点就是代码呗,我们平常敲的计算机能读懂的东西),这种指令运行于电子计算机上,也是满足人们需求的一种信息化工具。
他以某些程序设计语言编写,运行于某种目标结构体系上。
举个栗子,程序就如同以英语(程序设计语言)写作的文章,要让一个懂得英语的人(编译器)同时也会阅读这篇文章的人(结构体系)来阅读、理解、标记这篇文章。一般的,以英语文本为基础的计算机程序要经过编译、链接而成为人难以解读,但可轻易被计算机所解读的数字格式,然后放入计算机内部运行。
2.3 数据和程序的关系
数据得到程序指令的处理,才能得到大多数普通人能看懂的数据(网页,app,游戏),数据只有得到程序的处理才有价值,也只有处理之后才能被称为是信息,单纯的数据其实是没有任何的意义的
2.4文件
计算机文件(或称文件、电脑档案、档案),是存储在某种长期储存设备上的一段数据流。所谓“长期储存设备”一般指磁盘、光盘、磁带等。其特点是所存信息可以长期、多次使用,不会因为断电而消失。计算机文件分为文本文件和二进制文件,文本文件仅由字符的串行构成,除此之外的文件都是二进制文件。
2.4程序设计中所谈文件
在程序设计中,我们所谈的文件,一般有两种,程序文件和数据文件(根据文件功能所划分)
2.4.1程序文件
包括源程序文件(后缀为.c),目标文件(windows环境下后缀为.obj),可执行程序(windows环境下是.exe)
2.4.2数据文件
文件的内容不是程序指令,而是程序运行时读入和写入的数据,可能包括程序运行需要从中读取数据的文件或输出内容的文件
我们着重讨论数据文件
我们最常见到的就是将数据从标准输入流(键盘)输入,显示到标准输出流(屏幕、也就是显示器)中
其实有时候我们会将数据输出到磁盘文件上,当需要的时候,再将数据从磁盘文件中拿出来,这时我们就必须学会在程序中如何操作文件
三、文件的打开和关闭(包含如何对一个文件进行读写)
3.1文件指针
讲解文件指针之前,我们先来给大家介绍一下,缓冲文件系统:
ANSI C标准采用“缓冲文件系统”处理数据文件。 所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。
如果从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘中的文件里面去。
如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区 (充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区 (给程序变量) 。
举个栗子:
我们在程序中使用到的文件都会开辟一个文件信息区(存放文件信息),这些存在于系统声明的信息都是由编译器的开发人员(微软员工)定义好类型的,并且该结构体的类型声明为FILE(源码中利用FILE创建了一个_public_file变量)。
每当我们打开一个文件时,系统会根据文件的情况自动创建一个FILE类型的结构体变量,用于填充我们文件情况的信息,我们在使用时,直接使用就好了,不必关心操作系统是如何操作的
所以我们就可以定义一个文件指针变量
FILE*pf; 这个变量可以存放我们打开的磁盘中文件的地址
然后我们就可以通过这个指针,去访问,它所维护的那个文件中所存储的信息了
3.2文件的打开和关闭(对文件进行读写)
文件在读写之前应该先打开文件,在我们使用完毕之后要关闭文件
ANSI C规定使用fopen和fclose函数来打开和关闭文件,下面是fopen和函数fclose的介绍
我们打开文件的方式,C语言规定有以下几种:
从介绍中可以读出,fopen是需要两个参数的第一个是我们的文件名,第二个参数是我们打开文件的方式,我们如果只读或只写或追加的话,打开文件的方式由表格可知,有三种,分别是(“r”,“w”,“a”)。而且他的返回值也是一个指向打开这个文件的指针,如果打开失败,他会返回一个空指针,所以在接收fopen函数返回值时,我们一般还要判断返回值是否有效,也就是确定他是否为空指针
3.3如何利用库函数来读写文件
举个栗子:
我们先来介绍几个有关写入数据到文件里面的函数
3.3.1 fgetc和fputc
fgetc,fputc分别是字符输入函数和字符输出函数,他们都适用于所有输入流和所有输出流,如果你记不住这几个函数的功能分别是什么,其实将他翻译一下就好了,get character of file和put character of file其实就是从流中读取出一个字符和向流中写入一个字符的功能
int fgetc( FILE *stream );参数是文件指针,也就是你要操作的文件地址
int fputc( int c, FILE *stream );第一个参数是你要写进去的字符(它以ascll码值的形式存储),第二个参数是文件指针,也就是你要操作的文件地址
1.fputc代码展示:
int main() { FILE* pfwrite = fopen("test.txt", "w");//以只写的方式打开文件名为test.txt的文件,路径在我们这个工程的目录底下 if (pfwrite == NULL) { printf("%s", strerror(errno)); return 0; } //写文件,用fputc将字符写到文件流中 else { fputc('l', pfwrite); fputc('o', pfwrite); fputc('v', pfwrite); fputc('e', pfwrite); } //关闭文件,将指针置为空指针 fclose(pfwrite); pfwrite == NULL; return 0; }
2.fgetc代码展示
int main() { FILE* pfread = fopen("test.txt", "r"); if (pfread == NULL) { printf("%s", strerror(errno)); } else { printf("%c", fgetc(pfread)); printf("%c", fgetc(pfread)); printf("%c", fgetc(pfread)); printf("%c", fgetc(pfread)); } //关闭文件,将文件指针置为空指针 fclose(pfread); pfread == NULL; return 0; }
3.3.2 fgets和fputs
fgets,fputs分别是文本行输入函数和文本行输出函数,他们都适用于所有输入流和所有输出流,get string of file和put string of file,其实就是从一个流中读取字符串和向一个流中写入字符串
int fputs( const char *string, FILE *stream );第一个参数是你要放到文件里面的字符串,第二个参数是你所操作的文件指针
char *fgets( char *string, int n, FILE *stream );第一个参数是你从文件读取出来字符串后,字符串所存储的地方(可以是一个字符数组),第二个参数是你所读取的字符串的最大字符个数,第三个参数是你所操作的文件地址,也就是文件指针
1.fputs代码展示:
int main() { FILE* pf = fopen("test.txt", "w"); char buf[1024] = { 0 }; if (pf == NULL) { printf("%s", strerror(errno)); return 0; } else { fputs("wyn", pf); //fgets(buf, 1024, pf); //printf("%s", buf); } //关闭文件 fclose(pf); pf = NULL; return 0; }
2.fgets代码展示
int main() { FILE* pf = fopen("test.txt", "r"); char buf[1024] = { 0 }; if (pf == NULL) { printf("%s", strerror(errno)); return 0; } else { //fputs("wyn", pf); fgets(buf, 1024, pf); printf("%s", buf); } //关闭文件 fclose(pf); pf = NULL; return 0; }
3.3.3 fscanf和fprintf
fscanf,fprintf分别是格式化输入函数和格式化输出函数,他们也适用于所有输入流和所有输出流,我们对scanf和printf是比较熟悉的,我们知道他是一种格式化的输入和输出函数,但我们以前都知道,使用scanf或printf时我们只需要向其中输入数据让printf在显示器里显示出来就可以了,但其实这两个函数只是使用了默认输入流(键盘)和输出流设备(屏幕显示器),scanf: Read formatted data from the standard input stream. printf: Print formatted output to the standard output stream.
而我们现在介绍的这两个函数其实多了一个参数,这个参数就是指向文件的文件指针,我们可以更改这个文件流。
int fprintf( FILE *stream, const char *format [, argument ]…);
int printf( const char *format [, argument]… );
通过函数互相的对比,我们可以轻松看出fprintf和fscanf的第二个参数其实就是格式化的形式,我们可以自己去定义这个格式
int fscanf( FILE *stream, const char *format [, argument ]… );
int scanf( const char *format [,argument]… );
1.fprintf代码展示
typedef struct stu { int n; float score; char arr[10]; }stu; int main() { stu s1 = { 100,3.14f,"iloveyou" }; FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { printf("%s\n", strerror(errno)); return 0; } fprintf(pf, "%d %f %s", s1.n, s1.score, s1.arr); //我们将信息内容打印到文件流pf里面去 fclose(pf); pf = NULL; return 0; }
原来的printf是把信息打印到标准输出流里面,但现在我们可以通过fprintf将信息打印到文件输出流里面,其实说白了就是,改变了信息的目的地,原来的目的地是显示器,现在的目的地可以是文件
2.fscanf代码展示
typedef struct st { int n; float score; char arr[10]; }stu; int main() { stu s1 = { 0 }; FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { printf("%s\n", strerror(errno)); return 0; } fscanf(pf, "%d %f %s", &(s1.n), &(s1.score), s1.arr); //从文件中将信息输入到我们想要存放的变量里面去,以前是从键盘中将信息输入到变量里面去 fprintf(stdout, "%d %f %s", s1.n, s1.score, s1.arr); //从标准输出流(屏幕)中将我们存放到变量中的信息打印出来 fclose(pf); pf = NULL; return 0; }
原来的scanf是,把我们从标准输入流设备(键盘),输入的信息存放到我们创建的变量里面去,但现在的fscanf是从文件流里面将我们的信息存放到我们所创建的变量,其实说白了就是改变了信息的来源方式,原先来自于键盘,现在可以来自于文件
3.3.4 fread和fwrite
fread,fwrite分别是二进制输入函数和二进制输出函数,他们的适用范围只有文件流这一种流,是不包括标准输入和输出流的(这个需要特殊记忆一下)
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );第一个参数是我们从文件中读取数据后,数据要被暂时存放的位置,第二个参数是我们读取的信息的字节大小,第三个参数是要读取的项目整体大小,第四个参数是从哪个文件里读取,我们需要传一个文件的地址
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );fwrite函数和fread函数只有第一个参数是不一样的,我们这里只给介绍第一个参数,第一个参数是我们要将变量中存放的信息写到文件里面去,我们传过去的是变量的地址
1.fwrite代码展示
typedef struct stu { char name[20]; int age; double score; }stu; int main() { stu s = { "张三",20,55.9 }; FILE* pf = fopen("test.txt", "wb"); if (pf == NULL) { printf("%s\n", strerror(errno)); return 0; } fwrite(&s, sizeof(stu), 1, pf); fclose(pf); pf = NULL; return 0; }
2.fread代码展示
typedef struct stu { char name[20]; int age; double score; }stu;//注意stu现在是一种类型 int main() { stu s = { "张三",20,55.9 }; stu tmp = { 0 }; FILE* pf = fopen("test.txt", "rb"); if (pf == NULL) { printf("%s\n", strerror(errno)); return 0; } fread(&tmp, sizeof(stu), 1, pf); printf("%s %d %lf", tmp.name, tmp.age, tmp.score); fclose(pf); pf = NULL; return 0; }
这里通过运行结果可以看出,我们上面的fwrite函数成功将信息以二进制的形式写到文件test.txt里面去了,正因为如此,我们通过二进制读取的方式才能将文件的信息以我们能看懂的方式输出到屏幕上
3.4 对比一组函数的功能(加深对输入输出函数的理解)
1.scanf/printf 是针对标准输入流/标准输出流的 一种格式化输入语句和输出语句
2.fscanf/fpirntf 是针对所有输入流/所有输出流的 一种格式化输入语句和输出语句(包含文件输入流和文件输出流)
3.sscanf/sprintf 是从字符串中读取格式化的数据 是把格式化的数据存储到(输出成)字符串
这里我们看一下,sscanf和sprintf这两个函数的声明介绍
int sscanf(const char* buffer, const char* format[, argument] …);
int sprintf(char* buffer, const char* format[, argument] …);
通过观察我们可以看到,其实这两个函数与最初的scanf和printf相比,只是多了一个要传缓冲区地址的这么一个参数
typedef struct stu { int n; float score; char arr[10]; }stu; int main() { stu s = { 1024,3.14,"love" }; stu tmp = { 0 }; char buf[1024] = { 0 }; sprintf(buf, "%d %f %s", s.n, s.score, s.arr); //原来是输出到屏幕上,现在我们将一组格式化的数据输出到(存储到)字符串数组buf里面 printf("%s\n", buf); //将格式化的数据转换成字符串存储到buf里面,我们在打印到stdout中 sscanf(buf, "%d %f %s", &(tmp.n), &(tmp.score), tmp.arr); //原来是从键盘这种标准输入流中将信息输入到我们想要放到的变量 里面 //现在我们从buf这种输入流,将信息输入到我们想要的变量(结构体tmp)里面 printf("%d %f %s\n", tmp.n, tmp.score, tmp.arr); return 0; }
通过代码运行结果可以看出,我们成功将一组格式化的数据写到字符数组里面,也成功的将字符串数组里面的内容拿出来,存放到我们想让他存在的变量里面,其实就是将原来的scanf和printf的功能扩大了一个使用的范围