文件操作讲解

目录

一.为什么使用文件

二.什么是文件

2.1程序文件

2.2数据文件  

2.3文件名

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

fwrite函数

fclose函数 

 四.文件的打开和关闭 

4.1流和标准流 

4.2文件指针

4.3文件的打开和关闭

五.文件的顺序读写

5.1文件的顺序读写函数

5.1.1fgetc函数

5.1.2fputc函数

 5.1.3fgets函数

5.1.4fputs函数

5.2scanf和printf

5.2.1fscanf函数

 5.2.2sprintf函数

六.文件的随机读写

6.1fseek函数 

6.2ftell函数

 6.3rewind函数

七.文件读取结束的判定

 1.文本文件

 2.二进制文件

八.文件缓冲区 


一.为什么使用文件

如果没有文件,我们写的程序数据是储存在内存中的,一旦程序结束,内存回收,数据就没有了,再次运行程序,上次写的程序就消失了,如果我们要将数据持久化,我们就可以使用文件了。

二.什么是文件

在程序设计中,我们讨论的文件一般有两种:程序文件,数据文件(从文件的功能的角度来分类)

2.1程序文件

程序⽂件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执⾏程序(windows 环境后缀为.exe)。

2.2数据文件  

⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件。

2.3文件名

⼀个⽂件要有⼀个唯⼀的⽂件标识,以便⽤⼾识别和引⽤。 

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

为了⽅便起⻅,⽂件标识常被称为⽂件名。 

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

根据数据的形式,数据文件被分为文本文件或者二进制文件

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

文本文件:数据在内存中以二进制的形式存储,要求在外存上以ASCII码的形式存储,ASCII字符的形式存储的文件就是文本文件。

如何判断一个数据在文件中是怎么存储的呢?
字符一律以ASCII码值的形式存储,数值型数据即可以用ASCII形式存储,也可以使用二进制的形式进行存储。

比如整数10000,如果以ASCII形式存储,则磁盘中占用5个字节(每一个字符一个字节),而以二进制的形式进行输出,则在磁盘上只占4个字节。

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h>
int main()
{int a = 10000;	FILE* pf = fopen("text.txt","wb");if (pf == NULL)//如果文件打开失败返回NULL;{perror("fopen");return 1;}fwrite(&a,4,1,pf);fclose(pf);pf = NULL;return 0;
}

fopen函数:以二进制的写(“wb”)的形式打开text.txt文件.
如果项目路径下没有这个文件,就会自动创建一个这样的文件.并且返回一个文件指针 

fwrite函数

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

 这个函数的功能就是将数据写入一个文件中。

  1. ptr指向一个数组.这个数组就是我们要写入的数据。
  2. count指的是我们需要写入的数组的元素个数。
  3. size指的是数组元素的大小。
  4. 我们需要写入的这个文件的文件指针。
  5. 返回值是成功写入的元素个数

fclose函数 

在我们打开文件后,使用完成,就需要关闭这个文件,fclose这个函数是用于这个的,并且为了防止非法访问,我们还需要将文件指针置为空指针,避免野指针。

综上:

我们上面的代码就是打开了一个叫做text.txt的文件,并且以二进制的形式写入了一个数据a。 

这个文件可在我们的项目路径下看到。 

 因为a是一个整形占四个字节,所以参数2是4。

正常来说,我们运行程序后,我们点击项目文件中的text.txt文件,我们发现我们无法看懂写了什么,是乱码。

这是因为这个文件是二进制的文件,在vs2022中,我们把这个文件添加到解决方案资源管理器中

 

点击完现有项,再把这个文件添加进去。

在打开方式中选择二进制编辑器

这样我们就打开了这个文件 

 

前面的00000000不用管,后面的10 27 00 00 就是我们写入的数据,我们会发现这不是二进制的,其实是因为二进制的形式过于长了,所以采用十六进制,而且2个字符就是一个字节,更加方便。

我们存入的数据是10000,为什么显示10 27 00 00 呢?

我的这个机器是小端字节序存储,10000的二进制是00002710,所以int a的第一个字节是10 ,第二个字节是27,再进行写入数据的时候,是一个字节一个字节的写的,所以第一个是10,然后再是27. 

 四.文件的打开和关闭 

4.1流和标准流 

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

C程序针对文件,画面,键盘等数据的输入输出操作都是通过流操作的。

一般情况下,我们想要向流里写数据或者从流中读取数据,都是要打开流的,然后操作。

那为什么我们从键盘上输入数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语言程序默认打开了3个流:

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

这三个流的类型都是FILE*,通常也叫做文件指针。

4.2文件指针

