打开与关闭
文件打开就是判断这个文件资源可不可以被占用,如果可以,就能够打开成功,否则打开失败
文件关闭就是释放文件资源
1、打开文件
1.1 函数声明
FILE *fopen(const char *pathname, const char *mode);
返回值:出错返回NULL,成功返回流指针
pathname:文件的路径。如果是当前目录下的文件,只写文件名即可。其他目录需要写绝对路径
mode:打开的模式
模式 | 含义 | 适用情况 |
r rb | 只读,文件必须存在 | 只想查看,不想修改文件 |
w wb | 只写,先清空原文件,写时覆盖原文件,没有则创建 | 只写一次的情况 |
a ab | 只写,追加原文件,没有则创建 | 需要多次写入的情况 |
r+ r+b | 读写,写时覆盖原文件,文件必须存在 | 可使用光标随意位置写入 |
w+ w+b | 读写,先清空原文件,写时覆盖原文件,没有则创建 | 可使用光标随意位置写入 |
a+ a+b | 读写,写时追加原文件,没有则创建 | 不可指定光标位置 |
注意:打开文件后,必须判断返回值是否有效。只有当返回值有效时,才能对文件进行操作。
注意:打开文件后,光标指向文件的开头
1.2 基本使用方法
使用步骤:
- 定义一个流指针fp
- 调用fopen打开文件
- 判断返回值有效性
具体代码实现如下:
#include <stdio.h>
int main(){
//1.定义一个流指针
FILE* fp = NULL;
//2.打开文件
fp = fopen("./test.c","r");//以只读方式打开文件./test.c
//3.判断返回值有效性
if(fp == NULL){
perror("fopen");//打印错误信息
return -1;
}
printf("open success\n");
fclose(fp);
return 0;
}
代码执行结果如下:
2、关闭文件
2.1 函数声明如下:
int fclose(FILE *stream);
返回值:成功返回0;失败返回EOF(-1),并设置错误号errno
stream:指向要关闭的文件的流指针
2.2 关闭文件函数所做的事情:
流关闭时,自动刷新缓冲区中的数据并释放缓冲区,这意味着将之前对文件的写入内容会真正的写入到磁盘中,并释放内存开辟的缓冲区。
如果不调用fclose但是程序结束,也会有fclose的同样的效果。
3、打印错误信息
3.1 函数声明如下:
//先输出字符串s,再输出错误号对应的信息
void perror(const char *s);
//根据错误号errnum,返回对应的错误信息
char *strerror(int errnum);
对于strerror的参数errnum,固定填入errno即可。这需要先包含#include <errno.h>
3.2 基本使用方法
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
fp = fopen("./alksjdla","r");
if(fp == NULL){
perror("perror"); //perror 打印错误
printf("strerror:%s\n",strerror(errno)); //strerror 打印错误
return -1;
}
printf("open success\n");
return 0;
}
代码执行结果如下:
读取与写入
输入输出两大问题:
- 光标问题:
读取与写入操作都是从光标位置开始执行,执行完成后都会对光标位置进行修改。因此读写操作首先要考虑的时当前光标位置,以及执行之后光标移动的结果。
- 缓冲区问题:
输出(写入)操作,都是行缓冲模式,如果输出结果没有'\n',那么需要在输出之后使用fflush手动刷新缓冲区,将缓冲区内容写入到文件中。
1、按字符输入输出
1.1 按字符输入函数声明如下:
//从文件中读取一个字符
int fgetc(FILE *stream);
//这是fgetc的宏,不是函数
int getc(FILE *stream);
//只能读取标准输入的一个字符,等同于fgetc(stdin)
int getchar(void);
返回值:成功返回字符,失败或读到文件末尾返回EOF(-1)
stream:指向要读取的文件的流指针
注意:从文件的当前光标位置读取一个字符,读取后光标偏移一个字符
1.2 按字符输出函数声明如下:
//向文件中写入一个字符
//同样,fputc只是将数据存入缓冲区,当遇到\n时才真正的存入到文件中
int fputc(int c, FILE *stream);
//这是fputc的宏,不是函数
int putc(int c, FILE *stream);
//只能向标准输出上输出一个字符,等同于fputc(stdout)
//同样,putchar只是将数据存入缓冲区,当遇到\n时才打印到屏幕
int putchar(int c);
返回值:成功返回写入的字符,失败返回EOF
c:要写入的字符
stream:指向要写入的文件的流指针
注意:将光标移动(与模式有关),在光标位置写入一个字符,写入后光标偏移一个字符
1.3 基本使用方法
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
//1.打开文件
fp = fopen("./file","a+");//以追加方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//读取文件
printf("1:输出%%c %c\n",fgetc(fp));//光标指向0,读取后偏移一个字符
printf("2:输出%%c %c\n",fgetc(fp));//光标指向1,读取后偏移一个字符
//写入文件
fputc('x',fp);//先将光标指向末尾,再向光标位置写入一个字符,写后光标偏移一个字符
//读取文件
printf("3:输出%%d %d\n",fgetc(fp));//光标指向末尾,没有数据
//关闭文件
fclose(fp);
return 0;
}
代码执行结果如下:
2、按行输入输出
2.1 按行输入
2.1.1 按行输入函数声明如下:
//从文件中读取数据,存放在s中
char *fgets(char *s, int size, FILE *stream);
//从标准输入上读取数据,存放在s中
char *gets(char *s); //不使用,因为没有指定读取数据的大小,容易造成缓冲区溢出
返回值:成功返回缓冲区首地址,失败返回NULL
s:将读取的内容放置的缓冲区地址
size:每次读取的字符的个数,'\0'同样算一个字符。size>输入字符个数时,会将回车也存入s
比如size = 3,输入abcd,最终缓冲区存放的数据为ab'\0',而不是abc
比如size = 10,输入abcd,最终缓冲区存放的数据为abcd '\n' '\0'
stream:从哪一个文件读取数据
注意:fgets执行后,光标会进行移动
2.1.2 size验证代码如下:
- 当size>字符个数时,会将\n存入缓冲区
- 当size<字符个数时,'\0'也算一个字符
2.1.3 按行输入的原理验证
按行输入函数 fgets()的原理就是读到回车就算一行,假如这一行有n个字符,如果n<size,那么读取到的是完整的一行数据;如果n>=size,那么本次只读取size-1个字符,其余字符下次调用fgets()会接着读取。
具体代码如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
char buf[100];
FILE* fp = NULL;
//打开文件
if((fp = fopen("./file","r")) == NULL){
perror("fopen");
return -1;
}
//读取文件
fgets(buf,5,fp);//从fp文件上一次最多读取5个字符
printf("1:%s\n",buf);
fgets(buf,5,fp);//从fp文件上一次最多读取5个字符
printf("2:%s\n",buf);
fgets(buf,5,fp);//从fp文件上一次最多读取5个字符
printf("3:%s\n",buf);
//关闭文件
fclose(fp);
return 0;
}
代码执行结果如下:
2.1.4 应用:获取文件的行号
具体代码实现如下:
int main(){
FILE* fp = NULL;
int i = 0;
char buf[3] = {0};//数组大小可随意填,后面有判断是否换行的处理,不需要一次读完一行
if((fp = fopen("./file","a+")) == NULL){//以追加方式,可读可写
perror("fopen");
return -1;
}
//计算文件有多少行
while(fgets(buf,sizeof(buf),fp) != NULL){
if(buf[strlen(buf)-1] == '\n'){//判断倒数第二个字符是否为换行,是换行代表这行读完
i++;
}
}
fclose(fp);
return 0;
}
2.2 按行输出
2.2.1 按行输出函数声明如下:
//将字符串数据s输出到文件中
int fputs(const char *s, FILE *stream);
//将字符串数据s输出到标准输出上
int puts(const char *s);
返回值:成功返回非负数,失败返回EOF
s:要输出的字符串
stream:指向要输出到的文件位置
注意:puts会自动添加'\n',fputs不会自动添加'\n'
注意:fputs执行后,光标会进行移动
2.2.2 基本使用方法
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
//1.打开文件
fp = fopen("./file","a+");//以追加方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//这里的fputs能写进去是因为文件关闭了,应该之后用fflush刷新一下
fputs("fputs",fp);//没有换行符'\n'
puts("puts");
//关闭文件
fclose(fp);
return 0;
}
代码执行结果如下:
3、按对象(二进制)输入输出
3.1 按对象输入输出函数声明:
//按对象输入,从stream中读取数据存入到ptr中
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
//按对象输出,将ptr中的数据写入到stream中
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
返回值:成功返回读写对象的个数,出错返回EOF
ptr:缓冲区指针,用于存放读的数据或要写的数据
size:每个内容的大小(块大小) 单位:字节
nmemb:读写多少个内容
stream:进行操作的文件流指针
注意:这两个函数既可以读写文本文件,也可以读写二进制文件(数据文件)
注意:这两个函数执行后,光标都会进行移动
3.2 读写文本文件实验
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
char buf[100] = {0};
//1.打开文件
fp = fopen("./file","a+");//以追加方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
fread(buf,sizeof(char),5,fp);//从fp指向的文件中读取5*sizeof(char)个字节,存放到buf中
printf("read:%s\n",buf);
//关闭文件
fclose(fp);
return 0;
}
代码运行结果如下:
3.3 读写二进制文件实验
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct test{
char name[10];
int age;
};
int main(){
FILE* fp = NULL;
struct test tWrite = {"rsy",20};
struct test tRead;
//打开文件
fp = fopen("./file","a+");//以追加方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//从fp指向的文件中写入1*sizeof(struct test)个字节
fwrite(&tWrite,sizeof(struct test),1,fp);
//重置光标位置
fclose(fp);
if((fp = fopen("./file","a+")) == NULL){
perror("fopen");
return -1;
}
//从fp指向的文件中读取1*sizeof(struct test)个字节
fread(&tRead,sizeof(struct test),1,fp);
printf("name = %s,age = %d\n",tRead.name,tRead.age);
//关闭文件
fclose(fp);
return 0;
}
代码运行结果如下:
流刷新定位
1、流刷新
流刷新的作用,就是手动的让缓冲区的内容进行输出,丢弃输入缓冲区的内容。
1.1 刷新流的函数声明如下:
int fflush(FILE *stream);
返回值:成功返回0,失败返回EOF
stream:指向文件的流
如果输出到屏幕,使用fflush(stdout)即可。
1.2 刷新流实验
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct test{
char name[10];
int age;
};
int main(){
FILE* fp = NULL;
struct test tWrite = {"rsy",20};
struct test tRead;
//打开文件
fp = fopen("./file","a+");//以追加方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//写入数据
fwrite("1234",sizeof("1234"),1,fp);
fflush(fp);//刷新流,这里的1234没有'\n',但刷新后可以将1234写入文件中
while(1);
//关闭文件
fclose(fp);
return 0;
}
2、流定位
流定位就是可以手动改变流指针(光标)的位置
注意:这下面3个函数只适用2GB以下的文件
2.1 流定位的函数声明如下:
//查询流指针位置
long ftell(FILE *stream);
返回值:成功返回流当前的读写位置,失败返回EOF
stream:要进行操作的文件流指针
注意:刚打开文件时,流指针的值为0,流指针的单位为字节。
//将流指针定位到开头
void rewind(FILE *stream);
//定位一个流
int fseek(FILE *stream, long offset, int whence);
返回值:成功返回0,失败返回 EOF
stream:要进行操作的文件流指针
offset:偏移量,正数代表向文件尾偏移,负数代表向文件头偏移,0代表不偏移
whence:偏移位置,从哪里开始偏移
参数 | 含义 |
SEEK_SET | 从文件头开始偏移 |
SEEK_CUR | 从文件流指针当前位置开始偏移 |
SEEK_END | 从文件尾开始偏移 |
注意:当fopen时,模式为a时,fseek不起作用
2.2 流定位实验
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
char read_buf[100] = {0};
//打开文件
fp = fopen("./file","w+");//打开时清空,以覆盖方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//1.打开后,文件流指针为0
printf("after fopen,point = %ld\n",ftell(fp));
//2.写入数据后,文件流指针偏移n个字节
fwrite("12345",strlen("12345"),1,fp);
fflush(fp);
printf("after write 12345,point = %ld\n",ftell(fp));
//3.写完后读取,需要将流指针重置位置
rewind(fp);
fread(read_buf,100,1,fp);
printf("now file data is %s\n",read_buf);
memset(read_buf,0,sizeof(read_buf));
//4.将流指针指向头,流指针变为0
rewind(fp);
printf("after rewind,point = %ld\n",ftell(fp));
//写入是在流指针位置开始写入,这会覆盖数据
fwrite("ab",strlen("ab"),1,fp);
fflush(fp);
rewind(fp);
fread(read_buf,100,1,fp);
printf("now file data is %s\n",read_buf);
memset(read_buf,0,sizeof(read_buf));
//5.fseek测试
//将流指针指向ab345的b
fseek(fp,1,SEEK_SET);
fread(read_buf,100,1,fp);//读取之后流指针指向了5后面
printf("after fseek->b,read data is %s\n",read_buf);
memset(read_buf,0,sizeof(read_buf));
fread(read_buf,100,1,fp);
//printf("again read,read data is %s\n",read_buf);
//memset(read_buf,0,sizeof(read_buf));
//将流指针指向ab345的5
fseek(fp,-1,SEEK_CUR);
fread(read_buf,100,1,fp);
printf("after fseek->5,read data is %s\n",read_buf);
memset(read_buf,0,sizeof(read_buf));
//关闭文件
fclose(fp);
return 0;
}
代码运行结果如下:
格式化输入输出
1、格式化输出
1.1 格式化输出的函数声明如下:
//将内容输出到屏幕
int printf(const char *format, ...);
//将内容输出到文件
int fprintf(FILE *stream, const char *format, ...);
//将内容输出到指定字符数组
int sprintf(char *str, const char *format, ...);
返回值:成功返回输出字符的个数,失败返回EOF
stream:要进行操作的文件流指针
str:要进行操作的字符数组指针
注意:fprintf也是从当前流指针位置开始写入
1.2 格式化输出实验
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
char buf[100] = {0};
//打开文件
fp = fopen("./file","w+");//打开时清空,以覆盖方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//printf
printf("printf:%s\n","this is printf");//将字符串打印到屏幕
//sprintf
sprintf(buf,"%s","this is sprintf");//将字符串写进buf中
printf("sprintf:%s\n",buf);
memset(buf,0,sizeof(buf));
//fprintf
fprintf(fp,"%s","this is fprintf");//将字符串写进fp指向的文件中
fflush(fp);
rewind(fp);
fread(buf,sizeof(char),100,fp);
printf("fprintf:%s\n",buf);
memset(buf,0,sizeof(buf));
//关闭文件
fclose(fp);
return 0;
}
代码运行结果如下:
2、格式化输入
2.1 格式化输入的函数声明如下:
//从标准输入上获取数据
int scanf(const char *format, ...);
//从文件中获取数据
int fscanf(FILE *stream, const char *format, ...);
//从字符串中获取数据
int sscanf(const char *str, const char *format, ...);
返回值:成功返回读取的数据个数,失败返回EOF
stream:从哪个文件获取数据
str:从哪个字符数组获取数据
注意:fscanf与sscanf会将存储空间先清空,再存入读取的值
注意:fscanf也是从当前流指针位置开始读取
sscanf这种格式化输入应用见b站视频,链接如下:
4.3.2蓝桥杯嵌入式组_串口接收数据解析(不定长)_哔哩哔哩_bilibili
2.2 格式化输入实验
具体代码实现如下:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(){
FILE* fp = NULL;
char buf[100] = {0};
//打开文件
fp = fopen("./file","w+");//打开时清空,以覆盖方式,可读可写打开文件
if(fp == NULL){
perror("fopen");
return -1;
}
//scanf
scanf("%s",buf);
printf("scanf:%s\n",buf);
memset(buf,0,sizeof(buf));
//sscanf
sscanf("abcdef:123:xxx","%[^:]",buf);
printf("sscanf:%s\n",buf);
memset(buf,0,sizeof(buf));
//fscanf
fwrite("this is fscanf\n",strlen("this is fscanf\n"),1,fp);
rewind(fp);
fscanf(fp,"%s",buf);
printf("fscanf:%s\n",buf);
//关闭文件
fclose(fp);
return 0;
}
代码执行结果如下: