目录
1. 什么是文件
2. 文件名
3. 文件类型
4. 文件缓冲区
5. 文件指针
6. 文件的打开和关闭_fopen_fclose
7. 文件的顺序读写
7.1 fputc
7.2 fgetc
7.3 fputs
7.4 fgets
7.5 fscanf
7.6 fprintf
7.7 fread
7.8 fwrite
7.9 对比scanf/printf、fscanf/fprintf、sscanf/sprintf
我们刚刚学习过了动态版的通讯录,试想一下,如果我们每次打开通讯录,都需要对所需要的好友进行重复增加,当退出通讯录以后,所加的好友又会消失,往往复复,这将是一件很麻烦的事;我们当然希望把通讯录的信息存储下来,下次打开时还可以使用上次存储的信息;本节文件操作将会帮我们解决这个问题;
一起开始文件操作的学习吧!
磁盘上的文件就是本节所称的文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件;
程序文件:包括源程序文件(后缀为.c)。目标文件(后缀为.obj)、可执行程序(后缀为.exe);
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据。比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本篇主要讨论的是数据文件;
一个文件需要有唯一的文件标识,以便于用户识别和引用;
文件名包含3部分:文件路径+文件名主干+文件后缀 ag. c:\code\test.txt 为了方便起见,文件标识常被称为文件名;
(c:\code称为文件路径;test称为文件名主干;.txt称为文件后缀)
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。(文本文件是我们可以看得懂的)
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件;
如果要求在外存上以ASCII码的形式存储,则需要在存储之前进行转换。以ASCII字符的形式存储的文件就是文本文件。
数据在内存中的存储方式:字符一律以ASCII形式存储,数值型数据既可以用ASCII形式储存,也可以使用二进制形式存储。
ag. 例如在内存中存储10000的储存形式:00000000 00000000 00100111 00010000
如果以ASCII码的形式存储得到的是文本文件,占用5个字节:00110001 00110000 00110000 00110000 00110000 (ASCII码的存储方式是按照ASCII码表来存储的,10000中1对应49,也就是00110001,0对应48,也就是00110000)
如果以二进制的形式存储得到的是二进制文件,占用4个字节:00000000 00000000 00100111 00010000
所谓的缓冲文件系统是指系统自动地在内存中为程序中的每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会首先送到输出缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区,充满输入缓冲区以后,再从缓冲区逐个地将数据送到程序数据区。缓冲区的大小根据C编译系统决定。
在缓冲文件系统中,关键的概念是“文件类型指针”,简称文件指针;
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(文件的名字、文件状态、文件的当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名为FILE。
在stdio.h文件中拿到FILE的定义,该FILE是一个结构体变量,该结构体如下所示:
#ifndef _FILE_DEFINED
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf 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);
int main()
{
//打开文件2022_12_24.txt
//如果写F:\\新建文件夹\\新建文件夹\\VS2013\\2022_12_24\\2022_12_24\\text ,他表示绝对路径,绝对路径需要注意转义;\\转义再转义
//而如果写text,则表示相对路径,默认相对路径和该编程项目文件在同一目录下
//如果出现打开文件失败或者找不到文件,则需要注意是相对路径还是绝对路径,文件和工程文件是否在一起
FILE* pf = fopen("F:\\新建文件夹\\新建文件夹\\VS2013\\2022_12_24\\2022_12_24\\text", "r");//“r”只读
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
fclose(pf);
pf = NULL;
return 0;
}
//F:\新建文件夹\新建文件夹\VS2013 这是文件原本的存储路径,之所以写成F:\\新建文件夹\\新建文件夹\\VS2013
//是因为单个\t会被识别为转义字符,所以\\t 表示转义之后在进行转义;
注意:
1. 打开文件fopen也会出错,文件打开失败时,返回值为NULL;如果打开成功,会返回该文件指向的指针;打开文件以后,也需要fclose关闭文件,将文件所指向的指针赋值为空指针;
2. 打开文件的过程是:该文件所指向的指针首先向保存该文件的文件信息区访问,文件缓冲区满以后会访问该文件。关闭的过程同样如此,首先会访问文件缓冲区;
文件的顺序读写涉及的函数有:
字符输入函数:fgetc,适用于所有输入流 //读文件 函数原型:int fgetc(FILE* stream); 头文件:stdio.h
字符输出函数:fputc,适用于所有输出流 //写文件 函数原型:int fputc(int c,FILE* stream);
文本行输入函数:fgets,适用于所有输入流 //读取一行 函数原型:char* fgets(char* string,int n,FILE* stream);第一个参数是读取的数组,第二个参数是所能读取的最大数量,第三个参数是文件指针;
文本行输出函数:fputs,适用于所有输出流 //写一行 函数原型:int fputs(const char* string,FILE* stream);
格式化输入函数:fscanf,适用于所有输入流 //格式化读取程序 函数原型:int fscanf(FILE* stream,const char *format[,argument]……);
格式化输出函数:fprintf,适用于所有输出流 //格式化写程序 函数原型:int fprintf(FILE* stream,const char* format[,argument]……);
以上所有都是以文本形式输入/输出;
以下是以二进制形式输入/输出;
二进制输入:fread,适用于文件 //读二进制文件 函数原型:size_t fread(void* buffer,size_t size,size_t count,FILE* stream);
二进制输出:fwrite,适用于文件 //写二进制文件 函数原型:size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);
流:流是一个高度抽象的概念;数据要操作各种各样的硬件,所采用的读写方式是不同的,所以为了简化这一抽象的过程,引入流的概念;
我们只需要知道程序向流中去输入数据,数据需要转换给不同的硬件,在这个过程中起着重要作用的就是流;我们要知道我们通过程序操作各种硬件,都是在流中进行的;
C语言程序只要运行起来,就默认的打开了3个流:
stdin:标准输入流---键盘;// 输入默认是键盘输入的程序
stdout:标准输出流---屏幕;//因为错误和输出的程序是需要我们视觉的,所以在屏幕
stderr:标准错误流---屏幕;
字符输出函数:fputc,适用于所有输出流 //写文件 函数原型:int fputc(int c,FILE* stream);
这个时候如果继续用fputc写文件,而写的内容为空,对于该打开方式来说,表示已经执行了该程序,原本写的内容会被覆盖;又因为fputc写文件并未写任何内容,所以文件为空;
通过fputc写一个字符通过流到屏幕上,也就是stdout;
字符输入函数:fgetc,适用于所有输入流 //读文件 函数原型:int fgetc(FILE* stream); 头文件:stdio.h
从流里面读取一个字符,也可以从stdin标准输入流里面读取,函数的返回值是读取到的ASCII,若读取失败,返回-1;
hello是通过fputc写入的,则可以通过pgetc获得;
从标准输入流里读取字符:意思就是程序运行,stdin表示标准输入流,可以理解为从键盘输入的,所以手动敲hello,会通过fgetc读出;每次读一个字符;所以用%c打印,是能够读取空白字符的;
能够读取空白字符的意思是:
通过fgetc读取输入数据流,该程序我读取5个字符,当我输入abcd\n时,在读取a b c d 后后面会空白换行-----2行,第一行是5个字符中的第五个,因为我输入数据流没有第五个字符,所以默认打印换行;第二行是\n,%c会打印成换行符;
文本行输出函数:fputs,适用于所有输出流 //写一行 函数原型:int fputs(const char* string,FILE* stream);
写或者输出一个字符串到流,通过%s打印出来,适用于所有的输出流(stdout);
\n会认为是转义字符,打印换行;
通过fputs写标准输出流打印到屏幕:
文本行输入函数:fgets,适用于所有输入流 //读取一行 函数原型:char* fgets(char* string,int n,FILE* stream);第一个参数是读取的数组,第二个参数是所能读取的最大数量,第三个参数是文件指针;
从流里面读取一个字符串,也可以从标准输入流stdin里面读;
打开文件的方式是读r;文件里面存放的是helloworld welcome,fgets的第二个参数表示读的最大字符数,此处设置时5,会读取hell\n,转义字符\n占用一个;当不足4个字符时,会换行。
第二次读取会从第一次读取的结束处继续读取;
从标准输入流stdin读取字符,可以读取到空白字符;
同样的,num设置为5,读取hell\n,\n占用一个字符,o wo 表示可以读取到空白字符;rld不足4个字符,\n转义字符打印成换行;
格式化输入函数:fscanf,适用于所有输入流//格式化读取程序 函数原型:int fscanf(FILE* stream,const char *format[,argument]……);
fscanf就是读取fprintf已经写入文件的程序,将其打印下来;
格式化输出函数:fprintf,适用于所有输出流 //格式化写程序 函数原型:int fprintf(FILE* stream,const char* format[,argument]……);
因为我们之前基于文件的读写操作都是字符/字符串,那么具有一定格式的程序是否可以文件操作呢?答案是可以的。
使用fscanf和fprintf将输出数据流打印到屏幕:
二进制输入:fread,适用于文件 //读二进制文件 函数原型:size_t fread(void* buffer,size_t size,size_t count,FILE* stream);
二进制写入文件的程序是乱码,但是可以通过fread将文件中的内容读出来;
二进制输出:fwrite,使用于文件 //写二进制程序 函数原型:size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);
程序原型:int sscanf(const char *buffer,const char * format[,argument]……);
程序原型:int sprintf(char *buffer,const char*format[,argument]……);
sprintf具有可以把结构体转换成字符串的能力:
(定义一个结构体,初始化结构体成员变量;运用sprintf函数,打印数组buf内容,结果发现,%s打印出来的结果是整个结构体的内容;表明sprintf可以将结构体转换成字符串的形式)
struct S
{
int n;
float score;
char arr[20];
};
int main()
{
struct S s = { 100, 3.14f, "abcdef" };
struct S tmp = { 0 };
char buf[1024] = { 0 };
//sprintf功能是把格式化的数据转换成字符串存储到buf
sprintf(buf, "%d %f %s", s.n, s.score, s.arr);
//sscanf的功能是从buf中读取格式化的数据到tmp中
sscanf(buf, "%d %f %s", &(tmp.n), &(tmp.score), &(tmp.arr));
printf("%d %f %s", tmp.n, tmp.score, tmp.arr);
return 0;
}
1. scanf/printf:是针对标准输入流/标准输出流的格式化输入/输出语句;
2. fscanf/fprintf:是针对所有输入流/所有输出流的格式化输入/输出语句;
3. sscanf/sprintf:sscanf是从字符串中读取格式化的数据
sprintf是把格式化数据输出成字符串