缓冲⽂件系统中,关键的概念是“⽂件类型指针”,简称“⽂件指针”。
每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 FILE.

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

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

FILE * pf;

这个pf的变量指向这个文件的文件信息区,通过这个文件的文件信息区就能够访问该文件,也就是说,通过文件指针变量能够间接找到与它相关联的文件了。

4.3文件的打开和关闭

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

在编写程序的时候,我们使用fopen函数打开这个文件,这个函数会返回一个FILE*的指针变量指向这个文件,也相当于建立了指针和文件的关系。

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

FILE * fopen ( const char * filename, const char * mode );

 filename就是文件名主干和后缀名,mode就是打开的方式

int fclose ( FILE * stream );

 stream就是流,也就是文件指针。

mode表示文件的打开方式

文件的使用方式含义如果指定的文件不存在
"r"只读,为了读取数据,打开一个已经存在的文本文件出错
"w"只写,为了写入数据,打开一个的文本文件创建一个新的文件
"a"追加,在文件的末尾追加数据,打开一个文本文件创建一个新的文件
"rb"只读,为了读取数据,打开一个二进制文件出错
"wb"只写,为了写入数据,打开一个二进制文件创建一个新的文件
"ab"追加,在二进制文件末尾添加数据创建一个新的文件
"r+"读写,打开一个文本文件出错
"w+"读写,打开一个文本文件创建一个新的文件
"a+"读写,在文件的末尾进行读写创建一个新的文件
"rb+"读写,打开了二进制文件出错
"wb+"读写,打开一个二进制文件创建一个新的文件
"ab+"读写,打开一个二进制文件创建一个新的文件
  1. r是读(read),w是写(write),a是追加(append)
  2. 所有只能读的文件,如果不存在,就会报错。而写和追加就会创建新的文件
  3. b是二进制的意思(binary)
  4. 后面有+,就是既可以读又可以写。

实例代码:

#include<stdio.h>
int main()
{FILE* pf = fopen("text.txt","w");if (pf == NULL) {perror("fopen");return 1;};fputs("file open example",pf);fclose(pf);pf = NULL;return 0;
}

五.文件的顺序读写

5.1文件的顺序读写函数

观察下面这个代码:

	//打开文件FILE* pf = fopen("text.txt","w");if (pf == NULL){perror("fopen");return 1;}//访问文件char arr[10] = "abcde";fwrite(arr,1,5,pf);//关闭文件fclose(pf);pf = NULL;

当我们打开这个txt文件,我们发现fwrite就向这个文件中写了五个字符abcde。

5.1.1fgetc函数

 函数原型:

int fgetc ( FILE * stream );

这个函数的功能是从流中获得字符。返回又文件中的光标所指向的字符 。

在我们运行完上面的的代码后,我们再次运行以下代码.

	int ch1 = fgetc(pf);int ch2 = fgetc(pf);int ch3 = fgetc(pf);printf("%c %c %c ", ch1, ch2, ch3);

我们发现输出结果是 a b c,为什么呢?

这是因为 指定流的内部文件位置指示器,最开始是在开头的,每一次调用函数后,它的位置会往后移动一个字符,所以会依次打印a b c。

当遇到文件末尾和读取失败的时候,这个函数会返回EOF.

如果需要打印完整个文件就可以采用循环的方式

	int ch = 0;while ((ch = fgetc(pf)) != EOF){printf("%c", ch);}

5.1.2fputc函数

函数原型:

int fputc ( int character, FILE * stream );

这个函数的功能就是将字符写到流中,并且使位置指示器前进一个字符。

	//写文件fputc('a',pf);fputc('b',pf);fputc('c',pf);//读文件int ch = 0;while ((ch = fgetc(pf)) != EOF){printf("%c", ch);}

在这个文件中显示的就是abc

在文件中打印26个字母:

	for(char ch = 'a'; ch <= 'z';ch++){fputc(ch,pf);}

 5.1.3fgets函数

fgetc是从流中得到字符,fgets就是从流中得到字符串

函数原型:

char * fgets ( char * str, int num, FILE * stream );

得到的字符串需要放在一个字符数组中,第一个参数就是数组名。

第二个参数是我们需要读取的字符个数。

第三个就是流

	char str[26] = { 0 };fgets(str,26,pf);printf(str);

运行这个代码输出结果是 

这是我们刚刚输入的26个字母。但是我们发现少了字符'z'。

通过调试我们发现 

 

这个字符数组的最后一个字符不是'z',而是'\0'。

这是因为fgets这个函数会默认给最后一个字符补上'\0',所以如果我们想完整打印出26个字母,参数2就不应该是26而应该是27,因为字符串的末尾均是'\0',所以需留'\0'。

