文件操作【详解】

目录

一、什么是文件

二、文件的打开和关闭

1.文件指针

2.文件的打开和关闭

3.文件的打开方式

三、文件的顺序读写

1.关于输入输出,读和写

2.关于流的介绍

3.操作文件的函数

[字符输入函数 fgetc的使用](#字符输入函数 fgetc的使用)

[字符输入函数 fputc的使用](#字符输入函数 fputc的使用)

[文本行输出函数 fputs()](#文本行输出函数 fputs())

[文本行输入函数 fgets](#文本行输入函数 fgets)

[格式化输出函数 fprintf()](#格式化输出函数 fprintf())

[格式化输入函数 fscanf()](#格式化输入函数 fscanf())

[二进制输出 fwrite](#二进制输出 fwrite)

[二进制输入 fread (从文件到内存)](#二进制输入 fread (从文件到内存))

四、文件的随机读写

1.fseek

2.ftell

3.rewind

五、文本文件和二进制文件

六、文件读取结束的判定

七、文件缓冲区


前言 :当初在通讯录上的数据都是临时存放到内存中的,当程序运行结束的时候,所添加的数据就没有了。等到下一次运行通讯录的时候,数据又需要重新录入,我们发现这样会很繁琐,我们就想着用什么来存着这些数据一直保留着。

于是 这个问题就涉及到数据持久化 的问题了,我们一般数据持久化的方法有:数据存放到磁盘文件中、存放到数据库中等方式。所以这里就提到了文件

一、什么是文件

平时我们说的硬盘上的文件就文件

当然在程序设计中,我们一般说到的文件有两种:程序文件、数据文件(从文件功能上来看)。

1.程序文件

  • 源程序文件 (后缀 .c)
  • 目标文件(.obj)
  • 可执行程序(.exe)

2.数据文件

程序运行时读写的数据,需要输入输出的文件

3. 文件名

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

例如 d:\code\test.txt

二、文件的打开和关闭

1.文件指针

在缓冲文件系统中,关键的概念是 文件类型指针 , 简称 文件指针

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

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

创建一个FILE*的指针变量

cpp 复制代码
FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件

2.文件的打开和关闭

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

在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系

对于打开文件我们一般使用的是 fopen()函数,然后使用fclose()函数来关闭文件

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

注意当直接写文件主干时,文件是用相对路径来存放的

3.文件的打开方式

还有关于二进制文件

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

三、文件的顺序读写

按顺序进行文件的读写

1.关于输入输出,读和写

首先这是一个相对而言的,例如 相对于一个人而言 读课本是输入(就比如知识存到大脑),然后 写博客就是 输出(把大脑里面的知识输出到博客文章中)。

2.关于流的介绍

这是有些人就有一些疑问,当我们是printf , scanf 又是怎么一回事呢?

通常把显示器称为标准输出文件 printf() 就向这个文件输出数据

通常把键盘称为标准输入文件 scanf() 就向这个文件读取数据

c 语言运行起来 默认打开 三个流

  • 标准输入流stdin
  • 标准输入流stdout
  • 标准错误流stderr

所以我们要记得关闭文件

是一个抽象的概念

IO文件流 : 输入流:数据从文件复制到内存的过程; 输出流:数据从内存保存到文件的过程。

在IO文件流中,是相对于计算机程序中的内存来说的

3.操作文件的函数

字符输入函数 fgetc的使用

cpp 复制代码
int fgetc ( FILE * stream );

这里的 int 返回值 会接收到的内容,当遇到文件末尾,读取失败会返回EOF

cpp 复制代码
#include<stdio.h>
int main()
{
	//使用fopen()打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n",ch);
	ch = fgetc(pf);
	printf("%c\n", ch);
	//使用fclose()关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

使用stdin 从键盘上读

cpp 复制代码
#include<stdio.h>
int main()
{
	//使用fopen()打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(stdin);
	printf("%c\n", ch);//从键盘上读
	ch = fgetc(stdin);
	printf("%c\n", ch);
	//使用fclose()关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

字符输入函数 fputc的使用

cpp 复制代码
int fputc ( int character, FILE * stream );
cpp 复制代码
#include<stdio.h>
int main()
{
	//使用fopen()打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件,写一个字符放到流中
	fputc('a',pf);
	fputc('c',pf);
	//使用fclose()关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

打开文件查看

之前介绍的stdout 也可以不写入到文中,写入到屏幕上

cpp 复制代码
#include<stdio.h>
int main()
{
	//使用fopen()打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件,写一个字符放到流中
	//stdout 写入到屏幕上
	fputc('a', stdout);
	fputc('c', stdout);
	//使用fclose()关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

上述是文件的顺序读写

文本行输出函数 fputs()

从程序输出到文件中(写)

cpp 复制代码
int fputs ( const char * str, FILE * stream );
cpp 复制代码
#include<stdio.h>
int main() 
{
	FILE* pf = fopen("data.txt","w");
	if (pf == NULL) 
	{
		perror("fopen");
		return 1;
	}
	//写文件  一行的写
	fputs("hello\n",pf);
	fputs("world\n",pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

文本行输入函数 fgets

cpp 复制代码
char * fgets ( char * str, int num, FILE * stream );

要木遇到最多读n - 1 个,后面再追加一个'\0',要木遇到\n 不再读了

cpp 复制代码
//一行的读
#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件  一行的读
	char arr[10] = {0};
	fgets(arr,10,pf); //这里只读了9个. 要木遇到最多读n - 1 个,后面再追加一个'\0',要木遇到\n 不再读了
	printf("%s",arr);
	fclose(pf);
	pf = NULL;
	return 0;
}

格式化输出函数 fprintf()

cpp 复制代码
int fprintf ( FILE * stream, const char * format, ... );

Write formatted data to stream 写格式化数据到流里面去

cpp 复制代码
#include<stdio.h>
struct S 
{
	int a;
	float b;
};
int main() 
{
	FILE* pf = fopen("data.txt","w");
	if (pf == NULL) 
	{
		perror("fopen");
		return 1;
	}

	//写文件 从程序中写入文件
	struct S s = {100,3.14f};
	fprintf(pf,"%d %f",s.a,s.b);

	fclose(pf);
	pf = NULL;
	return 0;
}

格式化输入函数 fscanf()

cpp 复制代码
int fscanf ( FILE * stream, const char * format, ... );

Read formatted data from stream把带有格式的数据从流里面读出

cpp 复制代码
#include<stdio.h>
struct S
{
	int a;
	float b;
};
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件 把文件里面的数据读到程序里面去
	struct S s = {0};
	fscanf(pf,"%d %f",&(s.a),&(s.b));
	//这里把数据打印出来
	printf("%d %f",s.a,s.b);
	//这里把数据打印出来的第二种方式
	//fprintf(stdout,"%d %f",s.a,s.b);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

区分:

// sprintf 和 sscanf

cpp 复制代码
#include<stdio.h>
struct S
{
	int a;
	float b;
	char str[10];
};
int main()
{
	char arr[30] = { 0 };
	struct S s = { 100,3.14f,"hello" };//将结构体里面的数据转换为字符串arr里面
	struct S tmp = {0};
	sprintf(arr, "%d %f   %s", s.a, s.b, s.str);//里面的空格也会转换到arr里面

	//从字符串中拿出格式化数据
	sscanf(arr,"%d %f %s",&(tmp.a),&(tmp.b),tmp.str);//从字符串arr中读取格式化数据
	printf("%d %f %s", tmp.a,tmp.b,tmp.str);
	return 0;
}

二进制输出 fwrite

cpp 复制代码
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
cpp 复制代码
#include<stdio.h>
struct S 
{
	int a;
	float b;
	char str[10];
};
int main() 
{
	struct S s = {100,3.14f,"zz"};
	FILE* pf = fopen("data.txt","wb");
	if (pf == NULL) 
	{
		perror("fopen");
		return 1;
	}

	//写文件
	fwrite(&s,sizeof(struct S),1,pf);

	fclose(pf);
	pf = NULL;
	return 0;
}

打开文件 我们发现 看不明白(因为是二进制文件)

可以使用fread 来读二进制文件

二进制输入 fread (从文件到内存)

cpp 复制代码
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

Read block of data from stream

cpp 复制代码
//fread
#include<stdio.h>
struct S
{
	int a;
	float b;
	char str[10];
};
int main()
{
	struct S s = { 0 };
	FILE* pf = fopen("data.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	fread(&s,sizeof(struct S),1,pf);
	//打印
	printf("%d %f %s",s.a,s.b,s.str);
	fclose(pf);
	pf = NULL;
	return 0;
}

四、文件的随机读写

这里的文件随机读写 是 想读写哪里就读写哪里

1.fseek

cpp 复制代码
int fseek ( FILE * stream, long int offset, int origin );

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

注意origin的参数

  • SEEK_SET 从文件的起始位置开始
  • SEEK_CUR 从当前所指向的位置开始
  • SEEK_END 从文件的末尾开始 (注意从右向左 数字为负的)
cpp 复制代码
//fseek的使用
#include<stdio.h>
int main() 
{
	FILE* pf = fopen("data.txt","r");
	//文件里面的内容为abcdefghi
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf,3,SEEK_SET);//定位文件指针的指向,这里的正3表示从左到右
	int ch = fgetc(pf);
	printf("%c\n",ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

图解

2.ftell

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

cpp 复制代码
long int ftell ( FILE * stream );
cpp 复制代码
//ftell返回文件指针相对于起始位置的偏移量
#include<stdio.h>
int main() 
{
	FILE* pf = fopen("data.txt","r");
	//文件内容是abcdefghi
	if (pf == NULL) 
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n",ch);//a
	ch = fgetc(pf);
	printf("%c\n", ch);//b
	ch = fgetc(pf);
	printf("%c\n", ch);//c
	//用ret来接收偏移量
	int ret = ftell(pf);
	printf("%d\n",ret);//3  这里只适用于标准输出流(屏幕)
	fprintf(stdout,"%d",ret);//3 适用于所有的输出流
	fclose(pf);
	pf = NULL;
	return 0;
}

3.rewind

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

cpp 复制代码
void rewind ( FILE * stream );
cpp 复制代码
//rewind 让文件指针的位置 回到 文件的起始位置
#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	//文件内容是abcdefghi
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n", ch);//a
	ch = fgetc(pf);
	printf("%c\n", ch);//b
	ch = fgetc(pf);
	printf("%c\n", ch);//c
	//用ret来接收偏移量
	int ret = ftell(pf);
	printf("%d\n", ret);//3  这里只适用于标准输出流(屏幕)

	//使用rewind 回到起始位置
	rewind(pf);
	int a = ftell(pf);// 0
	fprintf(stdout,"%d",a);

	fclose(pf);
	pf = NULL;
	return 0;
}

五、文本文件和二进制文件

文本文件

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

二级制文件

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

例如 十进制的10000 看下方图解

例如

cpp 复制代码
#include<stdio.h>
int main() 
{
	int a = 10000;
	FILE* pf = fopen("data.txt","wb");
	if (pf == NULL) 
	{
		perror("fopen");
		return 1;
	}
	fwrite(&a,5,1,pf);//写到二进制文件中
	fclose(pf);
	pf = NULL;
	return 0;
}

我们已经把数据以二进制形式写入到文件中,但是当我们打开时发现

这是二进制文件,我们需要用特殊的编辑器来查看,就比如使用 Visual Studio 2022 来进行查看

下面是查看方法:

第一步 先 右击 源文件 - 添加 - 现有项

第二步 找到文本并添加进去

这时就会看到

第三步 右击 文件 - 点击打开方式

第四步 选择二进制编辑器

然后就有了

六、文件读取结束的判定

  • fgetc 判断是否为EOF
  • fgets 判断返回值是否为NULL
  • fread 判断返回值是否小于实际要读的个数

在这里 要多说一个 feof

feof: 用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

即 feof 用来判断是什么原因结束的

七、文件缓冲区

文件缓冲区是指在操作系统中,将文件读取或写入时,先将数据缓存到内存中的一段空间,等待一定量的数据积累后再进行实际的读写操作 。这样做的好处是可以提高文件输入和输出效率,减少文件系统的负担。而且通过缓冲,可以减少磁盘或网络的读写次数,降低系统负担,提高程序的性能。

文件缓冲区包括输入缓冲区和输出缓冲区。输入缓冲区用于存储等待处理的输入数据,而输出缓冲区用于存储等待写入文件的数据。当输入缓冲区或输出缓冲区已满或达到一定数量时,系统才会进行实际的读写操作。当文件操作完成时,缓冲区的内容会被写入或者读取出来,或者在程序运行结束时被自动释放。

在编程中,我们可以通过控制文件缓冲区大小和刷新缓冲区来提高程序效率。如果希望立即将缓冲区的数据写入磁盘或读取最新数据,可以使用flush()、fflush()等函数来强制刷新缓冲区。

cpp 复制代码
#include<stdio.h>
#include<windows.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//
	fprintf(pf,"hello\n");
	fflush(pf);  //刷新后就会出现在文件中
	Sleep(10000);//睡眠10秒
	fprintf(pf,"world");
	fclose(pf);
	pf = NULL;
	return 0;
}
相关推荐
xinghuitunan13 分钟前
蓝桥杯顺子日期(填空题)
c语言·蓝桥杯
Half-up15 分钟前
C语言心型代码解析
c语言·开发语言
懒大王就是我1 小时前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
半盏茶香1 小时前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
小堇不是码农1 小时前
在VScode中配置C_C++环境
c语言·c++·vscode
小肥象不是小飞象1 小时前
(六千字心得笔记)零基础C语言入门第八课——函数(上)
c语言·开发语言·笔记·1024程序员节
励志成为嵌入式工程师6 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
Peter_chq7 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
hikktn8 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
观音山保我别报错8 小时前
C语言扫雷小游戏
c语言·开发语言·算法