当前位置: 首页 > news >正文

C语言:详解文件操作

C语言:详解文件操作

1.文件相关知识
2.文件的打开和关闭
3.文件的顺序读写
4.文件的随机读写
5.文件读取结束判定
6.文件缓冲区

1.文件相关知识

什么是文件?

磁盘(硬盘)上的文件是文件。

在程序设计中,我们一般谈的文件有两种:程序文件、数据文件。

程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。

本期讨论的是数据文件。

一个文件要有一个唯一的文件标识,文件名包含3部分:文件路径 + 文件名主干 + 文件后缀,例如:c:\code\test.txt 。最后一个 \ 和 . 之间的是文件主干名,最后一个 \ 之前的内容(包括最后一个 \ )为文件路径,剩下的内容和点号 . 为文件后缀。
在这里插入图片描述
根据数据的组织形式,数据文件被称为文本文件或者二进制文件

数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件

如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

流和标准流

程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了帮助程序员对各种设备进行方便的操作,我们抽象出了的概念,我们可以把流想象成流淌着字符的河。

C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。

C语言程序在启动的时候,默认打开了3个流:

  • stdin :标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout :标准输出流,在大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
  • stderr :标准错误流,在大多数环境中输出到显示器界面。

stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为文件指针。C语言中,就是通过 FILE* 的文件指针来维护流的各种操作。

文件指针

每个被使用的文件都在内存中开辟了⼀个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息保存在一个结构体变量中,该结构体类型由系统声明,取名 FILE

下面创建一个FILE*类型的指针变量pf。

FILE* pf;  //文件指针变量

pf是一个指向FILE类型数据的指针变量,可以使pf指向某个文件的文件信息区,通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件。
在这里插入图片描述

2.文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。

fopen函数用来打开文件,函数原型如下:

FILE* fopen(const char* filename,const char* mode);
  • 参数filename为文件名,用字符串表示。
  • 参数mode为文件打开模式,用字符串表示。

读和写的意义如图所示:
在这里插入图片描述
文件打开模式如下:
在这里插入图片描述
注意:对于"w",若打开的文件存在且已有信息,则清空原有信息。

fclose函数用来关闭文件,函数原型如下:

int fclose(FILE* stream);

示例:打开一个文件,然后关闭文件。

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");if(pf==NULL)  //检查{perror("fopen");  //如果pf为NULL,就会打印 fopen:错误信息 ,perror的参数是一个字符串return 1;}fclose(pf);  //关闭文件pf=NULL;  //把pf置为NULL,避免成为野指针。return 0;
}

程序运行后就会自动创建test.txt文件,按如下方法找到test.txt文件。
在这里插入图片描述
接下来点击 “x” 退出,按下图操作。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.文件的顺序读写

顺序读写函数介绍

在这里插入图片描述
上面说的适用于所有输入流一般指适用于标准输入流和其他输入流(如文件输入流);所有输出流一般指适用于标准输出流和其他输出流(如文件输出流)。

fputc函数

fputc函数可将字符输出到文件中,函数原型如下:

int fputc(int ch,FILE* stream);
  • 参数ch传字符或ASCII码值

示例:在test.txt文件上写字母a~z。

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");if(pf==NULL)  //检查{perror("fopen");return 1;}char ch=0;for(ch='a';ch<='z';ch++)  //写入字母a~zfputc(ch,pf);fclose(pf);pf=NULL;return 0;
}

程序运行结束后,我们用记事本打开文件,就能看到文件被写入字母a~z。
在这里插入图片描述
fputc函数的第二个参数是文件指针类型的stream,因为fputc适用于所有输出流,所以我们将上述代码中fputc的参数pf改为stdout,即可把字母a~z输出到屏幕。

#include<stdio.h>
int main()
{char ch=0;for(ch='a';ch<='z';ch++)fputc(ch,stdout)return 0;
}

在这里插入图片描述

fgetc函数

fgetc函数可以从文件中读取字符,函数原型如下:

int fgetc(FILE* stream);
  • 若成功读取字符,返回字符的ASCII码值。
  • 若读取遇到文件末尾,或读取失败,返回EOF(即为 -1)。

示例:把test.txt中的字母a~z显示到屏幕上。

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}int ch=0;while((ch=fgetc(pf))!=EOF)  //如果能正常读取,循环继续,直到读到文件末尾printf("%c",ch);fclose(pf);pf=NULL;return 0;
}   

在这里插入图片描述
fgetc也可以把参数pf改为stdin,这样就能从键盘输入字符,类似scanf。

#include<stdio.h>
int main()
{int ch=0;ch=fgetc(stdin);fputc(ch, stdout);   return 0;
}

在这里插入图片描述

