c语言终点站--文件操作

前言:

为什么要学习文件操作呢?想要知道这个问题,我们就需要先了解什么是数据的可持久化。

那么什么是数据的可持久化呢?数据的可持久化就是把内存中的数据对象永久的保存在电脑的磁盘文件中,将程序数据在持久状态和瞬时状态相互转换的机制。

而文件操作就可以让我们达到这一目的。

1.什么是文件

磁盘上的文件都是文件。

在程序设计中,我们将文件分为两种:程序文件、数据文件(从文件功能角度来分类)。

1.1程序文件

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

像我们写代码时创建的test.c或者test.cpp文件就是程序文件。

1.2 数据文件

数据文件存放程序在运行时需要读取的数据。

列如通讯录中的各个对象的数据,在使用通讯录程序时我们需要先从数据文件中读取数据。

接下来我们主要讨论的是数据文件。

在我们日常写代码刷题,处理数据的输入输出都是终端作为对象,即从终端的键盘输入数据,运行结果显示到显示器上。        

就像这个:

其实更多时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理 的就是磁盘上文件。

1.3 文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:文件路径+文件名主干+文件后缀

比如:

 c:\code\test.txt

 这个路径的意思就是c盘下的code文件夹里面的test.txt文件

为了方便查找,文件标识常被称为文件名。 而且在同一目录下的文件名必须不一样。

2.文件的打开与关闭

2.1文件指针

文件指针其实就是文件类型的指针。

在我们使用一个文件时,会在内存开辟一块相应的文件信息区,用来存放文件的相关信息(如文件的名 字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE(注意大写).

例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

struct _iobuf {char *_ptr;int   _cnt;char *_base;int   _flag;int   _file;int   _charbuf;int   _bufsiz;char *_tmpfname;};
typedef struct _iobuf FILE;

注意,不同的编译器FILE类型包含的内容不完全相同,但是大同小异。

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息, 使用者不必关心细节。

一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

下面我们可以创建一个FILE*的指针变量:

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

2.2文件的打开与关闭

要想将数据写入文件,或者是将文件中的数据读出来,都需要先打开目标文件。就像我们如果想喝牛奶,那肯定是先把瓶盖拧开。同样的,在完成一些列操作后,我们需要把文件关闭,跟喝完牛奶要盖上瓶盖一样的道理。

在打开文件的时候,会返回一个FILE类型的指针来指向该文件,我们就通过这个指针来对文件进行数据的读取和写入。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream ) ;

当我们使用fopen来打开文件的时候,需要传入的参数有两个,一个是文件路径filename,还有一个是打开文件的方式mode.

文件路径怎么书写?

在C语言中,文件路径的书写是根据操作系统的不同而有所差异。下面是一些常用操作系统的文件路径书写方式示例:

  • 绝对路径:C:\folder\file.c
  • 相对路径:folder\file.c 或者 …\folder\file.c (… 表示上一级目录)
绝对路径举例:
 c:\code\test.txt
相对路径举例:
..\\code\\test.txt

这里为什么要用“\\”而不是“\”呢?这就是涉及到转移字符的问题了,这里就不多说了。

有哪些打开方式呢?

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)        为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件         
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件         
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件         

实例:

#include <stdio.h>
int main ()
{FILE * pFile;//打开文件pFile = fopen ("myfile.txt","w");//文件操作if (pFile!=NULL){fputs ("fopen example",pFile);//关闭文件fclose (pFile);}return 0;
}

 3.文件的顺序读写

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入函数fread文件
二进制输出函数fwrite文件

3.1scanf/fscanf/sscanf的区别:

3.1.1 scanf

标准输入(即键盘)读取输入数据。它的函数原型为int scanf(const char *format, ...),其中format是格式字符串,用于指定输入数据的格式。scanf根据格式字符串解析输入数据,并将解析结果存储到对应的参数中。

3.1.2 fscanf

文件中读取输入数据。它的函数原型为int fscanf(FILE *stream, const char *format, ...),其中stream是指向文件的指针,format是格式字符串。fscanf根据格式字符串从文件中解析输入数据,并将解析结果存储到对应的参数中。

3.1.3 sscanf

字符串中读取输入数据。它的函数原型为int sscanf(const char *str, const char *format, ...),其中str是输入字符串,format是格式字符串。sscanf根据格式字符串从字符串中解析输入数据,并将解析结果存储到对应的参数中。

区别:

这些函数在功能上是类似的,但是适用的输入源不同。scanf适用于从标准输入(键盘)读取数据,fscanf适用于从文件读取数据,sscanf适用于从字符串读取数据。

需要注意的是,这些函数都可以返回成功匹配并读取的参数个数,用于判断读取是否成功。返回值为EOF(-1)表示读取失败。此外,它们的格式字符串的语法是相同的,都可以使用格式控制符来指定输入数据的类型和格式。

printf/fprintf/sprintf的区别跟上面的一样,只不过是输出数据。

4.文件的随机读写

4.1 fseek

根据文件指针的位置和偏移量来定位文件指针.

int fseek ( FILE * stream, long int offset, int origin );

举例:

