前言:
在 【C语言】—— 文件操纵(上) 一文中,我们对文件有了一个简朴的了解,并学会了怎样打开和关闭文件,下面就让我们一起来学学怎样对文件进行读写吧。
五、文件的次序读写
5.1、 次序读写函数介绍
函数名功能实用于fgetc字符输入函数所有输入流fputc字符输出函数所有输出流fgets文本行输入函数所有输入流fputs文本行输出函数所有输出流fscanf格式化输入函数所有输入流fprintf格式化输出函数所有输出流fread二进制输入文件fwrite二进制输出文件 注:上面说的实用于所有输入流一般指实用于标准输入流和其他输入流(如文件输入流);所有输出流一般指实用于标准输出流和其他输出流(如文件输出流)
下面我们对上述函数逐一进行介绍
5.2、 f p u t c fputc fputc 函数
- 函数功能:将一个字符写入流中,这个流其实就是文件流
- 函数参数:
- i n t int int c h a r a c t e r character character:要写入的字符,字符的本质就是 A S C I I ASCII ASCII 码值,因此这里参数类型为 i n t int int 没有问题
- F I L E FILE FILE * s t r e a m stream stream :指向要写入文件的文件指针
- 返回类型:返回类型是 int:当写入乐成,返回写入的值,当写入失败,返回EOF(-1)
函数使用:
- #include<stdio.h>
- int main()
- {
- FILE* pf = NULL;
- //打开文件
- pf = fopen("test.txt", "w");
- //文件操作
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- //写文件
- fputc('a', pf);
- fputc('b', pf);
- fputc('c', pf);
- //写入26个字母
- char ch = 0;
- for (ch = 'a'; ch <= 'z'; ch++)
- {
- fputc(ch, pf);
- }
- //关闭文件
- fclose(pf);
- pf = NULL;
- }
复制代码
这样,字符就写好了。
当写入字符时,还有一些细节需要注意:当一个文件打开时,最开始其实是有一个光标指向第一个位置,每当用 f p u t c fputc fputc 函数写入一个字符,光标则退却一格。光标是用来维护此时如今我们这个文件写到哪的,而且是按照一定的次序往后走的,因此叫做次序读写
5.3、 f g e t c fgetc fgetc 函数
- 函数功能:从流(文件)中获取一个字符
- 返回值:返回类型为 i n t int int 。假如乐成,就会将读到的字符返回,假如读取失败大概遇到文件末尾返回EOF(-1)
- 为什么返回类型是 int 呢?正是因为它会返回两种类型的值:字符的 A S C I I ASCII ASCII码 值和 EOF;假如返回类型为 char,则 EOF 无法返回
函数使用:
- #include<stdio.h>
- int main()
- {
- FILE* pf = NULL;
- //打开文件
- pf = fopen("test.txt", "r");
- //文件操作
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- //读文件
- int ch = 0;
- while ((ch = fgetc(pf)) != EOF)
- {
- printf("%c ", ch);
- }
- printf("\n");
- //关闭文件
- fclose(pf);
- pf = NULL;
- }
复制代码 运行结果:
而同样,以只读的方式打开文件,刚开始光标是在第一个位置,即指向 a a a,每读一个字符,光标向退却一位。
5.4、 f p u t s fputs fputs 函数
- 函数功能:将 str 字符串写入文件流中,直至遇到 ‘\0’ 制止(‘\0’不会被写入)
注:多次调用该函数,并不会实现自动换行,要想换行应自动输入‘\n’
函数使用:
- #include<stdio.h>
- int main()
- {
- //打开文件
- FILE* pf = NULL;
- pf = fopen("test.txt", "w");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- //写文件
- fputs("hello", pf);
- fputs("world\n", pf);
- fputs("hello csdn\n", pf);
- //关闭文件
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码 运行结果:
我们可以看到,加了换行符后,文件的光标是直接落到下一行的。
5.5、 f g e t s fgets fgets 函数
- 函数功能:从流中最多读取 num 个字符,并放在 str 所指向的空间中
- 函数读 n u m num num 个字符,但是最多只能读取 n u m num num - 1个,因为最后一个位置函数会自己加上 ‘\0’
- 该函数不会换行读取。当 n u m num num 大于字符数时,遇到换行符 ‘\n’,将 ‘\n’ 读取后,不再往下读取,自己加上 ‘\0’ 后制止。
- 当函数读取乐成,返回的是目的空间的地点;读取失败则返回空指针(NULL)
函数使用:
- #include<stdio.h>
- int main()
- {
- //打开文件
- FILE* pf = NULL;
- pf = fopen("test.txt", "r");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- //读文件
- char arr1[10] = "xxxxxxxxx";
- fgets(arr1, 8, pf);
- char arr2[10] = "xxxxxxxxx";
- fgets(arr2, 8, pf);
- //关闭文件
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码 运行结果:
5.6、 f p r i n t f fprintf fprintf 函数
该函数的功能是将数据以格式化的形式写入流中(以文本的形式)
其实, f p r i n t f fprintf fprintf 函数和 p r i n t f printf printf 函数是非常相像的,让我们来对比一下
他们的区别仅仅是第一个参数的有无而已,其他都是千篇一律的,所以你会用 p r i n t f printf printf你就会用 f p r i n t f fprintf fprintf
多的一个参数是什么呢?是文件流,你需要将数据输出到的那个文件流
- #include<stdio.h>
- struct S
- {
- char name[20];
- int age;
- float score;
- };
- int main()
- {
- struct S s = { "张三", 20, 75.5f };
- //打开文件
- FILE* pf = NULL;
- pf = fopen("test.txt", "w");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- //写文件
- fprintf(pf, "%s %d %f", s.name, s.age, s.score);
- //关闭文件
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码 运行结果:
5.7、 f s c a n f fscanf fscanf 函数
该函数的功能是从文件流中读取格式化的数据。
不难发现, f s a n f fsanf fsanf 与 s c a n f scanf scanf 函数很像,我们来对比一下
同 f p r i n t f fprintf fprintf 一样, f s c a n f fscanf fscanf 与 s c a n f scanf scanf 只是相差一个参数而已,你会用 s c a n f scanf scanf 自然也就会用 f s c a n f fscanf fscanf 函数,第一个参数即是你所要读取的文件流。
- #include<stdio.h>
- struct S
- {
- char name[20];
- int age;
- float score;
- };
- int main()
- {
- struct S s = { 0 };
- //打开文件
- FILE* pf = NULL;
- pf = fopen("test.txt", "r");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- //读文件
- fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
- //printf("%s %d %.2f\n", s.name, s.age, s.score);
- fprintf(stdout, "%s %d %.2f\n", s.name, s.age, s.score);
-
- //关闭文件
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码
运行结果:
注意看,上述代码用了 f p r i n t f fprintf fprintf 来将数据打印在屏幕上
还记得最开始的表格中, f p r i n t f fprintf fprintf 最后一列写的是所有输出流吗?这所有输出流就包括了文件流和标准输出流,既然 f p r i n t f fprintf fprintf 可以输出到文件中,那么自然也就可以输出到屏幕中,完成 p r i n t f printf printf 一样的功能。
而同理,前面讲的 f p u t c fputc fputc、 f g e t s fgets fgets、 f s c a n f fscanf fscanf 等函数也可以从标准输入(输出)流中获取(输出)数据。
5.8、 p r i n t f / f p r i n t f / s p r i n t f printf/fprintf/sprintf printf/fprintf/sprintf 函数对比
通过我们前面的学习,我们已经知道了 p r i n t f printf printf 和 f p r i n t f fprintf fprintf 函数的作用:
- p r i n t f printf printf:把数据以格式化的形式打印在标准输出流上
- f p r i n t f fprintf fprintf : 把数据以格式化的形式打印在 指定的输出流 上
那么 s p r i n t f sprintf sprintf 函数又是作什么的呢?我们一起来看看
该函数的作用是:将数据以格式化的形式写到字符串上。其实就是把格式化的数据转换成字符串
- #include<stdio.h>
- struct S
- {
- char name[20];
- int age;
- float score;
- };
- int main()
- {
- struct S s = { "张三", 20, 75.5f };
- char buf[50] = { 0 };
- sprintf(buf, "%s %d %f", s.name, s.age, s.score);
- printf("%s\n", buf);
- return 0;
- }
复制代码 运行结果:
该代码完全是以 %s 的形式打印的,说明数据已经完全转换成字符串了。
5.9、 s c a n f / f s c a n f / s s c a n f scanf/fscanf/sscanf scanf/fscanf/sscanf 函数对比
同样,通过我们前面的学习,我们已经知道了 s c a n f scanf scanf 和 f s c a n f fscanf fscanf 函数的作用:
- s c a n f scanf scanf:从 标准输入流 中读取格式化的数据
- f s c a n f fscanf fscanf:从 指定输入流 中读取格式化的数据
那 s s c a n f sscanf sscanf 的功能又是什么呢?学习了 s p r i n t f sprintf sprintf ,我们猜测,其应该是从字符串中读取格式化数据,是不是呢?我们一起来看看
函数功能:从字符串中读取格式化数据
- #include<stdio.h>
- struct S
- {
- char name[20];
- int age;
- float score;
- };
- int main()
- {
- struct S s = { "张三", 20, 75.5f };
- char buf[50] = { 0 };
- sprintf(buf, "%s %d %f", s.name, s.age, s.score);
-
- struct S a = { 0 };
- sscanf(buf, "%s %d %f", s.name, &(s.age), &(s.score));
- printf("%s %d %f\n", s.name, s.age, s.score);
- return 0;
- }
复制代码 运行结果:
5.10、 f w r i t e fwrite fwrite 函数
- 函数功能:以二进制的形式将内存块中的数据写入文件中
- 参数介绍:
- c o n s t const const v o i d void void * p t r ptr ptr: p t r ptr ptr 是指向要写入数据的数组的指针
- s i z e size size_ t t t s i z e size size:表现要写入的每个元素的大小
- s i z e size size_ t t t c o u n t count count:表现要写入元素的个数
下面我们直接上代码:
- #include<stdio.h>
- int main()
- {
- int arr[] = { 1,2,3,4,5 };
- int sz = sizeof(arr) / sizeof(arr[0]);
- FILE* pf = NULL;
- pf = fopen("test.txt", "wb");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- fwrite(arr, sizeof(arr[0]), sz, pf);
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码
我们以二进制的方式打开:
5.11、 f r e a d fread fread 函数
该函数的作用是:以二进制的形式读取数据到内存中
我们可以看到,这函数的参数与 f w r i t e fwrite fwrite 是大同小异的,这里就不逐一介绍了,我们直接上代码
- #include<stdio.H>
- int main()
- {
- int arr[5] = { 0 };
- FILE* pf = NULL;
- pf = fopen("test.txt", "rb");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- fread(arr, sizeof(arr[0]), 5, pf);
- for (int i = 0; i < 5; i++)
- {
- printf("%d ", arr[i]);
- }
- printf("\n");
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码 运行结果:
但是,上面代码是我提前知道了统共的数据个数,当我不知道数据具体个数是又该怎么办呢?
这里,我们需要知道 f r e a d fread fread 函数的返回值,该函数的返回值是读取到的数据的个数。这时,当我要求读 7 个数据,而返回值是 5 时,说明数据读完了。
上面的代码我们可以做如下修改:
- #include<stdio.h>
- int main()
- {
- int arr[5] = { 0 };
- FILE* pf = NULL;
- pf = fopen("test.txt", "rb");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- int i = 0;
- while (fread(arr + i, sizeof(arr[0]), 1, pf))
- {
- printf("%d ", arr[i]);
- i++;
- }
- printf("\n");
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码
六、文件的随机读写
前面我们所学习到的函数都是次序读写,光标是依次往后移动。那能不能做到随机读写呢,即我想在哪里读写就在哪读写,指那打那。
当然是可以的,下面让我们一起来学习。
6.1、 f s e e k fseek fseek 函数
- 功能:根据文件指针的位置和偏移量来定位文件指针(光标)。
- 参数介绍:
- l o n g long long i n t int int o f f s e t offset offset:相对于起始位置的偏移量,可正可负
- i n t int int o r i g i n origin origin:起始位置
起始位置选择:
常量所指位置SEEK_SET文件的起始位置SEEK_CUR当前光标位置SEEK_END文件末端
这个函数有什么用呢?好比文件中有 a b c d e f g abcdefg abcdefg 的数据,当前光标指向 a a a,而我想直接读 e e e,这时就可以用该函数移动光标啦。
例子:
- #inclu<stdio.h>
- int main()
- {
- FILE* pf = NULL;
- pf = fopen("test.txt", "r");
- if (NULL == pf)
- {
- perror("fopen fail");
- return 1;
- }
- char ch = 0;
- ch = fgetc(pf);
- printf("%c\n", ch);
- fseek(pf, 3, SEEK_CUR);
- ch = fgetc(pf);
- printf("%c\n", ch);
- fclose(pf);
- pf = NULL;
- return 0;
- }
复制代码 运行结果:
6.2、 f t e l l ftell ftell 函数
f t e l l ftell ftell 函数会返回文件指针(光标) 相对于文件起始位置的 偏移量
这里,我们想:假如我们让光标读到文件末尾,在返回偏移量,是不是就能知道文件的长度呢?答案是肯定的。
例子:
- #includ<stdio.h>
- int main()
- {
- FILE* pf;
- long size;
- pf = fopen("test.txt", "rb");
- if (NULL == pf)
- perror("Error opening file");
-
- fseek(pf, 0, SEEK_END); // non-portable
- size = ftell(pf);
- fclose(pf);
- printf("Size of test.txt: %ld bytes.\n", size);
-
- return 0;
- }
复制代码
6.3、 r e w i n d rewind rewind 函数
r e w i n d rewind rewind 函数可以让文件指针回到起始位置
走的太远,别忘了回头路
例子:
- #include<stdio.h>
- int main()
- {
- int n;
- FILE* pf;
- char buffer[27];
- pf = fopen("test.txt", "w+");
- for (n = 'A'; n <= 'Z'; n++)
- {
- fputc(n, pf);
- }
- rewind(pf);
- fread(buffer, 1, 26, pf);
- fclose(pf);
- buffer[26] = '\0';
- printf(buffer);
- return 0;
- }
复制代码
七、文件读取结束的判断
7.1、 被错误使用的 f e o f feof feof
很多人都以为 f e o f feof feof函数是用来直接判断文件读取是否结束。其实这是大错特错的
f e o f feof feof 的作用是:当文件读取结束时,判断读取结束的原因是否是因为:遇到文件末尾结束。
如今假设文件读取结束了,但是是什么原因读取结束的呢?
f e o f feof feof 函数是判断是否是因为遇到文件末尾而结束的。
而还有个函数叫 f e r r o r ferror ferror 是用来判断是否是因为遇到错误而读取结束的
其实在我们打开一个流时,会有两个标记值
当读文件的过程中确实是遇到文件末尾了,就会将第一个值标记;遇到错误就会将第二个值标记。
f e o f feof feof是用来检测第一个标记的; f e r r o r ferror ferror是用来检测第二个标记的
f e o f feof feof 函数:当文件确实是因为读取到文件末尾而结束时,返回一个非零值,反之返回 0
7.2、怎样判断文件读取结束
那么怎样来判断文件是否读取结束呢?其实在前面结束各个函数时已经顺便介绍了:通过函数的返回值进行判断!
(1)文本文件判断
函数名正常读取返回值读取结束或遇到错误的返回值fgetc返回读取到的字符的ASCII码值EOFfgets返回目的空间的地点NULL
(2)二进制文件判断
二进制文件用 f r e a d fread fread 进行读取, f r e a d fread fread 返回值是其读取到的个数。当其返回值小于实际要读取的个数时,表现文件读取结束
八、 文件缓冲区
我们想一个问题:当我们想往文件中存 26 个字母,这 26 个字母是直接从程序(内存)中存到文件(硬盘)中的吗?
其实不是的。
ANSI C 标准采用 “缓冲文件体系” 处理的数据文件的,所谓缓冲文件体系指的是体系自动在内存中为程序中为每一个正在使用的文件开发一块“文件缓冲区”。
从内存向磁盘输出数据会先送达内存中的缓冲区,装满缓冲区或自动刷新缓冲区才将数据送到磁盘上。
假如从磁盘向盘算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区或刷新缓冲区),然后在从缓冲区逐个地将数据送到程序数据区(程序变量等)。
缓冲区的大小根据C编译体系决定的。
那为什么要有文件缓冲区呢?
其实,当我们向文件中输入输出数据时,相关函数会调用操纵体系相关接口;这时假如写一个字符就调用一次操纵体系,是不是效率太低,并且我们的操纵体系上面可不止跑着一个程序,这时你一直打扰操纵体系,操纵体系就没法干活了。
应用缓冲区,在缓冲区攒够一定数据再一次性全部录进,效率就会提升很多。
下面,我们通过一段代码验证缓冲区的存在:
- #include<stdio.h>
- #include <windows.h>
- //VS2019 WIN11环境测试
- int main()
- {
- FILE* pf = fopen("test.txt", "w");
- fputs("abcdef", pf);//先将代码放在输出缓冲区
- printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
- Sleep(10000);
- printf("刷新缓冲区\n");
- fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
- //注:fflush 在⾼版本的VS上不能使⽤了
- printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
- Sleep(10000);
- fclose(pf);
- //注:fclose在关闭⽂件的时候,也会刷新缓冲区
- pf = NULL;
- return 0;
- }
复制代码 这里我们需要注意:
因为有文件冲区的存在,C语言在操纵文件的时候,需要做刷新缓冲区大概在文件操纵结束的时候关闭文件。
假如不做,可能导致读写文件的问题
好啦,本期关于文件操纵的知识就介绍到这里啦,盼望本期博客能对你有所帮助。同时,假如有错误的地方请多多指正,让我们在C语言的学习路上一起进步!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |