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

高阶C语言之五:(数据)文件

目录

文件名

 文件类型

文件指针

 文件的打开和关闭

文件打开模式 

文件操作函数(顺序)

0、“流”

1、字符输出函数fputc

2、字符输入函数fgetc 

3、字符串输出函数fputs

4、 字符串输入函数fgets

 5、格式化输入函数fscanf

6、格式化输出函数fprintf

7、二进制输入函数fread

8、二进制输出函数fwrite

 几种输入输出

文件随机读写函数

1、fseek:定位指针

2、ftell:返回指针位置

3、rewind:重定位指针

4、实例

 文本文件和二进制文件

 文件读取结束的判定

1、被错误使用的feof函数

2、判断文件是否读取结束:

3、实例

文件缓冲区


改造通讯录(可保存信息):month_11/test_12 · Hera_Yc/bit_C_学习 - 码云 - 开源中国

本文相关代码:month_11/test_16/main.c · Hera_Yc/bit_C_学习 - 码云 - 开源中国

文件:即硬盘上的文件,文件将数据直接存放在电脑的硬盘上,做到了数据的持久化

在程序设计当中,程序员所说的文件一般分为两种:程序文件、数据文件。

  • 程序文件:包括源程序文件(后缀为 .c )、目标文件(windows环境后缀为 .obj )、可执行程序文件(windows环境后缀为 .exe )。
  • 数据文件:文件的内容不一定是程序,而是程序运行时读写的数据。

个人对文件操作的理解:

  1. 文件操作这里的本质就是一堆库函数,懂得这些库函数就可以对函数进行操作了。
  2. 文件的使用与动态内存类似,需要“开辟”和“释放”,即文件的打开和关闭。
  3. 相对于内存来说,文件(硬盘)是一种外部设备。

文件名

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

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

如:"C:\Users\Desktop\newc++file.cpp"

文件标识通常称为文件名

windows和Linux允许没有后缀名的文件。

 文件类型

包含在头文件<stdio.h>中

Q:什么是文件类型?

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

 文件类型声明:

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

        文件类型并不是真正文件的类型,而是将硬盘文件的一些信息作为一组数据存放在内存中,CPU通过这组数据来对文件进行操作。 

        在对文件进行操作时,可以将文件看作一个FILE类型的变量,FILE类型的变量是在使用文件时,系统自动创建的。

文件指针

FILE* pf;

文件指针:定义pf是一个指向FILE类型的数据变量。通过文件指针变量能够找到与它关联的文件


 文件的打开和关闭

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

文件的使用:打开文件--->读出\写入数据--->关闭文件。

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

函数声明:

FILE* fopen(const char* filename, const char* mode);
//filename :文件名
//mode     :打开模式

int fclose(FILE* stream);
//stream   :文件指针
文件打开模式 
  • 'r'模式:这是“只读”模式。如果文件不存在,尝试打开将失败。文件指针位于文件开头,不会清空文件原有内容。

  • 'w'模式:这是“只写”模式。如果文件存在,它将被清空并从头开始写入;如果文件不存在,将创建一个新文件。文件指针位于文件开头。

  • 'a'模式:这是“追加”模式。如果文件存在,写入的数据将添加到文件末尾,不会清空原有内容;如果文件不存在,将创建一个新文件。文件指针位于文件结尾。

  • 'r+'模式:这是“读写”模式。文件必须存在,文件指针位于文件开头。可以在文件任意位置读取或写入内容,写入操作会覆盖原有位置的内容。

  • 'w+'模式:这也是“读写”模式。它类似于'w'模式,但是它允许读取操作。打开文件后,会清空文件内原有的内容。

  • 'a+'模式:这同样是“读写”模式。它类似于'a'模式,但是它允许读取操作。写入内容时,只会追加在文件尾部。

  • 还有很多操作模式,感兴趣读者自行查阅。

实例:

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");//fopen的参数是:路径 、操作
	if (pf == NULL)
	{
		perror("fopen");
        //perror等价于printf("open:%s\n",strerror(errno));
		return 1;
	}
	//文件操作
	//...

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件操作函数(顺序)

头文件<stdio.h>

0、“流”

文件操作函数中的输入输出是相对于内存而言的:

        屏幕和键盘也算一种外部设备(通过流),为什么C程序不用主动的去打开或关闭这些外部设备?

