Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。
目录
- 前言
- 四、文件的顺序读写
- 4.1 顺序读写函数介绍
- 4.1.1 fputc
- 4.1.2 fgetc
- 4.1.3 fputs
- 4.1.4 fgets
- 4.1.5 fprintf
- 4.1.6 fscanf
- 4.1.7 sprintf(操作的不是文件)
- 4.1.8 sscanf(操作的不是文件)
- 4.1.9 fwrite
- 4.1.10 fread
- 五、文件结束的判定
- 5.1 被错误使用的feof
- 5.2 文本文件读取结束
- 5.3 二进制文件读取结束
- 六、文件缓冲区
- 总结
前言
上篇文章中我们初步了解了文件的相关信息,文件的打开和关闭,以及文件的随机读写等
本篇文章将详细介绍一些文件顺序读写函数的作用、特点和用法,使我们更加方便地操作文件,还会讲到如何判定文件的结束等,内容可能有点多,请耐心看完哦
四、文件的顺序读写
4.1 顺序读写函数介绍
下面这些函数都在头文件
<stdio.h>
中定义
函数名 | 功能 | 适用于 |
---|---|---|
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输出函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件输入流 |
fwrite | 二进制输出 | 文件输出流 |
4.1.1 fputc
fputc
函数的原型如下:
int fputc( int ch, FILE *stream );
fputc
函数的功能是:写入字符ch
到给定输出流stream
ch
:要写入的字符stream
: 输出流
开始时在当前工程目录底下创建一个文本文档,存入数据:
运行下面的代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
int i = 0;
for (i = 'a'; i <= 'z'; i++)
{
//fputc函数一次只能写入一个字符
fputc(i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行上面的代码后查看文档,可以看到里面的内容已经被修改
4.1.2 fgetc
fgetc
函数原型如下:
int fgetc( FILE *stream );
stream
:读取字符的来源
fgetc
函数读取正常时返回读取到的字符的ASCII码值,失败时返回EOF
运行下面的代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = 0;//注意:为处理 EOF 需要 int 而非 char
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行成功,在终端上打印出 ‘a’ ~ ‘z’:
fgetc
和fputc
适用所有输入流和所有输出流,当然包括标准输入流stdin
和标准输出流stdout
:
#include <stdio.h>
int main()
{
int ch = 0;
ch = fgetc(stdin);//从键盘(标准输入流)上读取
fputc(ch, stdout);//将字符输出(写)到屏幕(标准输出流)
return 0;
}
4.1.3 fputs
fputs
函数原型如下:
int fputs( const char *str, FILE *stream );
fputs
函数的功能是:将以NULL
结尾的字符串str的每个字符写入到输出流stream
,如同通过重复执行fputc
,不将 str 的空字符串写入
运行下面的代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("Are you ok?", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行成功后查看文档,内容已经被重写:
fputs
函数在写入字符串的时候是不主动换行的
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("Are you ok?", pf);
fputs("What can I say? man.", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4.1.4 fgets
fgets
函数原型如下:
char* fgets( char *str, int count, FILE *stream );
str
:指向char
型数组元素的指针count
:写入的最大字符数(典型的为 str 的长度)stream
:读取数据来源的文件流
fgets
函数的返回值:成功时为str
,失败时为NULL
fgets
函数的作用:
- 从给定文件流读取最多
count-1
个字符并将它们存储于str
所指向的字符数组 - 若文件尾出现或发现换行符则终止分析,后一情况下 str 将包含一个换行符
- 若读入字符且无错误发生,则紧随
str
的最后一个字符后写入空字符'\0'
将test.txt
文档中的内容改为“abcdefghijklmnopq”:
调试下面的代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char str[20] = "xxxxxxxxxxxx";
fgets(str, 5, pf);
printf("%s\n", str);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
可以看到,虽然函数fgets
确实在数组str
中存入了5个字符,但是只读取了文档test.txt
中实际的4个字符存入数组str
中,还有一个是字符‘\0’
也就是说当参数
count
给的值是5的时候,实际只从文件中读取4个字符
将test.txt
文档中的内容改为:
调试下面的代码:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char str[20] = "xxxxxxxxxxxx";
fgets(str, 10, pf);
printf("%s\n", str);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
可以看到,数组
str
中并没有存入我们预想的10个字符,所以fgets
函数遇到换行符‘\n’
会停止读取,并且将‘\n’
也存入数组str
中
当然不管哪种情况最后都会补‘\0’
同样的,fgets
和fputs
也适用所有输入流和所有输出流,当然也包括标准输入流stdin
和标准输出流stdout
:
#include <stdio.h>
int main()
{
char str[20] = { 0 };
fgets(str, 20, stdin);
fputs(str, stdout);
return 0;
}
4.1.5 fprintf
fprintf
函数原型如下:
int fprintf( FILE *stream, const char *format, ... );
将结果写入输出流
stream
对比printf
:
int printf( const char *format, ... );
将结果写入输出流
stdout
可以看到fprintf
函数比printf
多了一个参数——文件指针
其中 ...
表示可变参数列表,所以函数fprintf
也可以有若干个参数
运行下面的代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
struct S s = { "xiaoshuai", 25, 75.3 };
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%s\n%d\n%.1lf\n", s.name, s.age, s.weight);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
可以看到,文档的内容已经被重写
4.1.6 fscanf
fscanf
函数的原型如下:
int fscanf( FILE *stream, const char *format, ... );
对比scanf
函数:
int scanf( const char *format, ... );
也是多了一个文件指针参数
运行下面的代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
struct S s = { 0 };
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf, "%s %d %lf\n", s.name, &s.age, &s.weight);
//从文件中读取的信息存到结构体s中
//注意:从文件中读的时候不要用 %.1lf
//s.name是数组名不需要加取地址操作符
printf("%s\n%d\n%.1lf\n", s.name, s.age, s.weight);
//打印在屏幕上
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
当然也可以用fprintf
打印:
fprintf
和fscanf
就相当于printf
和scanf
的升级版,功能是在它们两个之上的
4.1.7 sprintf(操作的不是文件)
注意:函数
sprintf
操作的不是文件,在这里介绍是为了对比
sprintf
函数原型如下:
int sprintf( char *buffer, const char *format, ... );
sprintf
函数的功能:将结果写入字符串buffer
, 如果所写入的字符串(加上终止空字符)超出由buffer
所指向的数组的大小,则行为未定义。
运行下面的代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
char str[100] = { 0 };
struct S s = { "xiaomei", 24, 55.2 };
sprintf(str, "%s %d %.1lf", s.name, s.age, s.weight);
printf("%s\n", str);
return 0;
}
sprintf
其实是将格式化的数据转化为字符串
4.1.8 sscanf(操作的不是文件)
注意:函数
sscanf
操作的不是文件,在这里介绍是为了对比
sscanf
函数的原型如下:
int sscanf( const char *buffer, const char *format, ... );
sscanf
函数的功能是从字符数组中提取数据,然后格式化
运行下面的代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
char str[100] = { 0 };
struct S s = { "xiaomei", 24, 55.2 };
//将结构体变量s中格式化的数据转化为字符串存入字符数组str中
sprintf(str, "%s %d %.1lf", s.name, s.age, s.weight);
//printf("%s\n", str);
//临时变量
struct S tmp = { 0 };
//将字符数组str中的数据格式化的存入结构体变量tmp中
sscanf(str, "%s%d%lf", tmp.name, &tmp.age, &tmp.weight);
fprintf(stdout, "%s %d %.1lf", tmp.name, tmp.age, tmp.weight);
return 0;
}
scanf
/printf
:针对标准输入流 / 标准输出流的格式化输入 / 输出函数fscanf
/fprintf
:针对所有输入流 / 所有输出流的格式化输入 / 输出函数sscanf
/sprintf
:将格式化的数据转换为字符串 / 将字符串转化为格式化的数据
4.1.9 fwrite
fwrite
函数原型如下:
size_t fwrite( const void *buffer, size_t size, size_t count,FILE *stream );
fwrite
函数的参数:
buffer
:指向数组中要被写入的首个对象的指针size
:每个对象的大小count
:要被写入的对象数stream
:指向输出流的指针
fwrite
函数的返回值:成功写入的对象数,若错误发生则可能小于count
fwrite
函数的作用:将buffer
指向空间内count
个大小为size
的元素数据写到输出流stream
(文件)中
运行下面的代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
struct S s = { "xiaochou", 23, 65.7 };
//打开文件
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//以二进制的形式写到文件中
fwrite(&s, sizeof(struct S), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行成功后文档内是我们看不懂的二进制的信息
4.1.10 fread
fread
函数的原型如下:
size_t fread( void *buffer, size_t size, size_t count,FILE *stream );
fread
函数的参数:
buffer
:指向要读取的数组中首个对象的指针size
:每个对象的字节大小count
:要读取的对象数stream
:读取来源的输入文件流
fread
函数的返回值:成功读取的对象数,若出现错误或文件尾条件,则可能小于count
fread
函数的作用:从输入流stream
(文件)中读取count
个大小为size
个字节的数据存到buffer
指向的空间内
运行下面的代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
struct S s = { 0 };
//打开文件
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//读取二进制的信息到文件中
fread(&s, sizeof(struct S), 1, pf);
//打印结构s的成员
fprintf(stdout, "%s\n%d\n%.1lf\n", s.name, s.age, s.weight);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
可以看到fread
函数又将二进制的信息读文件中变成了我们可以看懂的信息
五、文件结束的判定
5.1 被错误使用的feof
文件读取结束有两个原因:
-
- 遇到文件结尾
-
- 遇到错误
feof
函数的原型如下:
int feof( FILE *stream );
feof
函数的返回值:若已抵达流尾则为非零值,否则为 0
feof
函数的作用是:当文件已经读取结束的时候,判断读取结束的原因是否是遇到文件结尾
但是这个函数经常被用错,部分人以为feof
函数的作用是判断文件读取是否结束,其实不是的
5.2 文本文件读取结束
文本文件读取是否结束,判断返回值:
fgetc
:判断是否为EOF
fgets
:判断是否为NULL
例如:
#include <stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = 0;//注意:为处理 EOF 需要 int 而非 char
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
//判断是什么原因结束的
if (ferror(pf))
{
puts("I/O error when reading");
}
else if (feof(pf))
{
puts("End of file reached successfully");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
5.3 二进制文件读取结束
二进制文件读取是否结束,判断返回值是否小于实际要读的个数。
例如:
#include <stdio.h>
struct S
{
char name[20];
int age;
double weight;
};
int main()
{
struct S s = { 0 };
//打开文件
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取二进制的信息到文件中
size_t num = fread(&s, sizeof(struct S), 1, pf);
if (num == 1)
{
puts("Array read successfully, contents:");
}
else if (feof(pf))
{
printf("Error reading test.bin: unexpected end of file\n");
}
else if (ferror(pf))
{
perror("Error reading test.bin");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
六、文件缓冲区
ANSIC标准采用“缓冲文件系统”处理数据文件的,所谓缓冲文件系统是指系统自动的在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区,充满缓冲区后再逐个地将数据送到程序数据区(程序变量等),缓冲区的大小根据C编译系统决定。
总结
- 文件读写函数在编程中具有非常重要的作用,能够帮助程序员实现数据的持久化存储、数据交换、日志记录、配置文件处理等功能,提高程序的灵活性、可维护性和可扩展性,从而提升整个程序的质量和效率