C语言中有时要用到文件,那么如何用C语言来操作文件呢?
1、流的概念
我们程序的数据需要输出到外部设备上,也需要从外部设备输入到我们的C语言程序上,为了方便操作,我们抽象出了流的概念(到这里虽然你不理解,其实我也不理解,只需要记住流这个专业名词就好)。
一般情况下,我们要输入数据要先打开流,输入结束后要关闭流,文件操作也要打开文件,操作结束后关闭文件。
那为什么我们平时输入数据时没有打开对应的流呢?
其实C程序在我们开始写程序时就已经提前打开3个流了
stdin-标准输入流,大多数环境下从键盘上输入数据,scanf就是在标准输入流中读取数据;
stdout-标准输出流,大多数环境下在屏幕上输出数据,printf就是在标准输出流中输出数据;
stderr-标准错误流,大多数环境下在屏幕上输出数据;
stdin,stdout和stderr这三个流的类型就是FILE*,通常称为文件指针
C语言操作文件就是通过文件指针来打开和关闭文件的,进行一系列的文件操作;
2、文件的打开和关闭
我们在打开文件时会返回一个FILE*的指针,需要用一个FILE*类型的变量接收它;
ANSIC 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件;
//打开文件 FILE*pf=fopen("const char *filename","const char *mode"); //关闭文件 fclose(pf); pf=NULL;
filename 指的是文件名字,mode指的是打开模式
例如:
int main() { //打开文件 FILE*pf=fopen("data.txt","w"); if(pf==NULL) { perror("pf"); return -1; } //关闭文件 fclose(pf); pf=NULL; return 0; }
3、关于文件操作的函数
1、顺序读写介绍
fgetc
fgetc是从文件中读入一个字符,用只读的方式打开,返回值是int类型;
fputc
fputc是向文件中写入一个字符,用只写的方式打开文件
fgets
fgets是从文件中读入一串字符,用只读的方式打开;
fgets有三个参数,第一个是char *的指针,第二个是读入的字符个数,第三个是文件指针;
举个例子,fgets就是在p1指向的文件中读入至多4个字符,放入arr中;
为什么是至多四个字符,fgets函数规定的是读入num-1个字符,如果文件中不足num-1个字符,那么就有多少读多少,如果文件中足够num-1个字符,就读入num-1个字符;
fputs
fputs是向文件中写入一串字符串
举例:
#include <stdio.h> int main () { FILE * pFile; char sentence [256]; fgets (sentence,256,stdin); pFile = fopen ("mylog.txt","a"); fputs (sentence,pFile); fclose (pFile); return 0; }
fscanf
fscanf是格式化输入函数
scanf是从键盘向程序中格式化输入数据;
fscanf是文件中向程序中格式化输入数据;
fprintf
fprintf是从程序中向文件中格式化输入数据
fread
fread是用二进制的方式从文件中读取数据;
fread有四个参数,从文件中读取count个,大小为size字节的数据放入ptr中;
fwrite
fwrite是用二进制的方式从程序中向文件写入数据;
fwrite有四个参数,从ptr中读取count个,大小为size字节的数据放入文件中;
2、随机读写介绍
fseek
根据⽂件指针的位置和偏移量来定位⽂件指针;
文件中有光标,SEEK_SET是光标起始位置;
SEEK_CUR是光标的当前位置;
SEEK_END是光标末尾位置;
fseek可以调整光标的位置;
举例:
/* fseek example */ #include <stdio.h> int main() { FILE* pFile; pFile = fopen("data.txt", "wb"); fputs("This is an apple.", pFile); fseek(pFile, 9, SEEK_SET); fputs(" sam", pFile); fclose(pFile); return 0; }
用fseek将光标位置移到 a 的前面,再次写入" sam";
ftell
ftell是返回文件指针,也就是光标的当前位置相对于起始位置的偏移量;
举例:
计算文件有多少个字符:
/* ftell example : getting size of a file */ #include <stdio.h> int main () { FILE * pFile; long size; pFile = fopen ("myfile.txt","rb"); if (pFile==NULL) perror ("Error opening file"); else { fseek (pFile, 0, SEEK_END); // non-portable size=ftell (pFile); fclose (pFile); printf ("Size of myfile.txt: %ld bytes.\n",size); } return 0; }
rewind
rewind是使文件指针,也就是光标回到起始位置;
3、文件读取结束后的原因判定
feof和ferror
feof是判断文件是否遇到了/0而正常结束,
ferror是判断文件是否遇到了错误而非正常结束,
文件读取结束的标志
文本文件
文本文件的读取结束标志是判断返回值是否为EOF或者NULL,
例如:
fgtec返回EOF表示文件读取结束;
fgets返回NULL表示文件读取结束;
#include <stdio.h> #include <stdlib.h> int main(void) { int c; // 注意:int,⾮char,要求处理EOF FILE* fp = fopen("test.txt", "r"); if(!fp) { perror("File opening failed"); return 1; } //fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环 { putchar(c); } //判断是什么原因结束的 if (ferror(fp)) puts("I/O error when reading"); else if (feof(fp)) puts("End of file reached successfully"); fclose(fp); }
二进制文件
二进制文件的读取结束标志是判断返回值是否小于预期要读的个数;
例如:fread判断返回值是否小于预期读入的字符个数;
举例:
#include <stdio.h> enum { SIZE = 5 }; int main(void) { double a[SIZE] = {1.,2.,3.,4.,5.}; FILE *fp = fopen("test.bin", "wb"); // 必须⽤⼆进制模式 fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组 fclose(fp); double b[SIZE]; fp = fopen("test.bin","rb"); size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组 if(ret_code == SIZE) { puts("Array read successfully, contents: "); for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]); putchar('\n'); } else { // error handling if (feof(fp)) printf("Error reading test.bin: unexpected end of file\n"); else if (ferror(fp)) { perror("Error reading test.bin"); } } fclose(fp); }
4、文件缓冲区
解释
ANSIC标准采用“缓冲文件系统”处理的数据文件的, 所谓缓冲文件系统是指系统自动地在内存中为
程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,
装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据, 则从磁盘文件中读取数据输
入到内存缓冲区 (充满缓冲区), 然后再从缓冲区逐个地将数据送到程序数据区 (程序变量等)。
缓冲区的大小根据C编译系统决定。
缓冲区的概念,还是为了方便数据的传输,
举例
洗袜子,你今天换了一双袜子,放在盆里,一次洗一双袜子,需要水,洗衣粉还要忍受凉水的冰冷,每天换一双袜子,每天都要洗,是不是很麻烦,也用的水多,不如我们攒的袜子多了,把盆装满了,一次性洗,是不是更方便,收袜子的时候,也是一次收获很多双袜子,类似于文件缓冲区,加快了数据传输效率
一个有味道的例子!