1、任何一个C程序会默认打开3个流

  • FILE* stdin  - 标准输入流 (键盘)
  • FILE* stdout  - 标准输出流 (屏幕)
  • FILE* stderr  - 标准错误流 (屏幕)
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.arr, &(s.age), &(s.score));
	//printf("%s %d %f", s.arr, s.age, s.score);
	//等价于
	fprintf(stdout, "%s %d %f", s.arr, s.age, s.score);

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

1、字符输出函数fputc

函数声明:

int fputc( int c, FILE *stream );

使用:

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");// w:写入模式
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	char i = 0;
	for (i = 'a'; i < 'f'; i++)
	{
		fputc(i, pf);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}
2、字符输入函数fgetc 

函数声明:

int fgetc( FILE *stream );

使用:

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");// r:读取模式
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	int ch = '\0';
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c\n", ch);
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
3、字符串输出函数fputs

声明:

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

使用:

4、 字符串输入函数fgets
char *fgets( char *str, int n, FILE *stream );
//str:数据的存储位置
//n:要读取的最大字符数
//stream:文件指针

使用:

 5、格式化输入函数fscanf
int fscanf( FILE *stream, const char *format [, argument ]... );

使用:

 

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.arr, &(s.age), &(s.score));
	//printf("%s %d %f", s.arr, s.age, s.score);
	//等价于
	fprintf(stdout, "%s %d %f", s.arr, s.age, s.score);

	fclose(pf);
	pf = NULL;
	return 0;
}
6、格式化输出函数fprintf
int fprintf( FILE *stream, const char *format [, argument ]...);

使用:

7、二进制输入函数fread
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

 使用:

int main()
{
	struct S s = { 0};

	FILE* pf = fopen("test.txt", "rb");//rb:二进制读模式
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//以二进制形式写入到文件中
	fread(&s, sizeof(struct S), 1, pf);//读二进制数据,放在s中
	printf("%s %d %f", s.arr, s.age, s.score);
	fclose(pf);
	pf = NULL;
	return 0;
}
8、二进制输出函数fwrite
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
//buffer:待写入数据的地址
//size:待写入数据的大小
//count:最大写入项数
//stream:指向 FILE 结构的指针

使用:

 几种输入输出

int sprintf( char *buffer, const char *format [, argument] ... );
int sscanf( const char *buffer, const char *format [, argument ] ... );

使用:

int main()
{
	struct S s = { "zhangsan",20,50.5f };
	struct S tmp;
	char buf[100] = { 0 };
	//把s中的格式化数据转化成字符串放到buf中
	sprintf(buf, "%s %d %f", s.arr, s.age, s.score);

	printf("字符串:%s\n", buf);//这里打印的就是一个字符串了

	//从字符串buf中获取一个格式化数据到tmp中
	sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.score));
	printf("格式化:%s %d %f \n", tmp.arr, tmp.age, tmp.score);
	return 0;
}

补充:动态内存和文件、输入输出、流 

文件随机读写函数

1、fseek:定位指针
int fseek( FILE *stream, long offset, int origin );
//offset:偏移量
//origin:起始位置

fseek通过起始位置+偏移量的方法定位指针 。

origin由三种取值:

  • SEEK_SET:文件起始位置
  • SEEK_CUR:当前指针位置
  • SEEK_END:文件末尾
2、ftell:返回指针位置
long ftell( FILE *stream );
//返回指针相对于起始位置的偏移量
3、rewind:重定位指针
void rewind( FILE *stream );
//使指针返回到起始位置
4、实例
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("Fopen");
		return 1;
	}

	//当前文件中存放:abcdef
	
	//定位文件指针
	fseek(pf, -2,SEEK_END );
	//当前pf位置 =  -2 + SEEK_END 

	printf("%d\n", ftell(pf));//4
	//相对于文件起始位置的偏移量

	int ch = fgetc(pf);
	printf("%c\n", ch); // 打印的是e

	rewind(pf);//将指针返回到文件起始位置
	printf("%d\n", ftell(pf));//0

	fclose(pf);
	pf = NULL;
}

 文本文件和二进制文件

根据数据的组织形式,数据文件分为:

  • 二进制文件:把内存中的二进制数据,不加任何转化输出到外存中去,就是二进制文件。
  • 文本文件:在外存中以ASCII码的形式存放,在存储前需要转换。


 文件读取结束的判定

1、被错误使用的feof函数

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

int ferror( FILE *stream );
//ferror函数用于检测文件读写过程中是否产生了错误。
int feof( FILE *stream );
//feof函数用于检测是否已到达文件末尾(EOF,End Of File的缩写)
2、判断文件是否读取结束:
  1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc函数),或者为NULL。
  2. 二进制文件读取结束判断,判断返回值是否小于实际要读取的个数。

