目录
感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接
🐒🐒🐒 个人主页
🥸🥸🥸 C语言
🐿️🐿️🐿️ C语言例题
🐣🐓🏀 python
文件的顺序读写
顺序读写函数介绍
fputc
fputc的详细介绍
函数格式 int fputc ( int character, FILE * stream );
最开始打开文件时,文件里什么都没有,但是文件的指针是指向这个文件的启始位置(这个指针是一个状态指针,不是pf,具体为什么我也不知道,可能pf只是为了找到这个文件的位置,然后再通过这个状态指针将字符输出)
之后fputc将字符’j’输出进文件,然后文件指针会跳到下一个,然后再分别将字符’a’‘c’'k’输出(注意这里的pf是不变的)
最后再将文件关闭保存
我们将26个字符输出,注意这里ch已经是字符了,所有不用加’ ’
通过fputc也是可以将字符输出在屏幕上的,因为pf是FILE类型的指针,而在文件操作(上)中,有提到stdout标准输出流,是输出至显示器界面的,并且也是FILE类型的指针
fgetc
fgetc的详细介绍
函数格式 int fgetc ( FILE * stream );
当我们需要读取文件然后将读取的内容输出到屏幕上时
注意这里的ch是int类型的,fgetc返回的是读取字符的ASCLL码
当我们删除一行ch = fgetc(pf)时,我们再来看看结果
结果是abcc,因为我们少了一行ch = fgetc(pf),导致状态指针没有向后移动,所以最后才会出现两个c
如果我们需要将文件所以的内容输出到屏幕上,我们只需要这样操作
fputs
fputs的详细介绍
函数格式 int fputs ( const char * str, FILE * stream );
现在我们用fputs将字符串写入文件中
我们也可以将字符串放入数组中,然后将数组内容写入文件
fgets
fgets的详细介绍
函数格式 char * fgets ( char * str, int num, FILE * stream );
我们再用fgets去读写刚刚写的文件
通过调试可以看到’\0’和’\n’也是被读取进去的,我们发现当读取的时候遇到’\n’时后面就全是’\0’,所以我们就得出,当遇到’\n’时就不会再往后读取数据了
而如果我们第一行读不完,比如我们只读取3个字符的话,那么就应该这样写
而如果我们需要读取第二行的字符的话,我们可以往后继续加fgets函数
但是如果我们第一行没有读完就想用上面的方法读第二行的话会有些问题的
所有我们还需要再写一次fgets函数
fprintf
fprintf的详细介绍
函数格式 int fprintf ( FILE * stream, const char * format, … );
fprintf和printf是很相似的
比如我们现在需要将下面的结构体打印在屏幕上
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = { 3.14f,'w',100 };
FILE* pf = fopen("jack.txt","w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
printf("%f,%c,%d",s.f,s.c,s.n);
fclose(pf);
pf = NULL;
return 0;
}
而fprintf的话我们只需要稍微加一点
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = { 3.14f,'w',100 };
FILE* pf = fopen("jack.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf,"%f,%c,%d", s.f, s.c, s.n);
fclose(pf);
pf = NULL;
return 0;
}
这样我们就可以将数据写入文件了
另外,fprintf(pf,“%f,%c,%d”, s.f, s.c, s.n)中%f %c %d后面的逗号都会写入文件中,如果我们将逗号换成 - 也是会被写入文件的
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = { 3.14f,'w',100 };
FILE* pf = fopen("jack.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf,"%f-%c-%d", s.f, s.c, s.n);
fclose(pf);
pf = NULL;
return 0;
}
fscanf
fscanf的详细介绍
函数格式 int fscanf ( FILE * stream, const char * format, … );
fscanf和scanf也是类似的
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = {0};
FILE* pf = fopen("jack.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
scanf("%f-%c-%d", &(s.f), &(s.c), &(s.n));
fclose(pf);
pf = NULL;
return 0;
}
fscanf去读取文件的数据应该这样写
struct S
{
float f;
char c;
int n;
};
int main()
{
struct S s = {0};
FILE* pf = fopen("jack.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf,"%f-%c-%d", &(s.f), &(s.c), &(s.n));
printf("%f-%c-%d\n", s.f, s.c, s.n);
fclose(pf);
pf = NULL;
return 0;
}
fwrite
fwrite的详细介绍
函数格式 size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
举个例子:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
FILE*pf=fopen("jack.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
fclose(pf);
pf = NULL;
return 0;
}
我们需要将数据的来源也就是arr拷贝到流里面去,所有我们需要传入数组的指针,以及数组每个元素的大小,需要拷贝多少个元素,以及文件指针流
这时候我们打开文件发现,文件里面的东西我们根本就看不懂,其实是因为这是二进制的输出
为了能够看懂文件里面的东西,我们就需要用下面的fread去读文件的内容
fread
fread的详细介绍
函数格式 size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
fread和fwrite的函数格式是一样的,因为fread是要从流里面读取然后放到数组arr中,每个元素的大小和读取的格式也是必须要知道的,所以他们的格式是一样的
int main()
{
int arr[10] = { 0 };
FILE*pf=fopen("jack.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(arr, sizeof(arr[0]), sizeof(arr) / sizeof(arr[0]), pf);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
fclose(pf);
pf = NULL;
return 0;
}
需要注意的是上一个是用wb,这里是用的rb
文件的随机读写
fseek
fseek的详细介绍
函数格式 int fseek ( FILE * stream, long int offset, int origin );
其中 long int offset是偏移量 int origin是起始位置
起始位置有三种情况
SEEK_SET是最开始的位置
SEEK_CUR是当前位置
SEEK_END是结束位置
为了理解我们举个例子
int main()
{
FILE* pf = fopen("jack1.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
ch = fgetc(pf);
printf("%c\n", ch);//d
ch = fgetc(pf);
printf("%c\n", ch);//e
ch = fgetc(pf);
printf("%c\n", ch);//f
ch = fgetc(pf);
printf("%c\n", ch);//g
fclose(pf);
pf = NULL;
return 0;
}
当我们再读一次的话结果就会打印i,但是如果现在需要你读取的指针再一次指向a,这时候我们就需要用到fseek
int main()
{
FILE* pf = fopen("jack1.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
ch = fgetc(pf);
printf("%c\n", ch);//c
ch = fgetc(pf);
printf("%c\n", ch);//d
ch = fgetc(pf);
printf("%c\n", ch);//e
ch = fgetc(pf);
printf("%c\n", ch);//f
ch = fgetc(pf);
printf("%c\n", ch);//g
fseek(pf,-7,SEEK_CUR);
fclose(pf);
pf = NULL;
return 0;
}
因为a距离h差7,所以我们需要让文件指针偏移-7
我们也可以将fseek(pf,-7,SEEK_CUR)改成fseek(pf,0,SEEK_SET),这样的结果也是一样的
ftell
ftell的详细介绍
函数格式 long int ftell ( FILE * stream );
ftell是返回文件指针相对于起始位置的偏移量
以上面fseek的例子
rewind
rewind的详细介绍
函数格式 void rewind ( FILE * stream );
rewind是让当前指针位置回到起始
位置
文件读取结束的判定
在我们读取文件时我们不知道文件什么时候才会读取结束
被错误使用的 feof
feof
feof的详细介绍
函数格式int feof ( FILE * stream );
注意:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束
而是应用于当文件读取结束的时候,判断是读取失败结束的原因是否是遇到文件尾结束
文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
fgetc 判断是否为 EOF .(读取正常会返回一个字符的ASCLL码值,EOF就是读取结束)
fgets 判断返回值是否为 NULL .(读取成功会返回读取字符串的起始位置,失败会返回空指针)
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数
fread 要求读取count个大学为size字节的数据
如果真读取到count过数据,那就返回count
如果没有到话,就返回真实读取到的完整的数据个数
举个例子
如果一次需要读取5个元素,一个元素的大小为4个字节,因为要一次读取5个数据,所以我们读取的总个数是5的倍数,而总共元素个数却是16,因此当我们读取20个元素的时候,由于总个数小于要读取的个数,所以就会返回最后完整读取的元素1,因为1<5所以就结束了
补充:
ferror:装文件读取结束后,用来判断文件是否因为读取过程中遇到错误而结束
feof:在文件读取结束后,用来判断文件是否因为读取过程中遇到文件结束标志而结束
文本文件的例子
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c;
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
while ((c = fgetc(fp)) != EOF)
{
putchar(c);
}
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
二进制文件的例子
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.bin", "wb");
fwrite(a, sizeof * a, SIZE, fp);
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, fp);
if (ret_code == SIZE) {
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
putchar('\n');
}
else {
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数区(程序变量等)。缓冲区的大小根据C编译系统决定的。
简单的来说就是我们在程序数据区中写程序,比如int a=0 char c=‘a’…
我们将这些数据写入文件当中(也就是写入硬盘)
当我们将数据写入文件时,我们先将数据放入输出缓冲区,当输出缓冲区放满后在将这些数据放入硬盘中去
而当我们要读取文件的时候,就需要将硬盘的数据放入输入缓冲区,然后再读取
但是为什么不直接将硬盘当中的数据读取,非要经过一个输入缓冲区呢?并且要将程序数据写入文件时为什么又要经过输出缓冲区呢?
其实这是为了提高效率,当我们写文件的时候是需要调用操作系统接口的,而如果我们频繁调用操作系统接口的话就不利于效率
举个例子,自习课老师在讲台上帮同学们答疑,如果你一发现有不会的就上去问老师,这样老师的效率就很低,因为老师不能只照顾一个人,而如果你将你所有问题都整理好,一次性问完的话,这样效率就很高
下面的代码是验证缓冲区的存在
#include <stdio.h>
#include <windows.h>
//VS2013 WIN10环境测试
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;
}