因为数据如果只存储在内存中,程序结束后数据就被摧毁了,为了能够让数据持久化的保留,这里就需要把数据存储在文件中,需要使用的时候直接从文件中读取出来使用。
一、文件指针
在内存中有个专门来进行文件交互的区域,使用FILE*类型的指针来控制 *** 作文件,FILE *是个结构体指针。
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
流(stream):
流是一个很抽象的概念,可以理解为水流,为了方便所有外部设备的输入和输出,在其连接的上层封装了一个流。
当程序需要读取数据的时候,就会开启一个通向数据源的流。
这个数据源可以是文件,内存,或是网络连接。
类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。
这时候你就可以想象数据好像在其中“流”动一样。
其包括文件流、标准输入流(stdin 键盘)、标准输出流(stdout 屏幕)、标准错误流(stdrerr 屏幕)
那么对于 *** 作文件来说,就要使用文件的流。
FILE* pf=fopen("test.txt","r");//以读的方式打开文件
这里pf指针就可以理解为打开了文件test.txt并与文件流产生了连接。
注意这里最好判断pf是不是为NULL,如果为NULL说明打开文件失败
if(pf==NULL)
printf("文件打开失败\n");
这样就可以从文件读取数据或者写入数据了。
而这里根据fopen()的第二个参数,来选择对文件的具体 *** 作
字符串 | 说明 |
r | 以只读方式打开文件,该文件必须存在。 |
r+ | 以读/写方式打开文件,该文件必须存在。 |
rb+ | 以读/写方式打开一个二进制文件,只允许读/写数据。 |
rt+ | 以读/写方式打开一个文本文件,允许读和写。 |
w | 打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
w+ | 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。 |
a | 以附加的方式打开只写文件。 若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。 |
a+ | 以附加方式打开可读/写的文件。 若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。 |
wb | 以只写方式打开或新建一个二进制文件,只允许写数据。 |
wb+ | 以读/写方式打开或新建一个二进制文件,允许读和写。 |
wt+ | 以读/写方式打开或新建一个文本文件,允许读和写。 |
at+ | 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 |
ab+ | 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。 |
有的文件 *** 作会自己生成文件,如果fopen()第一个参数只写了文件名,其生成在同.c或者.cpp文件路径下 。
当然有的命令不会自己创建文件的话自己创建也可以。
fopen()其第一个参数可以如果只使用文件名,其称为相对路径。
如果加上经过的文件和盘符,其叫做绝对路径
对于绝对路径来说,其文件不用存放在同代码文件目录中,可以在任意地方。
二、文件 *** 作函数:
int ch = fgetc(pf); //从文件接受一个字符
fputc(ch,pf); //然后输出到文件里
int ch = fgetc(stdin); //从键盘接受一个字符
fputc(ch,stdout); //然后输出到屏幕上
fputs("abcdef\n",pf); //写一行
char arr[256]={0};
fgets(arr,256,pf); //只读一行,遇到\n结束。
只读255个,最后一个是\0
while(fgets(arr,256,pf)!=NULL){
printf("%s\n",arr);
}
PS:上面的函数和这里fputs()跟fgets()函数,不仅可以从文件中读取和写入一行数据,还可以使用标准输入流,标准输出流从键盘上读取输出到屏幕上。
写入文件一行字符串。
从文件读取一行。
struct Stu
{
char name[20];
int age;
double score;
}s={"张三","15",85.5};
//所有输出流的格式化输出(到文件):
fprintf(pf,"%s %d %lf",s.name,s.age,s.score);
//所有输入流的格式化(从文件)输入:
fscanf(pf,"%s %d %lf",s.name,&(s.age),&(s.score));
可以发现这两个函数跟printf()和scanf()非常相似。
fprintf(stdout,"%s %d %lf",s.name,s.age,s.score);
//相当于printf();
fscanf(stdin,"%s %d %lf",s.name,&(s.age),&(s.score));
//相当于scanf();
但是其实这两个函数跟 sprintf()和 sscanf()最为相似。
只不过一个是文件 *** 作,一个是字符串 *** 作。
char buf[256]={0};
sprintf(buf,"%s %d %lf",s.name,s.age,s.score);
//把一个格式化的数据转换成字符串
sscanf(buf,"%s %d %lf",s.name,&(s.age),&(s.score));
//从一个字符串中提取出格式化的数据
二进制写文件 :
fwrite(&s,sizeof(buf),1,pf);
fread(&s,sizeof(buf),1,pf);
二进制写文件(注意用wb格式),是把数据转换成二进制形式,不做任何更改,直接输出到文件。
但是由于文本文件.txt不能解析二进制,所以会出现乱码。
三、随机读写:指定位置读写
fseek();
SEEK_CUR;//文件当前的位置
SEEK_END;//文件末尾的位置
SEEK_SET;//文件开始的位置
文件数据有数据 a b c d e
//随机读
int ch = fgetc(pf);
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
这里读取了一个字符后,文件指针指向b,使用函数fseek()使指针在本位置,往后偏移2,指向d。
最后ch得到的就是字符d。
刚开始初始化指针pf时,指针指向文件最开始a,读一个字符,指针往后偏移1。
fseek(pf,-2,SEEK_END);
PS:使用负数是往前偏移,在指针指向文件开头的时候不要往前偏移。 指向文件时不要往后偏移。
//随机写
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
fputc('d', pf);
fseek(pf, -3, SEEK_CUR);
fputc('w', pf);
这样会把指针再次指向字符b的位置,然后把b改为w。
最后的文件存的结果就是awcd
当不知道文件指针指向何处的时候,可以用库函数来查看。
long int ftell ( FILE * stream );
返回文件指针相当于起始位置的偏移量
void rewind ( FILE * stream );
让pf指向起始地址
四、判断读取结束
是根据判断读取函数的返回值来判断的。
while(ch=getc(pf)!=EOF){
};
while(fgets(arr,256,pf)!=NULL){
};
while(fread(buf,4,1,pf)<1){
};
fread()当读到的数据已经小于1说明读取结束或者读取错误。
这时候可以使用feof()和ferror()两个函数来判断,是读到文件末尾结束的,还是读文件出错而结束的。
if(feof(pf)){
printf("文件读取结束\n");
}
else if(ferror(pf)){
printf("文件读取错误\n");
}
PS:EOF是end if file的缩写,其意思为文件的末尾。
其本质为一个宏。
五、缓冲区概念
文件在读取和写入的时候,并不是一个一个的写入和读取,这样效率低。
而是先把数据放到缓冲区,够一定数量后,一起写入或者读取。
fputs("abcde",pf);
Sleep(2000);
fflush(pf);//刷新缓冲区,把数据放到硬盘中去
这里使程序在abcde写入缓冲区,还未刷新缓冲区时,打开文件,发现文件上并没有数据。
等待20秒后,刷新了缓冲区,发现数据写入了文件。
PS:Sleep(20000)函数使程序休眠20秒,里面的单位是毫秒。
fflush()用来刷新缓冲区。
fclose()函数也有刷新缓冲区的功能。
FILE* pf=fopen("test.txt","w");
fclose(pf);
因为缓冲区的存在,所以 *** 作文件完成后必须使用fclose()关闭文件,防止后面继续 *** 作文件时会发生错误。
总结:
文件 *** 作使用的频率不高,通常在写程序日志,为了方便查看程序运行情况的时候使用。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)