一、流和标准流
作为程序猿,我们应该知道“流”的概念。那么什么是流呢?标准流又是什么呢?接下来,我们在进行文件操作之前,需要先对流有一个清楚的了解。
我们在对代码的输入时,通常会通过键盘来操作,即输入数据;同样操作系统对结果的输出,会在我们的程序运行结束之后会在屏幕上返回结果,即输出。那么我为了方便程序员对各种设备的操作,我们抽象出了流的概念:即我们可以把流想象成流淌着数据(字符)的河流。
正常情况写,我们是向流里写数据,或者输出数据,都是要先打开流,然后再进行操作。
1、标准流
这时候我们会想,我在写程序的时候要打开流才能进行操作,那么为什么没有提示我打开流呢?
在C语言程序启动时,会默认的打开3个流:
stdin | 标准输入流 | 如:scanf |
stdout | 标准输出流 | 如:printf |
stderr | 标准错误流 |
这三个流的类型是 FILE*,通常被称为文件指针。在C语言中就是通过文件指针来维护流的各种操作。
2、文件指针
这里引入了文件指针的内容,什么是文件指针?它该怎么定义呢?它的功能是什么呢?
首先当我们打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并且通过FILE的指针来维护这个FILE结构的变量,以此方便使用。
那么怎么去定义一个文件指针呢?方法如下:
FILE* pf; //文件指针变量
定义一个pf的变量,它指向文件所处的文件信息区(这个文件信息区是一个结构体变量),通过这个⽂件信息区中的信息就能够访问该⽂件。
二、对文件操作
1、文件的打开和关闭
在文件进行文件操作的时候,首先应该打开文件,其次使用完成之后,应该关闭文件。在C语言中,文件的打开和操作代码如下:
//打开文件
FILE *fopen(const char *filename, const char * mode );
//关闭文件
int fclose(FILE *stream);
mode表示文件的打开模式,有以下几种:
文件使用方式 | 含义 | 如果指定文件不存在 |
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
“w”(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建立一个新文件 |
“a”(追加) | 向⽂本⽂件尾添加数据 | 建立一个新文件 |
“rb”(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 出错 |
“wb”(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建立一个新文件 |
“ab”(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建立一个新文件 |
"r+"(读写) | 为了读和写,打开⼀个⽂本⽂件 | 出错 |
"w+"(读写) | 为了读和写,建立⼀个新的⽂件 | 建立一个新文件 |
"a+"(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建立一个新文件 |
"rb+"(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
"wb+"(读写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建立一个新文件 |
"ab+"(读写) |
打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写
| 建立一个新文件 |
例如 我们要打开一个命名为“test.txt”的文件,对它进行写的操作之后,再读取文件内容:
#include <stdio.h>
int main() {
FILE *file;
char ch;
// 创建并写入文件
file = fopen("test.txt", "w");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
// 写入字符
const char *text = "Hello, World!\n";
while (*text) {
fputc(*text++, file);
}
fclose(file);
// 读取并打印文件内容
file = fopen("test.txt", "r");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
while ((ch = fgetc(file)) != EOF) {
putchar(ch);
}
fclose(file);
return 0;
}
2、文件读写
在C语言中,文件读写分为顺序读写和随机读写:
2.1 顺序读写
顺序读写是指按照文件中数据的存储顺序进行读写操作。通常情况下,这种方式简单且效率较高,适用于大多数文件处理场景。
首先是对于顺序读写函数的介绍:
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
注:以上所有输入流指的是:标准输入流和其他输入流(文件流);所有输出流指的是:标准输出流和其他输出流(文件流)。
这里我们主要介绍一下fread和fwrite两个函数:
2.1.1fread和fwrite函数:
1、声明和定义:
//fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
//fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
size_t: 无符号整型
ptr:目标文件
size:读取类型所占字节的大小
nmemb:读取size的个数
stream:即读取的源文件
2.运用示例:
//fwrite 以二进制的形式写入
int main() {
int arr[] = { 1,2,3,4,5 };
//打开文件
FILE* pf = fopen("text.txt", "wb");
//判断文件是否打开成功
if (pf == NULL) {
perror("pf");
return 1;
}
//写数据
int len = sizeof(arr) / sizeof(arr[0]);
fwrite(arr, sizeof(arr[0]), len, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//fread 以二进制的形式读取
int main() {
int arr[5] = { 0 };//定义读取内容的存放地址
FILE* pf = fopen("text.txt", "rb");
//判断是否打开文件
if (pf == NULL) {
perror("pf");
return 1;
}
//读取数据
/*int len = sizeof(arr) / sizeof(arr[0]);
fread(arr, sizeof(arr[0]), 5, pf);
//循环输入
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}*/
//另一种写法:
int i = 0;
->1 指的是每次读取几个数
//|
while (fread(&arr[i], sizeof(int), 1, pf)) {
printf("%d ", arr[i]);
i++;
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.2 随机读写
随机读写允许在文件的任意位置进行读写操作。
2.2.1 fseek
1.函数声明:
int fseek(FILE *stream, long int offset, int whence)
2.函数介绍:
库函数fseek,设置流 stream 的文件位置(目标文件)为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
这里whence有三个参数可供选择:SEEK_CUR(当前位置),SEEK_SET(文件起始位置),SEEK_END(文件结尾位置)。
3.示例:
//fseek
int main() {
//打开文件 此处省略在文件中写入字符串
FILE* pf = fopen("text.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//读取文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
//fseek(pf, 4 , SEEK_CUR);
//fseek(pf, 5, SEEK_SET);//文件起始
fseek(pf, -3, SEEK_END);
//查找之后再次获取字符
ch = fgetc(pf);
printf("%c\n", ch);//f
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.2.2 ftell
1.函数声明:
long int ftell(FILE *stream)
2.函数介绍:
库函数 ftell返回给定流 stream 的当前文件位置。
3.示例:
int main() {
//打开文件 此处省略在文件中写入字符串
FILE* pf = fopen("text.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//读取文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
fseek(pf, 0, SEEK_END);
//返回文件偏移量
printf("%d\n", ftell(pf));//9
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.2.3 rewind
1.函数声明:
void rewind(FILE *stream)
2.函数介绍:
库函数 rewind设置文件位置为给定流 stream 的文件的开头。即将光标位置置为文件开头。
3.示例:
int main() {
//打开文件,此处省略在文件中写入字符串
FILE* pf = fopen("text.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//读取文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
fseek(pf, -3, SEEK_END);
printf("%d\n", ftell(pf));
//置为开头
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.3 对比总结
顺序读写:简单,适用于数据流处理,按顺序处理文件内容。
随机读写:灵活,适用于需要在文件中间插入、修改或读取特定位置数据的情况。
三、两组函数的对比
1、scanf、fscanf、sscanf
函数名 | 函数介绍 |
scanf | 从标准输入流(键盘)上读取格式化的数据 |
fscanf | 从指定的(所有的)输入流上读取格式化的数据 |
sscanf | 在字符串中读取格式化的数据 |
2、printf、fprintf、sprintf
函数名 | 函数介绍 |
printf | 把数据以格式化的形式打印在标准输出流上 |
fprintf | 把数据以格式化的形式打印在指定的(所有的)输出流上 |
sprintf | 把格式化的数据转化成字符串 |
四、文件读取结束的判断
1、feof()函数
feof 的作用是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。
fgetc 判断是否为 EOF 。
fgets 判断返回值是否为 NULL。
例如:
int main() {
//打开文件
FILE* pf = fopen("text.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
int ch = 0;
//按顺序读取
while ((ch = fgetc(pf)) != EOF) {
printf("%c\n", ch);
}
//判断是什么原因导致的读取结束
if (feof(pf)) // feof()函数 是判断已经结束的原因是什么
{
printf("遇到文件末尾\n");
}
//ferror()函数 如果设置了与流关联的错误标识符,该函数返回一个非零值,否则返回一个零值。
else if(ferror(pf)){
perror("ferror");
}
fclose(pf);
pf = NULL;
return 0;
}
结语 :
文件操作中,对于流的定义和解释需要我们去理解,至于所介绍到的函数则需要我们在见到或者使用时可以看懂和灵活运用。在文件操作中还有其他方面也需要我们了解,如文件缓冲区的存在,这里我就不去介绍了,有兴趣的大佬或萌新可以在评论区留言交流,感谢大家支持。