对于文本文件:

        fgetc:返回值为NULL或者EOF,表示文件读取结束,可以利用feof来对fgetc的返回值进行判断,来确定fgetc为什么读取结束。

对于二进制文件:

        fread:返回值是实际读到的元素的个数,参数count是要求读到的个数。比较返回值和count的关系,就可以判断二进制文件读取是否结束。

3、实例

 ferror和feof两者经常搭配使用:(以二进制读写为例)

enum { SIZE = 5 };

int main()
{
	double a[SIZE] = { 1,2,3,4,5 };
	FILE* fp = ("test.txt", "wb");
	if (fp == NULL)
	{
		perror("Fopen");
		return 1;
	}
	fwrite(a, sizeof(*a), SIZE, fp);
	fclose(fp);

	double b[SIZE];
	fp = fopen("test.txt", "rb");
	if (fp == NULL)
	{
		perror("Fopen");
		return 1;
	}
	size_t ret_code = fread(b, sizeof(*b), 8, fp);
	if (ret_code == SIZE){
		puts("数组读取成功:");
		int i = 0;
		for (i = 0; i < SIZE; i++)printf("%f ", b[i]);
		putchar('\n');
	}
	else{
		if (!feof(fp))
			printf("未达到文件末尾\n");
		else if (ferror(fp)){
			printf("读取错误\n");
		}
	}
	fclose(fp);
	fp = NULL;
	return 0;
}

文件缓冲区

(操作系统会对缓冲区进行详细的讲解,缓冲区也有相应的库操作函数)

        文件缓冲区是内存区预留的一定空间,用以暂时存放读写期间的文件数据。其主要目的是减少读取硬盘的次数,因为硬盘的读写速度相对较慢,而内存的读写速度较快。通过缓冲区,系统可以先将数据读入内存,然后再从内存中读取或写入数据,从而减少了对硬盘的直接访问,提高了数据处理的效率。

 缓冲区存在的意义

  1. 提高数据读写效率:通过缓冲区,系统可以减少对硬盘的直接访问次数,从而提高数据读写的效率。
  2. 保护硬盘:频繁的硬盘访问会加速硬盘的磨损和老化。通过缓冲区,系统可以减少对硬盘的访问次数,从而延长硬盘的使用寿命。
  3. 提高系统稳定性:缓冲区可以平滑数据传输过程中的波动和延迟,提高系统的稳定性和可靠性。

        因为有缓冲区的存在,C语言在操作文件的时候,需要刷新文件缓冲区或在文件操作结束时关闭文件。(fclose,也会自动刷新缓冲区)。

---------------------------------------------------文件到此结束------------------------------------------------------------

         文件了解即可。其实文件没有那么重要,知道最初识得文件操作函数即可,因为在实际项目开发的时候,不可能直接对文件来进行读写,而是利用数据库来存取数据,更加高效。


http://www.kler.cn/a/401802.html

相关文章:

  • python学习_2.去除字符strip方法
  • Web开发:ORM框架之使用Freesql的DbFrist封装常见功能
  • 汽车与摩托车分类数据集
  • 内网安全隧道搭建-ngrok-frp-nps-sapp
  • 3、C语言基本数据类型关键字
  • .NET 简介
  • 【Java】ArrayList与LinkedList详解!!!
  • 一种由于吸入硅酸盐粉尘而引起的肺部疾病:pneumonoultramicroscopicsilicovolcanoconiosis
  • 【java-ffmpeg】java 内存测试和集成
  • 第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令
  • 打开AI的黑盒子——机器学习可解释性!
  • 网络安全进阶
  • 【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
  • 在MacOS中Finder中通过路径来导航
  • RabbitMQ高可用
  • 《Python浪漫的烟花表白特效》
  • 【动手做】安装Miniconda和jupyter notebook环境实现线性回归
  • Conda 安装纯净版ComfyUI
  • 使用EventLog Analyzer日志分析工具监测 Windows Server 安全威胁
  • 【WPF】Prism学习(五)
  • 无人机航测技术算法概述!
  • ubuntu20.04的arduino+MU编辑器安装教程
  • C++代码优化(五):虚函数的开销和优化方式
  • 初始Python篇(6)—— 字符串
  • 人工智能学习——前言
  • 2024年第十四届APMCM亚太杯数学建模A题B题C题思路+代码解析汇总