这个函数在进行读取的时候,在遇到第num-1 个字符,和新的一行,或者文件末尾,均会停止读取,但是不会影响函数功能。前面读取的字符不会收到影响。

注意:在遇到换行符时,这个符号仍然会被包含进这个数组中。

5.1.4fputs函数

这个函数和fputc函数很相似。

函数原型:

int fputs ( const char * str, FILE * stream );

 写一个字符串到流中去。

参数1就是字符串。

	fputs("hello world",pf);

运行这个代码,我们就可以在文件看到hello world.注意末尾的'\0'是不会被写到流中去的。

5.2scanf和printf

5.2.1fscanf函数

我们知道scanf是从键盘上读取格式化的数据

那么fscanf就是从文件中读取格式化的数据

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

这个函数的函数原型与scanf几乎一模一样,只不过多了一个文件指针而已。

//定义一个结构体
struct stu
{char name[10];int age;int score;
};

在文件中输入张三 18 99

再运行以下代码

	struct stu student;fscanf(pf, "%s %d %d", student.name,&(student.age),&(student.score));printf("%s %d %d ",student.name,student.age,student.score);

输出结果就是张三 18 99.

 sscanf也是同理,从字符串中读取数据,本文只涉及文件相关内容,想要了解的读者,可以自行了解:sscanf - C++ Reference

 5.2.2sprintf函数

函数原型:

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

这个函数同理与printf函数类似,多了一个文件指针。从在屏幕上打印变成了在文件中打印

	struct S s = { "李四",28,95};fprintf(pf, "%s %d %d\n", s.name, s.age, s.score);

这样我们就可以在文件中看到这个结构体的信息。当然其实也是可以在屏幕上打印的。

stdout就是标准输出流,把文件指针写成stdout就可以在屏幕上打印,起到和printf一样的效果。

同理前面的函数也可以使用

int main()
{fputc('a', stdout);return 0;
}

这样就会在屏幕上打印a.

六.文件的随机读写

6.1fseek函数 

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

 这个函数的功能就是改变位置指示器的位置。

参数1是文件指针

参数2是偏移量

参数3有三种文件开头,文件末尾,和位置指示器当前位置

SEEK_SET就是文件开头,SEEK_END是文件末尾,SEEK_CUR是当前位置。

例:

	FILE* pf = fopen("test.txt","wb");fputs("This is an apple.",pf);fseek(pf,9,SEEK_SET);fputs(" sam",pf);

这个代码的作用就是将位置指示器从文件开头偏移9个字符。

This is an apple.

偏移9个字符后,位置指示器的位置就到了第一个字符a的后面,然后打印" sam".

在我们写代码的过程中,我们快速并且准确的知道当前 位置指示器的位置,那怎么办呢?
这时候就需要用到第二个函数

6.2ftell函数

long int ftell ( FILE * stream );

这个函数会返回在流中的位置指示器相对于起始位置的偏移量。

#include <stdio.h>
int main()
{FILE* pFile;long size;pFile = fopen("test.txt", "rb");if (pFile == NULL)perror("Error opening file");else{fseek(pFile, 0, SEEK_END); size = ftell(pFile);fclose(pFile);printf("Size of myfile.txt: %ld bytes.\n", size);}return 0;
}

偏移量的值是可以为0也可以为负和正的,正就是向右移动,负就是向左,0是原位置。 

这个代码计算了这个文件有多少个字节。

 6.3rewind函数

void rewind ( FILE * stream );

这个函数会将流的位置设置到文件开头。 

 例子:

#include<stdio.h>
int main()
{int n;FILE* pFile;char buffer[27];pFile = fopen("myfile.txt", "w+");for (n = 'A'; n <= 'Z'; n++)fputc(n, pFile);rewind(pFile);fread(buffer, 1, 26, pFile);fclose(pFile);buffer[26] = '\0';printf(buffer);return 0;
}

使用fseek函数也可以达到相同的效果

	fseek(pFile,0,SEEK_SET);

七.文件读取结束的判定

feof的作用是当文件读取结束的时候,判断文件读取结束的原因是不是遇到文件结尾而结束的。

ferror的作用是当文件读取结束的时候,判断文件读取结束的原因是不是发生错误而结束的。

 1.文本文件

文本文件的读取结束,判断返回值是否为EOF(fgetc函数),判断是否等于NULL(fgets)

 2.二进制文件

判断返回值是否小于实际要读取的个数(fread函数)

#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);
}
#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) {puts("Array read successfully, contents: ");for (int n = 0; n < SIZE; ++n)printf("%f ", b[n]);putchar('\n');}else { // error handlingif (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编译系统决定的。
#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
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;
}
因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。如果不做,可能会存在文件读写出现问题。

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

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