#include <stdio.h>
int main()
{FILE* pFile;pFile = fopen("example.txt", "wb");fputs("This is an apple.", pFile);fseek(pFile, 9, SEEK_SET);fputs(" sam", pFile);fclose(pFile);return 0;
}

打开example.txt文件

我们发现“sam"被插入到了字符串"This is an apple."中第九个位置。

具体是怎么实现的呢?

来看这一行代码

fseek(pFile, 9, SEEK_SET);
  • pFile 是指向文件的指针;
  • 9 是 offset 参数,表示要移动的字节数或记录数;
  • SEEK_SET 是 origin 参数,用于指定相对位置。SEEK_SET 表示从文件的开头开始计算偏移量。

我们通过这一行代码将文件指针pFile的位置向后移动了9个位置,所以我们在接下来的写入操作时,是从第9个位置开始写入的。

4.2 ftell

返回文件指针相对于起始位置的偏移量

long int ftell ( FILE * stream );

举例:

int main()
{FILE* pFile;long size;pFile = fopen("example.txt", "rb");if (pFile == NULL) perror("Error opening file");else{fseek(pFile, 0, SEEK_END);   // non-portablesize = ftell(pFile);fclose(pFile);printf("Size of example.txt: %ld bytes.\n", size);}return 0;
}

这里我们先用fseek将文件指针移动到数据字节的最后一个位置,这个时候ftell返回的就是起始位置到末位置的距离。

 4.3 rewind

让文件指针的位置回到文件的位置

void rewind ( FILE * stream );

举例:


int main() {FILE* pf = fopen("example.txt", "w+");//读和写文件char arr[27] = { 0 };for (char i = 'a'; i <= 'z'; i++) {fputc(i, pf);}//将26个字母写入到文件中//此时文件指针的位置指向最后一个位置//输出此时文件指针偏移量int x = ftell(pf);printf("此时文件指针跟起始位置的偏移量为%d\n", x);rewind(pf);//将文件指针的位置移到起始位置fread(arr, 1, 26, pf);//将文件里的数据读入到arr中for (int i = 0; i < 27; i++) {printf("%c ", arr[i]);}fclose(pf);pf = NULL;return 0;
}

我们可以看到,通过frewind将文件指针移动到起始位置,我们才能从头开始将26个字母读入到字符数组arr中。

5.文本文件和二进制文件

5.1二进制文件

我们知道,在内存中所有数据都是以二进制的形式储存的。

如果不加任何转换就将其输入到文件中,那这种文件就是二进制文件,而这种文件里的数据我们通常是看不懂的。

举例:

int main() {FILE* pf = fopen("example.txt", "wb+");//二进制文件的读和写int a=10000;fwrite(&a, 4, 1, pf);fclose(pf);pf = NULL;return 0;
}

 

是不是看不懂?

用二进制编译器打开一看

10 27 00 00这是表示的是啥意思?

其实这就是二进制中的10000,只不过是用16进制表示出来了

5.2 文本文件?

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

用以上例子在文本文件中又是怎么表示的呢?

10000在文本文件中就是五个字符排列在一起的,所以10000的ASCII码的形式存储形式是

 6.文件读取结束的判定

6.1被错误使用的feof

feof函数是C语言中的一个库函数,用于检测文件流的结束标志。它的作用是判断文件是否已经达到文件末尾。当文件读取到末尾时,feof函数返回非零值(真),否则返回零值(假)。

注意:

在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)。

2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

正确使用:

1.文本文件中:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{int c; // 注意:int,非char,要求处理EOFFILE* fp = fopen("test.txt", "r");if(!fp) {perror("File opening failed");return EXIT_FAILURE;}//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOFwhile ((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);
}
2.二进制文件中:
#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); // 写 double 的数组fclose(fp);double b[SIZE];fp = fopen("test.bin","rb");size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组if(ret_code == SIZE) {//判断成功读取的的数据次数是否等于SIZEputs("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);return 0;
}

 7.文件缓冲区

缓冲区概念:

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根 据C编译系统决定的。

同时,缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

为什么要引入缓冲区?

比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

总而言之,缓冲区使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

这里再介绍一个刷新缓冲区的函数fflush:

int fflush ( FILE * stream );

其中,stream 是要刷新的流的指针。如果 stream 为 NULL,则刷新所有流的缓冲区。

8.可持久化数据的具体项目例子

这里就给大家看一看我之前写的通讯录:

c语言小课设--通讯录(动态内存管理+可持久化数据)-CSDN博客该项目实现一个通讯录功能,除了能根据具体需求扩大空间之外,也实现了最基本基本的增删查改等功能,并在退出通讯录时销毁创造的空间,从而不造成内存泄露。另外,这个项目由三部分组成,函数功能的实现在Contact.c源文件中,各种头文件、函数等声明则由文件Contact.h来实现,最后测试在源文件test.c文件中进行。https://blog.csdn.net/qq_62987647/article/details/133466779?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22133466779%22%2C%22source%22%3A%22qq_62987647%22%7D

9.总结

文件操作的学习可以让我们持久化数据,同时也理解了文件数据是怎么输入输出的。

