引言
我们在写程序的时候,往往会将数据存放在变量当中。如果程序退出,内存回收,这些数据就会丢失。那么我们是否有办法对这些数据进行持久化的保存,再次打开程序时数据仍然存在?答案是肯定的。而能够做到这件事的就是:文件操作。
一、文件的打开和关闭
1.流
程序的数据需要输出到外部设备,也需要从外部设备输入。对于不同设备,输入输出方式各有不同。为了方便我们在对各种输入输出设备进行操作,就有了“流”这一高度抽象的概念。在c语言中,对文件,画面,键盘等的输入输出操作都是同过“流”进行的。在一般情况下,我们想要写入数据或者读取数据,都需要打开流。
2.标准流
c语言程序在启动的时候,默认有三个流已经为我们打开:
stdin--标准输入流,绝大多数情况下从键盘输入。
stdout--标准输出流,绝大多数情况输出至显示器上。
stderr--标准错误流,绝大多数情况输出至显示器上。
正因为这三个流已经默认打开,所以我们使用scanf、printf等函数就可以直接进行输入输出操作。
这三个流也是具有类型的,它们的类型是:文件指针(FILE*)。我们在进行文件操作的时候,通过文件指针变量就可以间接找到与它关联的文件。
3.文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。 数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
⼀个数据在文件中是怎么存储的呢?
字符⼀律以ASCII形式存储,数值型数据既可以以ASCII形式存储,也可以使用二进制形式存储。如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字节),而二进制形式输出,则在磁盘上只占4个字节。
4.控制文件打开与关闭的函数
在了解了这些前置知识之后,我们切入正题--打开文件和关闭文件。c语言提供了两个函数,它们分别负责文件的打开和关闭,原型如下:
打开文件:
FILE * fopen ( const char * filename, const char * mode );
关闭文件:
int fclose ( FILE * stream );
fopen函数有两个参数,第一个参数是以字符串形式表示的文件名,第二个参数是文件的打开方式,用字符串表示。这个函数在使用的时候我们需要创建一个文件指针变量来接收,当文件打开失败时,它会返回空指针。关于打开方式,在这里一一列举:
打开方式 | 含义 | 如果指定的文件不存在 |
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 返回空指针 |
“w”(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的⽂件 |
“a”(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“rb”(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 返回空指针 |
“wb”(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建⽴⼀个新的⽂件 |
“ab”(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“r+”(读写) | 为了读和写,打开⼀个⽂本⽂件 | 返回空指针 |
“w+”(读写) | 为了读和写,建立一个新的文本文件 | 建⽴⼀个新的⽂件 |
“a+”(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建⽴⼀个新的⽂件 |
“rb+”(读写) | 为了读和写打开⼀个⼆进制⽂件 | 返回空指针 |
“wb+”(读写) | 为了读和写,建立⼀个新的⼆进制⽂件 | 建⽴⼀个新的⽂件 |
“ab+”(读写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建⽴⼀个新的⽂件 |
对于fclose函数,它的参数是文件指针,用于关闭这个文件指针指向的文件。
接下来,我们尝试打开和关闭一个文件:
int main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL)//文件打开失败则退出程序 { perror("fopen"); return 0; } printf("文件打开成功\n"); fclose(pf); pf = NULL;//避免出现野指针,及时制空 return 0; }
运行结果:
可以看到,由于打开方式是“w”,该路径下确实出现了一个名为“test.txt”的文件。
二、文件的顺序读写
接下来介绍几个函数,用于在文件当中读取或者写入数据。
函数名 | 功能 | 适⽤于 |
fgetc | 字符输⼊函数 |
所有输⼊流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | ⽂本⾏输⼊函数 | 所有输⼊流 |
fputs | ⽂本⾏输出函数 | 所有输出流 |
fscanf | 格式化输⼊函数 | 所有输⼊流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | ⼆进制输⼊ | ⽂件输⼊流 |
fwrite | ⼆进制输出 | ⽂件输出流 |
接下来我们尝试使用一下fscanf和fprintf函数:
这两个函数的第一个参数是一个文件指针,用于对文件数据进行读取和写入操作。之后的参数与scanf、printf函数相同。
int main() { FILE* pf = fopen("test.txt", "w");//要写入数据,以写的形式打开文件 if (pf == NULL) { perror("fopen"); return 0; } fprintf(pf, "123456");//向文件中写入数据 fclose(pf); pf = NULL; return 0; }
可以看到,数据已经写入到文件当中了。接下来我们使用fscanf函数将文件数据打印到屏幕上:
int main() { char str[20] = { 0 }; FILE* pf = fopen("test.txt", "r");//要读取数据,以读的形式打开文件 if (pf == NULL) { perror("fopen"); return 0; } fscanf(pf, "%s", str);//读取数据到str当中 printf(str); fclose(pf); pf = NULL; return 0; }
运行结果:
三、文件的随机读写
1.fseek函数
fseek函数的作用是根据文件指针的位置和偏移量来定位文件指针。它的原型如下:
int fseek ( FILE * stream, long int offset, int origin );
它的第一个参数是文件指针,第二个参数是相对于设置位置的偏移量,第三个参数是设置的位置。
c语言定义了三个宏,可选择其中一个作为第三个参数:
SEEK_SET:文件的起始位置
SEEK_CUR:文件指针当前的位置
SEEK_END:文件的末尾
定位了文件指针之后,我们就可以在文件的指定位置处进行读取或者写入操作了。
2.ftell函数
ftell函数用于返回文件指针相对于起始位置的偏移量。函数原型:
long int ftell ( FILE * stream );
3.rewind函数
rewind函数用于将文件指针的位置回到文件的起始位置。它的函数原型:
void rewind ( FILE * stream );
四、文件读取结束的判定
1.对于文本文件,我们首先可以使用fgetc函数循环读取文件中的字符,直到读到EOF为止,说明文件读取结束。
2.对于二进制文件,我们使用fread函数判断其返回值:是否小于实际要读的个数。如果小于,则说明读取结束。
对于以上两种情况,文件读取结束时,我们可以继续判断文件读取结束的原因。介绍两个函数:feof和ferror。
feof函数用于判断文件读取结束的原因是否是遇到文件尾。它的原型如下:
int feof ( FILE * stream );
如果文件由于读取到文件末尾而读取结束,则返回EOF;如果是其他情况,则会返回0。
ferror函数用于判断文件读取结束的原因是否是出现I/O错误。原型如下:
int ferror ( FILE * stream );
如果文件由于出现I/O错误而读取结束,则返回非0值;其他情况则返回0。
接下来我们尝试写一段代码对文件读取结束进行判断:
int main() { int c = 0; FILE* fp = fopen("test.txt", "r"); if (fp == NULL) { perror("fopen"); return 0; } while ((c = fgetc(fp)) != EOF)//返回EOF则读取结束 { putchar(c); } //判断读取结束的原因 if (ferror(fp)) puts("I/O错误\n"); else if (feof(fp)) puts("文件读取结束\n"); fclose(fp); fp = NULL; return 0; }
总结
今天我们学习了文件操作相关的知识,了解了文件的打开关闭,写入数据或者从文件读取数据的方法,以及调整文件指针的函数,还有对文件读取结束的判定。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