fputs函数

fputs函数可以向文件输出一个字符串,函数原型如下:

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");if(pf==NULL){perror("fopen");return 1;}fputs("haha\n",pf);fputs("hehe\n",pf);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述
可以看到,test.txt文件被写入了haha和hehe,由于\n的作用,所以有3行。

同样地,我们把参数pf改为stdout,就能把字符串输出到屏幕。

#include<stdio.h>
int main()
{char str[]="abcdefg";fputs(str,stdout);return 0;
}

在这里插入图片描述

fgets函数

fgets函数能从文件中最多读取一行字符,但不会换行读,函数原型如下:

char* fgets(char* str,int num,FILE* stream);
  • 参数num表示最多读num个字符。
  • 读取成功返回字符串地址,读取失败或遇到文件末尾,返回NULL。

示例:将test.txt中的haha、hehe打印到屏幕上。

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}char str[100];while(fgets(str,30,pf)!=NULL)printf("%s",str);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述
同样的,我们把参数pf改为stdin,就能在键盘上输入。

#include<stdio.h>
int main()
{char str[100];fgets(str,5,stdin);printf("%s\n",str);return 0;
}

因为最多只读5个字符,且包含 ‘\0’ ,所以打印结果如下:
在这里插入图片描述

fprintf函数

fprintf函数是格式化输出函数,函数原型如下:

int fprintf(FILE* stream,const char* format,...);

示例:把结构体的数据写到文件中。

#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{struct S s={"张三",20,65.5f};FILE* pf=fopen("test.txt","w");if(pf==NULL){perror("fopen");return 1;}fprintf(pf,"%s %d %f",s.name,s.age,s.score);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述
可以看到,fprintf函数与printf类似,只是fprintf多了一个参数,我们同样可以通过对文件类型参数的调整来使数据输出到屏幕。

#include<stdio.h>
int main()
{int arr[10]={0};for(int i=0;i<10;i++){fprintf(stdout,"%d ",arr[i]);}return 0;
}

在这里插入图片描述

fscanf函数

fscanf函数为格式化输入函数,函数原型如下:

int fscanf(FILE* stream,const char* format,...);

示例:把test.txt文件中的数据读入结构体变量s中。

#include<stdio.h>
struct S
{char name[20];int age;float score;
};
int main()
{struct S s={0};FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}fscanf(pf,"%s %d %f",s.name,&(s.age),&(s.score));printf("%s %d %f\n",s.name,s.age,s.score);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述
fscanf也与scanf类似,不同之处就在于fscanf函数参数多了一个文件类型指针,我们通过这个指针也可以实现通过键盘输入数据。

#include<stdio.h>
int main()
{int n=0;fscanf(stdin,"%d",&n);fprintf(stdout,"%d",n);return 0;
}

在这里插入图片描述

fwrite函数

fwrite函数为二进制输出函数,返回成功读取的元素个数,只适用于文件输出流,函数原型如下:

size_t fwrite(const void* p,size_t size,size_t count,FILE* stream);
  • 例如p指向数组,每个元素占size个字节,共count个元素。

示例:把arr的数据写到文件test.txt中。

