【10月更文挑战第2天】
5.文件的顺序读写
顺序读写介绍
除了最后两行的,其他的都是读和写文本信息
第一组:fputc 和fgetc
//fputc--写字符
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
/*fputc('a', pf);
fputc('b', pf);
fputc('c', pf);*/
//循环写入
for (int i = 'a'; i <= 'z'; i++)
{
fputc(i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
/*
* fputc--写字符
int fputs(int *character,FILE*stream);
第一个参数是要写的字符
*/
/*
fgetc---读取一个字符
int fgets(FILE*stream)
只有一个参数就是那个文件的流
读取失败就会返回EOF,
读取正常的话会返回对应字符的ASCII码值
EOF是enf of file
文件的结束标志
*/
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//现在文件里面存放的是之前写入的26个字母
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
// //输出的就是a b c
// //我们在一开始读文件的时候,光标指向的是a,读完a之后,光标就指向了a的后一位的字母
// //文件的光标一直随着我们在读在变化
//
// //关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputc函数返回的是对应字符的ASCII码值,两个参数,第一个参数是要写的字符
第二个参数是文件对应的流,文件指针
fgetc读取字符,参数是对应的文件的指针
读取失败就会返回EOF,
读取正常的话会返回对应字符的ASCII码值
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch=0;
while ((ch = fgetc(pf)) != EOF)//只要返回值不是EOF我们一直进行读取
{
printf("%c ", ch);
}
//那么这个输出的就是26个字母
return 0;
}
int main()
{
int ch=fgetc(stdin);//从键盘(标准输入流)上读取
fputc(ch, stdout);//将字符输出(写)到屏幕(标准输出流)
return 0;
}
从键盘上输入,在屏幕上输出
第二组:fputs和fgets
fputs:
int fputs(const charstr,FILEstream)
第一个参数是一个字符指针,指向了一个字符串,第二个是一个文件指针
返回类型是int
int main()
{
//1.打开文件
FILE* pf = fopen("hu.txt", "w");
if (pf == NULL)//判断打开是否成功
{
perror("fopen");
return 1;
}
//2.写文件
fputs("I am a student", pf);//传过去的是字符串首元素的地址
int ret=fputs("are you ok?", pf);
//这两个输入的字符串在一行上面
printf("%d", ret);//返回值是0,说明成功了,不是负数
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//如果fputs输入成功的话,返回的就是一个非负整数
//失败的话,函数会返回EOF
如果fputs输入成功的话,返回的就是一个非负整数
失败的话,函数会返回EOF
fgets:
charfgets(char str,int nmu,FILE*stream)
第一个参数就是一个指针,指向复制到读取字符串的字符数组的指针
第二个参数num 是这个字符串能拷贝多少个字符
//fgets--读文件
int main()
{
//1.打开文件
FILE* pf = fopen("hu.txt", "r");
if (pf == NULL)//判断打开是否成功
{
perror("fopen");
return 1;
}
//2.读文件
char arr[20] = { 0 };//将要读的文件放到这个数组内,相当于拷贝到这个数组内
fgets(arr,20,pf);
printf("%s", arr);
//将读到的数据放到arr里面
//但是这里的arr并不是合适的,I am a studentare y我们还有些数据并没有读到
//读取到的字符末尾还有一个'\0',总共下来就是20个字符了,真实读到的只有19个
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//从流中读取字符串,将字符串赋值拷贝在字符串数组中,最多读num-1个字符
//如果遇到换行的话,我们会直接停下来的,那么我们会读取\n。\n后面还是要读取\0的
fgets在使用的时候,我们要先创创建一个字符串数组,这个数组会存储我们读到的数据的
num就是我们读取的数据个数,但是因为读取到的还有一个\0,所以我们实际读到的仅仅只有num-1个字符
//在键盘上读,输出在屏幕上
int main()
{
char arr[20] = { 0 };//存储我们读到的数据
fgets(arr, 20, stdin);//stdin是标准输入流
fputs(arr, stdout);//stdout是标准输出流
return 0;
}
fgets如果读取成功的话,那么会返回str--目标空间的起始地址,接不接收无所谓的
但是读取失败的话会返回一个空指针的
第三组:fscanf和fprintf
fprintf:
int fprintf(FILEstream,const char format,....)
第三个参数就是可变参数列表
而printf的参数没有第一个,因为printf默认操作的就是stdout
fprintf可以适用于所有的操作流,可以适用于文件流,也可以适用于标准输出流
fprintf将数据写到文件内
struct S
{
char name[20] ;
int age ;
float score ;
};
int main()
{
struct S s = { "lisi",18,88.5f };
//1.打开文件
FILE* pf = fopen("hu.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//2.写文件
/*fprintf()*/
fprintf(pf,"%s %d %f", s.name,s.age,s.score);//在前面加上文件指针,将后面所指的数据写到文件内
//printf("%s %d %f", name, age, score);
// 如果写入是结构体的数据也是可以的
//
// 将s里面的数据写到文件里面
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf将文件读出来
fscanf:
int fscanf(FILEstream,const char format,....)
scanf和fscanf的差异也仅仅只是缺失文件指针
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
//1.打开文件
FILE* pf = fopen("hu.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//2.读文件
//scanf("%s %d %f",&name,&age,&score)
//从文件中读取信息,将读取的信息存放在s的各个成员中
fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));//数组名就是地址,不用加取地址符号
//打印在屏幕上
//printf("%s %d %f", s.name, s.age, s.score);
fprintf(stdout,"%s %d %f", s.name, s.age, s.score);//默认输出流
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf将文件内的数据拷贝存在结构体中
对比一组函数,介绍sscanf和sprintf的用法
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf/printf 针对标准输入流(stdin)/标准输出流 (stdout) /格式化输入/输出函数
fscanf/fprintf 针对所有输入流(stdin)/输出流(stdout) /格式化输入/输出函数
第一种只能在键盘上输入和输出
第二种可以在文件和键盘上输入和输出
那么sprintf和sscanf有什么作用呢?
sprintf
int sprintf(char str,const char format,...)
sprintf作用就是将格式化的数据输入到指针str所指向的空间(字符串中)
可以理解为将格式化的数据转换为字符串
struct S
{
char name[20];
int age;
float score;
};
int main()
{
char arr[125] = { 0 };
struct S s = { "lisi",18,98.5f };
sprintf(arr, "%s %d %f", s.name, s.age, s.score);
//将后面的数据转换为字符串存在arr中
printf("%s", arr);//将数据以字符串形式打印出来
return 0;
}
/*
sprintf可以理解为将数据转换为字符串,然后存储在指定的字符数组中
*/
ascanf的作用就和sprintf作用相反
sprintf的作用是将数据以字符串的形式存储在数组内
那么sscanf就是将数组中的这些已经转化为字符串的格式化数据提取出来
sscanf
int sscanf(const char s,const char format , ....)
struct s
{
char name[20];
int age;
float score;
};
int main()
{
char arr[125] = { 0 };
struct s s = { "lisi",18,98.5f };
//临时变量
struct s tmp = { 0 };
//将s中的各个数据转换成字符串放在arr中
sprintf(arr, "%s %d %f", s.name, s.age, s.score);
//从字符串arr中提取格式化的数据,存放在tmp中
sscanf(arr,"%s %d %f", tmp.name, &(tmp.age), &(tmp.score));//name 是字符串首元素的地址
printf("%s %d %f", tmp.name, tmp.age, tmp.score);
return 0;
}
从字符串中提取格式化的数据,将字符串转化为格式化数据
总结:sprintf:将格式化的数据转换为字符串
sscanf:将字符串转化成格式化的数据
第五组:fread和fwrite
fwrite
size_t fwrite( const void ptr ,size_t size,size_t count, FILE**stream)
有四个参数
将ptr指向的这块空间里面的count个大小为size的元素写到stream所指向的文件里
fread
size_t fread( const void ptr ,size_t size,size_t count, FILE**stream)
两个函数的参数是一模一样的
那么fread的作用就是把stream里面的count个大小为size的元素写到ptr所指向的空间
从文件stream中读取count个大小为size个字节的数据,存放在ptr指向的空间中
//struct S
//{
// char name[20];
// int age;
// float score;
//};
//
//int main()
//{
// struct S s = { "lisi",18,98.6f };
// //将这些数据以二进制的形式写到文件中
//
// //打开文件
// FILE* pf = fopen("test.txt", "wb");//b就是二进制的形式,这里的wb就是二进制的写
//
// //判断文件打开成功没?
// if (pf == NULL)
// {
// perror("fopen");
// return 1;
// }
// //写文件
// fwrite(&s, sizeof(struct S), 1, pf);//写的数据来自s,我们要提供地址
// //这里只有1个结构体的数据,那么我们写1,写到pf所指向的文件里面去
//
// //关闭文件
// fclose(pf);
// pf = NULL;
// return 0;
//}
//这里我们已经将s中的数据以二进制的形式写到文件中去了
//那么下面的代码我们就进行解读
struct S
{
char name[20];
int age;
float score;
};
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);
//第一个参数是我们读完存放信息的地址
//第二个参数是一个元素的大小,
// 第三个元素是元素的个数
// pf就是我们要进行读的文件
printf("%s %d %f", s.name, s.age, s.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
这两个函数的返回值都是size_t 类型的
成功读取几个就返回
假设我们在用fread的时候,我们从文件中读取5个大小为size的数据放到ptr里面去,但是这个文件只有三个,读不到5个
那么我们这个函数就会返回我们实际读到数据的真实个数
文件的随机读写
想在哪里读就在哪里读,想在哪里写就在哪里写
文件的随机读写要确定我们这个文件里面写进去了很多信息
我们要根据我们的需要,将文件指针的指针(文件内容的光标)位置进行调整
fseek的使用
fseek
int fseek(FILE* stream,long int offest,int origin)
第一个参数是文件指针,第二个参数是偏移量 ,第三个参数是起始位置
从第二个参数我们能回想起之前学的offsetof---计算结构体成员相较于起始位置的偏移量
我们一定要知道偏移量和起始位置,我们才能定位这个文件指针(光标)
对于第三个参数,我们有三种选项
1.SEEK_SET:文件的起始位置
2.SEEK_CUR:文件指针(光标)当前的位置
3.SEEK_END:文件的末尾
int main()
{
//读取二进制的数据到文件中
//打开文件
FILE* pf = fopen("test.txt", "r");
//判断文件打开成功没?
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取文件,fgetc读取字符,返回被读字符的ASCII码值
int ch = 0;
ch=fgetc(pf);
printf("%c\n", ch);
//最开始,光标指向的是a,读完a之后,光标就会往后跳
ch = fgetc(pf);
printf("%c\n", ch);//这里打印出来的就是b,打印完b,光标就指向了c
//按照常规的话,下面的代码中的光标就指向了c,但是我们想直接读e,我们该做什么呢?
//所以我们现在要重新定位文件指针,将文件指针的(光标)指向e的位置
//fseek(pf, 4, SEEK_SET);//从文件起始位置偏移4下就能到e的位置了
//fseek(pf, 2, SEEK_CUR);//因为上面已经进行了b的打印了,那么光标就指向了c的位置,
//那么我们从当前位置进行光标的偏移,偏移两下光标就指向了e
fseek(pf, -2, SEEK_END);
//最后的位置就是f后面,光标指向了f的后面,那么这个光标前进两个指向的就是e了
ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
向后偏移的话偏移量就是正数,向前的话偏移量就是负数
ftell的使用
ftell
long int ftell(FILE*stream)
返回文件指针相对于起始位置的偏移量
int main()
{
//读取二进制的数据到文件中
//打开文件
FILE* pf = fopen("test.txt", "r");
//判断文件打开成功没?
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取文件,fgetc读取字符,返回被读字符的ASCII码值
int ch = 0;
ch = fgetc(pf);
printf("%c\n", ch);//a
//最开始,光标指向的是a,读完a之后,光标就会往后跳
ch = fgetc(pf);//b
printf("%c\n", ch);//这里打印出来的就是b,打印完b,光标就指向了c
fseek(pf, -2, SEEK_END);
//我们从文件的末尾进行访问,这是一个字符串,隐藏着一个'\0',所以我们要偏移两下才能到e
ch = fgetc(pf);
printf("%c\n", ch);//e
//读完e我们想知道当前位置相较于起始位置的偏移量
printf("%d", ftell(pf));//5
//为什么是5呢?
//因为上面我们刚读完e了,光标指向了f,那么这个位置距离起始位置的距离就是5了
//最开始的起始位置在a的前面
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
rewind的使用
让文件指针位置回到文件的起始位置
void rewind(FILE*stream)
int main()
{
//读取二进制的数据到文件中
//打开文件
FILE* pf = fopen("test.txt", "r");
//判断文件打开成功没?
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读取文件,fgetc读取字符,返回被读字符的ASCII码值
int ch = 0;
ch = fgetc(pf);
printf("%c\n", ch);//a
//最开始,光标指向的是a,读完a之后,光标就会往后跳
ch = fgetc(pf);//b
printf("%c\n", ch);//这里打印出来的就是b,打印完b,光标就指向了c
fseek(pf, -2, SEEK_END);
//我们从文件的末尾进行访问,这是一个字符串,隐藏着一个'\0',所以我们要偏移两下才能到e
ch = fgetc(pf);
printf("%c\n", ch);//e
//让文件指针回归到最初的位置
//将文件指针重新定位到文件起始位置
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
//那么这一次的打印就是a了
fclose(pf);
pf = NULL;
return 0;
}
文件的读取结束的判定
被错误使⽤的 feof
因为eof是文件结束的标志
以为feof函数是用来判断文件是否结束的,但其实不是的
牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
在文件的读取过程中,有可能读取文件结束
结束的原因:1.遇到文件末尾
2.遇到错误了
,是判断结束的原因的
- ⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
• fgetc 判断是否为 EOF .
• fgets 判断返回值是否为 NULL .
- ⼆进制⽂件的读取结束判断,
判断返回值是否⼩于实际要读的个数
。
例如:
• fread判断返回值是否⼩于实际要读的个数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,⾮char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp)//判断fp是不是空指针
{//如果是空指针的话,!为真,那么打印错误
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到⽂件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取⽂件循环
{
putchar(c);
}
//这个循环结束了,那么下面就是我们进行探讨读取结束的原因
//判断是什么原因结束的
if (ferror(fp))//是不是遇到错误而结束的
{
puts("I/O error when reading");
}
else if (feof(fp))//是不是遇到结尾结束的
{
puts("End of file reached successfully");
}
fclose(fp);
fp = NULL;
return 0;
}
#include <stdio.h>
enum { SIZE = 5 };//枚举类型定义一个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); // 写 double 的数组
//a是数组名,首元素的地址,那么*a就是首元素,那么sizeof*a计算的就是每个元素的大小
//将a中的数据以二进制的形式写到fp所指向的文件里面去
fclose(fp);//关闭文件
double b[SIZE];
fp = fopen("test.bin", "rb");//打开读文件,二进制形式rb
size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
//code是fread是实际读的个数,将fp中的数据以格式化数据的形式读到b中
if (ret_code == SIZE)//读取的个数等于size的话,就是正常读取
{
puts("Array read successfully, contents: ");
//对数组元素进行打印
for (int n = 0; n < SIZE; ++n)
printf("%f ", b[n]);
putchar('\n');
}
else { // error handling
if (feof(fp))//是不是读取的时候遇到了文件末尾而结束的
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp))//是不是遇到其他错误而结束的
{
perror("Error reading test.bin");
}
}
fclose(fp);
}
//拷贝文件
//将test1.txt拷贝到test2.txt
int main()
{
//打开文件
FILE*pfread=fopen("test1.txt", "r");//这个文件我们是用来读的
if (pfread == NULL)//打开失败的情况
{
perror("fopen\n");
return 1;
}
FILE* pfwrite = fopen("test2.txt", "w");//写文件
if (pfwrite == NULL)//打开失败的情况
{
perror("fopen\n");
fclose(pfread);//第二个文件都打开失败了,我们就直接将第一个文件关掉
return 1;
}
//读写文件
int ch = 0;
//读pfread所指向的文件,将文件内的数据通过ch写到pfwrite里面
while ((ch = fgetc(pfread)) != EOF)//不等于EOF就说明读取正常
{
fputc(ch, pfwrite);//fputc的第一个参数是要写的字符,第二个是文件指针
}
//当返回值是EOF的时候就结束了
//关闭文件
fclose(pfread);
pfread = NULL;
fclose(pfwrite);
pfwrite = NULL;
return 0;
}
/*我们在while循环中,先用ch = fgetc(pfread) != EOF,
* fgetc的返回值就是对应字符的ASCII码值
* 那么我们先读pfread里面的每个字符,然后在每层循环为ch附上每个字符的ASCII码值
* 在循环内,fputc第一个参数就是要写的字符数据,第二个参数就是所指向的文件指针,
* ch已经被赋上值了,那么我们就直接利用fputc将字符数据写到prwrite所指向的文件中
*
*
*
*/
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//这个文件我们是用来读的
if (pf == NULL)//打开失败的情况
{
perror("fopen\n");
return 1;
}
//读写文件
//int ch = 0;
//ch = fgetc(pf);
//int reet = feof(pf);//检测文件末尾
//printf("%d", reet);//打印出来的是0,如果不是结尾结束的话,那么就是不正常的
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);//将字符依次打印出来
}
int reet = feof(pf);
printf("%d", reet);//那么这里打印出来的就是1了
reet = ferror(pf);
printf("%d", reet);//输出的结果是0,因为此时已经是文件末尾了,并没有出现错误的情况
//关闭文件
return 0;
}
//如果这个是正常结束的话,到文件末尾的话,对于feof(pf)的话,返回值就是非0的数字
//如果不是结尾结束的话,那么返回的就是一个0
//w是写,r是读
对于feof来说的话,如果这个是正常结束的话,到文件末尾的话,对于feof(pf)的话,返回值就是非0的数字
如果不是结尾结束的话,那么返回的就是一个0
对于ferror来说的话,如果不是其他的错误信息导致停止话,就是返回的是0,
如果是什么错误信息导致的,那么这个返回的就是非0数字
文件缓冲区
ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为
程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓
冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输
⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓
冲区的⼤⼩根据C编译系统决定的
#include <stdio.h>
#include <windows.h>
//VS2022 WIN11环境测试
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
//写文件,将这个一个字符串放到文件内
//先将abcdef放到缓冲区内
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
Sleep(10000);//10000毫秒就是10秒
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
//注:fflush 在⾼版本的VS上不能使⽤了
printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭⽂件的时候,也会刷新缓冲区
pf = NULL;
return 0;
}
这⾥可以得出⼀个结论:
因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。
如果不做,可能导致读写⽂件的问题