有啥用呢?

用处大多了,比如说我们在用c语言写一个程序的时候,我们就可以将程序中的数据存放在文件中,以便于我们下次打开程序还能读取到,这也就更加符合我们的实际需求。

其实呢,当你学到文件这一块内容时,你对c语言的认知基本上有了一个新的高度,但是并不意味着是c语言学习的终点,学海无涯,我们当以谦卑的心态学习。

事已至此,好好学习,给我点赞,加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/130828.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Three.js如何计算3DObject的2D包围框?

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在Three.js应用开发中&#xff0c;有时你可能需要为3D场景中的网格绘制2D的包围框&#xff0c;应该怎么做&#xff1f; 朴素的想法是把网格的3D包围框投影到屏幕空间&#xff0c;例如&#xff0c;下图中的绿色框 3D包围框…

LeetCode【84】柱状图中的最大矩形

题目&#xff1a; 思路&#xff1a; https://blog.csdn.net/qq_28468707/article/details/103682528 https://www.jianshu.com/p/2b9a36a548fa 清晰 代码&#xff1a; public int largestRectangleArea(int[] heights) {int[] heightadd new int[heights.length 1];for (i…

php+html+js+ajax实现文件上传

phphtmljsajax实现文件上传 目录 一、表单单文件上传 1、上传页面 2、接受文件上传php 二、表单多文件上传 1、上传页面 2、接受文件上传php 三、表单异步xhr文件上传 1、上传页面 2、接受文件上传php 四、表单异步ajax文件上传 1、上传页面 2、接受文件上传ph…

typora常用偏好设置

启用自动保存 关闭拼写检查 插入图片的设置 将图片保存在当前文件夹内 换行设置 关闭换行符的显示功能

ElementUI增删改的实现及表单验证

文章目录 一、准备二、添加功能2.1 新增添加按钮2.2 添加弹出框2.3 data中添加内容2.4 methods中添加相关方法 三、编辑功能3.1 表格中添加编辑和删除按钮3.2 methods中添加方法3.3 修改methods中clear方法3.4 修改methods中的handleSubmit方法 四、删除书籍功能4.1 往methods的…

【LeetCode刷题笔记】哈希查找

771. 宝石与石头 解题思路&#xff1a; 1. HashSet &#xff0c;把所有 宝石 加入 set , 然后遍历检查 每一块石头是否包含在set中 &#xff0c;若包含就是宝石。 2. 计数数组map, 把所有 宝石 进行 count 数组 计数 &#xff0c;, 然后遍历检查 每一块石头是否 count[stone] …

算法错题簿(持续更新)

自用算法错题簿&#xff0c;按算法与数据结构分类 python1、二维矩阵&#xff1a;记忆化搜索dp2、图论&#xff1a;DFS3、回溯&#xff1a;129612964、二叉树&#xff1a;贪心算法5、字符串&#xff1a;记忆化搜索6、01字符串反转&#xff1a;结论题7、二进制数&#xff1a;逆向…

高效节能双冷源空调架构在某新建数据中心项目中的应用

随着互联网、通信、金融等行业的发展&#xff0c;数据中心产业迈入高质量发展新阶段&#xff0c;在国家“双碳”战略目标和“东数西算”工程的有力指引下&#xff0c;数据中心加快向创新技术、强大算力、超高能效为特征的方向演进。数据中心已经成为支撑经济社会数字化转型必不…

Linux系统管理:虚拟机Centos Stream 9安装

目录 一、理论 1.Centos Stream 9 二、实验 1.虚拟机Centos Stream 9安装准备阶段 2.安装Centos Stream 9 3.进入系统 一、理论 1.Centos Stream 9 (1) 简介 CentOS Stream 是一种 Linux 操作系统。安装此操作系统的难题在于&#xff0c;在安装此系统之前&#xff0c…

想要用Chat GPT写申请文书?先看各大名校招生官对它的态度是什么?

新的申请季已经正式开始&#xff0c;一些热门项目的ED截止日期也不再遥远&#xff0c;因此很多准留学生们都已经开始了关于文书的创作。 而随着科技的不断发展&#xff0c;以ChatGPT为首的一众AI工具也作为一种辅助手段愈发融入了我们的生活。 那么不免就会有一些同学在准备申…

户外led显示屏中的裸眼3D效果是怎么做出来的?

近几年&#xff0c;裸眼3D成了一个热点词汇&#xff0c;但凡它出现的地方都会迅速成为网络热门话题和网红打卡点。裸眼3D大屏凭借其立体逼真的画面显示效果&#xff0c;带给人们新颖震撼的视觉体验&#xff0c;不仅成为户外广告的“新宠”&#xff0c;还成为了城市的新地标&…

Java 获取服务器资源(内存、负载、磁盘容量)

1.说明 我们经常通过SSH终端发送shell命令进行服务器运维&#xff0c;从而获取到服务器的各种资源&#xff0c;按照这个思路&#xff0c;我们可以利用Java做一个定时任务&#xff0c;定时采集服务器资源使用情况&#xff0c;从而实现服务器资源的动态呈现。 2.封装SSH操作方法…