#include<stdio.h>
int main()
{int arr[]={1,2,3,4,5};int len=sizeof(arr)/sizeof(arr[0]);FILE* pf=fopen("test.txt","wb");if(pf=NULL){perror("fopen");return 1;}fwrite(arr,sizeof(int),len,pf);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述
由于文件的打开模式是"wb",所以test.txt文件里的内容是无法读懂的。

fread函数

fread函数是二进制输入函数,读到多少个元素就返回数字多少,只适用于文件输入流,函数原型如下:

size_t fread(void* p,size _t size,size_t count,FILE* stream);
  • size为元素所占字节数,count为元素个数。

示例:把test.txt文件中的二进制数据读到arr。

#include<stdio.h>
int main()
{int arr[5]={0};FILE* pf=fopen("test.txt","rb");if(pf==NULL){perror("fopen");return 0;}int i=0;fread(arr, sizeof(int), 5, pf);for (int i = 0;i < 5;i++){printf("%d ",arr[i]);}fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述

4.文件的随机读写

fseek函数

fseek可根据文件中的光标的位置来读写文件。函数原型如下:

int fseek(FILE* stream,long int,offset,int origin);
  • 参数offset为目标位置到起始位置偏移量。
  • 参数origin为起始位置,文件起始位置为SEEK_SET,光标当前位置为SEEK_CUR,文件末尾为SEEK_END。

示例:

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","w");  //先用"w"把之前文件中的内容清空if(pf==NULL){perror("fopen");return 1;}char str[]="abcdefghij";  //往文件中写入字母a~jfputs(str,pf);fclose(pf);  //关闭文件,刷新缓冲区接下来才能正常操作pf=fopen("test.txt","r");  //再次以"r"打开文件fseek(pf,4,SEEK_SET);  //光标从文件起始位置(向右)偏移4,向左偏移则为负数int ch=fgetc(pf);printf("%c\n",ch);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述
在这里插入图片描述

ftell函数

ftell函数能返回光标相对起始位置的偏移量,函数原型如下:

long int ftell(FILE* stream);

示例:

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}for (int i=0;i<5;i++)  //读取5个字符{fgetc(pf);}int ret=ftell(pf);printf("%d\n",ret);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述

rewind函数

rewind函数能让文件指针的位置回到文件的起始位置,函数原型如下:

void rewind(FILE* stream);

示例:

#include<stdio.h>
int main()
{FILE* pf=fopen("test.txt","r");if(pf==NULL){perror("fopen");return 1;}for (int i=0;i<5;i++)  //移动光标{fgetc(pf);}int ret=ftell(pf);printf("此时光标与起始位置的距离为%d\n",ret);rewind(pf);ret=ftell(pf);printf("此时光标与起始位置的距离为%d\n",ret);fclose(pf);pf=NULL;return 0;
}

在这里插入图片描述

5.文件读取结束判定

文件读取结束的原因可能是 遇到文件末尾 或 读取出错。

文本文件读取是否结束:

  • fgetc 的返回值判断是否为 EOF 。
  • fgets 的返回值判断返回值是否为 NULL 。

⼆进制文件的读取是否结束:

  • 判断返回值是否小于实际要读的个数,例如,fread判断返回值是否小于实际要读的个数。

其他判断方法:

  • feof函数,返回值非0,则遇到文件末尾。
int feof(FILE* stream);
  • ferror函数,错误标记被设置返回非0,否则返回0。
int ferror(FILE* stream);

6.文件缓冲区

ANSIC 标准采用 “缓冲文件系统” 处理的数据文件,缓冲文件系统是指:系统自动地在内存中为程序中每⼀个正在使用的文件开辟一块 “文件缓冲区”。

若从内存向磁盘(硬盘)输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

缓冲区的大小根据C编译系统决定的。
在这里插入图片描述
因为有缓冲区的存在,C语言在操作文件的时候,需要做 刷新缓冲区 或者 在文件操作结束的时候关闭文件(用fclose关闭文件)。如果不做,可能导致读写文件的问题。

拙作一篇,望诸位同道不吝斧正。

http://www.lryc.cn/news/598481.html

相关文章:

  • 双紫擒龙紫紫红黄安装使用攻略,2025通达信指标源码,擒龙追踪源码公式学习
  • 树莓派5与Zero 2 W全面解析:选型指南与入门攻略
  • IPv6网络优化
  • DeepSpeed-FastGen:通过 MII 和 DeepSpeed-Inference 实现大语言模型的高吞吐文本生成
  • Docker 打包Vue3项目镜像
  • 【数字IC验证学习------- SOC 验证 和 IP验证和形式验证的区别】
  • 旅行短视频模糊的常见原因及应对方法
  • C++常见面试题/笔试收录(一)
  • [202103][Docker 实战][第2版][耿苏宁][译]
  • Vue 3 项目性能优化指南
  • TCP 套接字--服务器相关
  • MCU(微控制器)中的高电平与低电平?
  • 使用 Vue 实现移动端视频录制与自动截图功能
  • 每日算法刷题Day52:7.24:leetcode 栈5道题,用时1h35min
  • linux权限续
  • 【从0开始学习Java | 第3篇】阶段综合练习 - 五子棋制作
  • 奇异值分解(Singular Value Decomposition, SVD)
  • 光通信从入门到精通:PDH→DWDM→OTN 的超详细演进笔记
  • day62-可观测性建设-全链路监控zabbix+grafana
  • 深度分析Java内存结构
  • 排序查找算法,Map集合,集合的嵌套,Collections工具类
  • SSM之表现层数据封装-统一响应格式全局异常处理
  • Spring AI 系列之二十四 - ModerationModel
  • 从0到1学习c++ 命名空间
  • 【Linux】linux基础开发工具(一) 软件包管理器yum、编辑器vim使用与相关命令
  • 【YOLOv8改进 - 特征融合】FCM:特征互补映射模块 ,通过融合丰富语义信息与精确空间位置信息,增强深度网络中小目标特征匹配能力
  • Springboot儿童医院问诊导诊系统aqy75(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 免费生成文献综述的网站推荐,助力高效学术写作
  • 408——数据结构(第二章 线性表)
  • 线段树学习笔记 - 练习题(2)