目录
文件状态指针
文件流
文件的顺序读写
fgetc
fputc
fgets
fputs
fscanf
fprintf
fread
fwrite
sscanf
sprintf
文件的随机读写
fseek
ftell
rewind
文件结束的判定
feof
ferror
今天接下来我们讲解文件读写函数。
流是一个高度抽象的概念!!在我们写程序的时候,我们需要将数据传到屏幕,存在硬盘上,传到网络上,U盘光盘等外部设备。不同的外部设备的读写方式不同,传输方式也不同。 有人觉得程序员也太难了,于是抽象化了流的概念。我们先将数据统一传输到流里面(文件流等),然后再传输到各个外部设备。
我们在写文件的时候,需要打开文件,关闭文件。我们用scanf从键盘上读取数据,printf向屏幕上打印数据,直接操作了为什么没有打开标准输入输出流呢?
那是因为C语言程序运行起来,就默认打开了3个流。
用打开标准输出流的方式打印26个字母
#include
int main()
{
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
if (ch % 5 == 0)
fputc('\n', stdout);//标准输出流
fputc(ch, stdout);
}
//a-97
//b-98
//c-99
//100换行了
return 0;
}
用打开文件流的方式打印26个字母。------>下面fputc
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
if (ch % 5 == 0)
fputc('\n', pf);//文件流
fputc(ch, pf);
}
//a-97
//b-98
//c-99
//100换行了
fclose(pf);
pf = NULL;
return 0;
}
记住:站在内存数据的角度去理解!! 其次文件要用输入输出函数去输入输出操作,也可以动键盘修改文本文件!!
功能 函数名 适用于
字符输入函数 fgetc 所有输入流
字符输出函数 fputc 所有输出流
文本行输入函数 fgets 所有输入流
文本行输出函数 fputs 所有输出流
格式化输入函数 fscanf 所有输入流
格式化输出函数 fprintf 所有输出流
二进制输入 fread 文件
二进制输出 fwrite 文件
我们接下来详细的介绍以上各个函数。函数头文件 参数 返回值 使用去介绍
fgetc - C++ Reference (cplusplus.com)
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fclose(pf);
pf = NULL;
return 0;
}
fputc - C++ Reference (cplusplus.com)
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputc('a', pf);//文件流
fputc('b', pf);
fputc('c', pf);
fputc('d', pf);
fclose(pf);
pf = NULL;
return 0;
}
请在文件中输入26个英文字母。
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char ch = 0;
for (ch='a'; ch <= 'z'; ch++)
fputc(ch, pf);
fclose(pf);
pf = NULL;
return 0;
}
//换行
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
if (ch % 5 == 0)
fputc('\n', pf);//文件流
fputc(ch, pf);
}
//a-97
//b-98
//c-99
//100换行了
fclose(pf);
pf = NULL;
return 0;
}
fgets - C++ Reference (cplusplus.com)
从流中获取字符串从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。
换行符使 fgets 停止读取,但它被函数视为有效字符,并包含在复制到 str 的字符串中。
终止空字符会自动附加到复制到 str 的字符之后
只会读num-1个字符
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char str[10] = { 0 };
int ret=fgets(str,7,pf);//实际上只会读6个字符
if (ret == EOF)
{
perror("fgets");
}
else
printf("%s", str);
fclose(pf);
pf = NULL;
return 0;
}
读到\n停止不读了
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char str[100] = { 0 };
int ret=fgets(str,12,pf);//实际上只会读6个字符
if (ret == EOF)
{
perror("fgets");
}
else
printf("%s", str);
fclose(pf);
pf = NULL;
return 0;
}
fputs - C++ Reference (cplusplus.com)
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char str[] = "abcdef";
fputs(str,pf);
fputs("abcdef", pf);
//两种写法
fclose(pf);
pf = NULL;
return 0;
}
下面两个函数用结构体去举例子 fscanf是输入 fpirntf是输出,先看下对比。
fscanf - C++ Reference (cplusplus.com)
#include
struct S
{
char c;
int i;
float a;
};
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//输入
//struct S s = { 't',7,3.14 };
//fprintf(pf,"%c %d %f",s.c,s.i,s.a);
//格式必须一摸一样
//输出
struct S s = {0};
fscanf(pf, "%c %d %f", &(s.c), &(s.i), &(s.a));
printf("%c %d %f", s.c, s.i, s.a);
fclose(pf);
pf = NULL;
return 0;
}
fprintf - C++ Reference (cplusplus.com)
#include
struct S
{
char c;
int i;
float a;
};
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
struct S s = { 't',7,3.14 };
fprintf(pf,"%c %d %f",s.c,s.i,s.a);
fclose(pf);
pf = NULL;
return 0;
}
前面的函数都是针对于文本文件的,下面这组函数针对的是二进制文件。
fread - C++ Reference (cplusplus.com)
从流中读取数据块从流中读取计数元素数组,每个元素的大小为字节,并将它们存储在 ptr 指定的内存块中。
流的位置指示器按读取的总字节数前进。
如果成功,则读取的总字节数为(大小*计数)。
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int arr[20] = { 0};//10个整型40个字节
fread(arr, 4, 10, pf);
int i = 0;
for (i = 0; i < 10;i++)
{
printf("%d ", arr[i]);
}
fclose(pf);
pf = NULL;
return 0;
}
fwrite - C++ Reference (cplusplus.com)
写入要流式传输的数据块将计数元素数组(每个元素的大小为字节)从 ptr 指向的内存块写入流中的当前位置。
流的位置指示器按写入的总字节数前进。
在内部,该函数解释所指向的块,就好像它是一个类型的元素数组,并按顺序写入它们,就好像为每个字节调用一样。
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//10个整型40个字节
fwrite(arr, 4, 10, pf);
fclose(pf);
pf = NULL;
return 0;
}
前面我们已经学习过了scanf&printf 和 fscanf&fprintf 我们再来学习一下sscanf&sprintf
sscanf - C++ Reference (cplusplus.com)
//需要结构体
//sprintf和sscanf配合使用
//两个才能达到效果
#include
struct S
{
int a;
float b ;
char c;
};
int main()
{
struct S s = { 7,3.14,'t' };
char str[100] = { 0 };
sprintf(str, "%d_%f_%c", s.a, s.b, s.c);
printf("%s\n", str);
struct S tmp = { 0 };
sscanf(str, "%d_%f_%c", &(tmp.a), &(tmp.b), &(tmp.c));
printf("%d\n", tmp.a);
printf("%f\n", tmp.b);
printf("%c\n", tmp.c);
return 0;
}
sprintf - C++ Reference (cplusplus.com)
#include
int main()
{
int a = 7;
float b = 3.14;
char c = 't';
char str[100] = { 0 };
sprintf(str, "%d_%f_%c", a, b, c);
printf("%s", str);
return 0;
}
最后我们来对比一下底下几组函数
fseek - C++ Reference (cplusplus.com)
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
fseek(pf, -4, SEEK_CUR);//移动光标的作用
fseek(pf, 0, SEEK_SET);
fseek(pf, -4, SEEK_END);
ch = fgetc(pf);
printf("%c", ch);
close(pf);
pf = NULL;
return 0;
}
特别提醒:如果我们想熟练掌握这个函数运用,我们必须对我们要读取的文件格式内容了如指掌
ftell - C++ Reference (cplusplus.com)
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
long int ret=ftell(pf);
printf("%d", ret);
close(pf);
pf = NULL;
return 0;
}
rewind - C++ Reference (cplusplus.com)
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
rewind(pf);
ch = fgetc(pf);
printf("%c", ch);
close(pf);
pf = NULL;
return 0;
}
特别提醒:以上函数移动的都是文件状态指针,和指向文件信息区的指针没有关系(它没动!)
被错误使用的feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
feof - C++ Reference (cplusplus.com)
A non-zero value is returned in the case that the end-of-file indicator associated with the stream is set.Otherwise, zero is returned.
ferror - C++ Reference (cplusplus.com)
A non-zero value is returned in the case that the error indicator associated with the stream is set.Otherwise, zero is returned.
练习1:将data1.txt的内容拷贝到data2.txt上面去。
#include
int main()
{
FILE* pfread = fopen("data1.txt", "r");
if (pfread == NULL)
{
perror("fopen");
return 1;
}
FILE* pfwrite = fopen("data2.txt", "w");
if (pfwrite == NULL)
{
perror("fopen");
fclose(pfread);
pfread = NULL;
return 1;
}
int ch = 0;
while( (ch = fgetc(pfread)) != EOF)
{
fputc(ch, pfwrite);
}
if (ferror(pfread))
puts("I/O error when reading");
else if (feof(pfread))
puts("End of file reached successfully");
if (ferror(pfwrite))
puts("I/O error when reading");
else if (feof(pfwrite))
puts("End of file reached successfully");
fclose(pfread);
pfread = NULL;
fclose(pfwrite);
pfwrite = NULL;
return 0;
}
练习2:二进制文件
#include
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = {1.0,2.0,3.0,4.0,5.0};
double b = 0.0;
size_t ret_code = 0;
FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组
fclose(fp);
fp = fopen("test.bin","rb");
// 读 double 的数组
while((ret_code = fread(&b, sizeof(double), 1, fp))>=1)
{
printf("%lf\n",b);
}
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
fclose(fp);
fp = NULL;
}
✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!
希望大家继续坚持在每天敲代码的路上。其实,没有人会一直带着你往前走,你自己一定要成为自己的救赎。
代码---------→【唐棣棣 (TSQXG) - Gitee.com】
联系---------→【邮箱:[email protected]】