相关文章

OpenCV-python安装教程

先安装opencv-contrib-python pip install opencv-contrib-python 再换源安装opencv-python pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 如果出现 使用这个&#xff0c;3.6环境下不能安装opencv的最新版本 pip install opencv-python4.5.5.62…

基于SpringBoot+Vue的电影票管理系统的设计和实现【附源码】

1、系统演示视频&#xff08;演示视频&#xff09; 2、需要交流和学习请联系

机台数据传输共享存在哪些问题?机台数据管控怎么做?

一些金融机构、大型制造业以及晶圆制造厂里面&#xff0c;都会存在大量的机台设备&#xff0c;这些机台会产⽣庞⼤⽽属性不同的数据&#xff0c;这些数据需要定期的进行采集和利用。机台数据在传输分享过程中&#xff0c;会面临各种问题和调整&#xff0c;所以需要做好机台数据…

CSS——精灵图

CSS——精灵图 目录 CSS——精灵图什么是精灵图&#xff1f;导入精灵图裁剪精灵图使用精灵图方式1方式2 什么是精灵图&#xff1f; 精灵图&#xff08;Spritesheet&#xff09;是指将多个小图标、图像或动画合并到一个大图像中的技术。在网页设计和游戏开发中&#xff0c;精灵…

使用 CloudDM 操作 PostgrgSQL 数据库

CloudDM 简介 CloudDM 是 ClouGence 公司推出的一款一站式数据库管理工具&#xff0c;使用它可以方便地访问和管理 MySQL、Oracle、PostgreSQL、阿里云 RDS、Greenplum、TiDB、Redis、StarRocks、Doris、SelectDB、SQL SERVER、ClickHouse、OceanBase 、PolarDB-X 、IBM Db2 等…

AI结合机器人的入门级仿真环境有哪些?

由于使用真实的机器人开发和测试应用程序既昂贵又费时&#xff0c;因此仿真已成为机器人应用程序开发中越来越重要的部分。在部署到机器人之前在仿真中验证应用程序可以通过尽早发现潜在问题来缩短迭代时间。通过模拟&#xff0c;还可以更轻松地测试在现实世界中可能过于危险的…

图的深度优先遍历DFS得到各节点的度

在 一文中&#xff0c;我们通过了广度优先搜索来得到图结构中各结点的度&#xff0c;在这一篇文章中&#xff0c;我们要通过深度优先搜索来得到图结构中各结点的度。 初始化 初始化&#xff0c;在下面的代码中&#xff0c;我们创建了一个具有6个结点的图结构&#xff0c;以及…

MHA高可用-解决MySQL主从复制的单点问题

目录 一、MHA的介绍 1&#xff0e;什么是 MHA 2&#xff0e;MHA 的组成 2.1 MHA Node&#xff08;数据节点&#xff09; 2.2 MHA Manager&#xff08;管理节点&#xff09; 3&#xff0e;MHA 的特点 4. MHA工作原理总结如下&#xff1a; 二、搭建 MySQL MHA 实验环境 …

NoSQL之Redis

目录 一、关系型数据库与非关系型数据库 1.关系数据库 2.非关系数据库 2.1非关系型数据库产生背景 3.关系型数据库与非关系型数据区别 &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 &#xff08;3&#xff09;对事物性的支持不同 …

VSCode常用修改默认设置(settings.json)

❓ 问题1 我现在在vscode中鼠标选中某个单词&#xff0c;相同的单词都会自动出现一个高亮背景色&#xff0c;我需要怎么关闭这个功能呢&#xff1f; ⚠️ 注意 selectionHighlight 这个是鼠标双击后的高亮匹配&#xff0c;可以保留默认开启的配置&#xff0c;不用去改它。 …

智能革命:ChatGPT3.5与GPT4.0的融合,携手DALL·E 3和Midjourney开启艺术新纪元

迷图网(kk.zlrxjh.top)是一个融合了顶尖人工智能技术的多功能助手&#xff0c;集成了ChatGPT3.5、GPT4.0、DALLE 3和Midjourney等多种智能系统&#xff0c;为用户提供了丰富的体验。以下是对这些技术的概述&#xff1a; ChatGPT3.5是由OpenAI开发的一个自然语言处理模型&#x…

Git的简单入门使用

文章目录 拷贝项目的步骤创建项目的步骤提交项目或项目文件的步骤恢复项目文件的步骤 拷贝项目的步骤 找到需要用来存放项目的文件夹&#xff1b;在文件夹页面空白处右键点击&#xff0c;然后再菜单中选择“Open Git Bash here”。在Github上找到需要进行拷贝的项目&#xff0…