Operator File(操作文件)

Operator File - 操作文件

今天要讲的是 Operator File(操作文件),废话不多说。

在这里插入图片描述

本文看点


让我们开始吧!

在这里插入图片描述

1.什么是文件?

文件通俗来说,就是我们磁盘上的文件就是文件,看到下方附图,大家就说明都明白了文件的基本意思。(至于电脑有什么隐晦的文件,我什么都不知啊)
在这里插入图片描述

但是在我们程序设计中,我们一般谈的文件有两种:程序文件;二进制文件

程序文件:

包括源文件(后缀 ,c)、目标文件(后缀 .obj)、可执行文件(后缀 .exe)
在这里插入图片描述
如果没有显示文件的吼住的朋友,可以参考上图。



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

扩展:
源文件,目标文件,可执行文件的联系:

源文件就是用汇编语言或高级语言写出来的代码保存为文件,目标文件是指源文件经过编译程序产生的能被cpu直接识别二进制文件。将目标文件链接起来就成了可执行文件。

源代码与源文件:

源代码(也称源程序)是指未编译的按照一定的程序设计语言规范书写的文本文件,是一系列人类可读的计算机语言指令。 在现代程序语言中,源代码可以是以书籍或者磁带的形式出现,但最为常用的格式是文本文件,这种典型格式的目的是为了编译出计算机程序。

计算机源代码的最终目的是将人类可读的文本翻译成为计算机可以执行的二进制指令,这种过程叫做编译,通过编译器完成。在大多数情况下,源代码等于源文件。

参考资料来源:百度百科-源文件




数据文件:

有时候文件的内容 不一定 是程序,而是 程序运行时 读写的数据,比如 程序运行 需要 从中 读取数据的文件,或者 输出 内容的文件

本章 讨论的是 数据文件。

在以前 所处理 数据的输入输出 都是以终端为对象的,即终端的键盘输入数据,运行结果 显示到 显示器上。
其实 有时候 我们会把 信息输出 到磁盘上,当需要的时候 再从磁盘上 把数据 读取到内存中 使用,这里 处理的就是 磁盘上 的 文件

文件 其实是一种 存放数据 的 一种媒介 或者说 是一种 介质








2.文件名

一个文件 要有 一个唯一的 文件标识,以便用户 识别引用, 文件名包含 3 部分: 文件路径 + 文件名主干 + 文件后缀
在这里插入图片描述
为了方便起见,文件标识 常被称为 文件名







3.文件类型

根据数据的组织形式,数据文件 被称为 文本文件 或者 二进制文件
数据 在内存中 以二进制的形式存储,如果 不加转换的 输出到  外存(硬盘),就是 二进制文件
如果要求在 外存(硬盘)上  以 ASCII码 的形式存储 ,则需要 在存储前 转换,以 ASCII 字符 的形式 存储的文件 是文本文件。


一个数据 在内存中 是怎么存储的呢?

 字符 一律 以 ASCII 形式存储,数值型数据 既可以用 ASCII 形式存储,也可以使用 二进制形式 存储。

如 有整数10000,如果以 ASCII 码 的形式输出到磁盘,则磁盘中占用 5 个字节(每个字符 一个字节,把 1 和 0 当做一个字符来处理)
在这里插入图片描述


接下来让我们通过实例来验证

 我们需要现在 该程序项目中创建一个 test.txt 文本文件,再来运行程序,没有这个文件程序都什么做不了
#include<stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("test.txt", "wb"); // 打开 一个 test.txt 文件, "wb" == write binary(以二进制的形式 写到文件中)
	fwrite(&a/*数据来自于 a 地址处*/, 4/*写4byte*/, 1, pf);// fwrite 写文件,写 a的内容 进去,&a 表示数据来自于 a 的地址处,写 4 个byte
	                                                        //写 1个 这样的数据(写 1个 4 byte 的 数据),放到 pf 维护的文件里去
	fclose(pf);// 写完之后,就可以关闭这个文件了
	pf = NULL; // 需要置为 空指针,保护文件的安全。
	return 0;
}

写完之后,运行该程序,test.txt 文件内容已被改变,怎么观察呢?
解决方案资源管理器 -》 源文件 -》 反键 -》选择 添加 现有项 -》 选择 我们所创建的 test.txt 文件,点击添加
在这里插入图片描述
在这里插入图片描述



添加成功 ,选择 test.txt 文件 -》 反键 -》 选择 打开方式 -》 选择 二进制编译器(点击确定)
如果 不做这一步骤,直接点击查看,是没有效果的
在这里插入图片描述
在这里插入图片描述





此时 你点击 test.txt 文件 你就会发现 test.txt 文件显示的是 10000 以十六进制 形式 展示出的结果(内存上是二进制存储,现在只是以十六进制来展示): 10 27 00 00 (小端存储模式)
即 0x 00 00 27 10
在这里插入图片描述





4.文件缓冲区

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

在这里插入图片描述


ensp;
让我们再通过 一道程序,来感受下 文件缓冲区

#include<stdio.h>
#include<Windows.h>
int mian()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将缓冲区的数据 写到文件(磁盘)
	// fflush 在 高版本 的 vs 上 不能使用了。(这里使用的 vs2013)

	printf("在睡眠 10 s,此时,再次打开 test,txt 文件,文件有了内容");
	sleep(10000);
	fclose(pf);
	// fclose 在关闭文件的时候,也会刷新缓冲区
	// 使用 fflush 是是因为 为了大家更直接感受到 "文件缓冲区"
	pf = NULL;
	return 0;
}

未执行程序之前

在这里插入图片描述



记住执行程序之后,要快速打开文件,虽然给了20s时间(10s 时间没找到就尴尬了),此时文件中是没有数据的
在 10s 之内,打开文件,发现文件没有内容,说明 数据 此时 是在文件缓冲区中
在这里插入图片描述


我再来看看 10s 过后 ,程序调用fflush函数(刷新文件缓冲区),test.txt 文件 是否有 数据
在这里插入图片描述
效果证明,文件缓冲区 是存在的,在第一个10s里,要写入 文件中 的内容,存到了文件缓冲区中,随后 调用 fflush 刷新缓冲区,把文件缓冲区 里的 数据 写入 文件中。而后面的 10s,是因为 fclose 也可以 刷新缓冲区,这样写的目的,是为了让你觉得是 ffluse 把数据写到文件里,而不是 fclose,其实 就是为了让你清楚感受到 文件缓冲区 的存在。






5.文件指针

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

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

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

附图(vs2013):
在这里插入图片描述

在这里插入图片描述
每当打开一个文件的时候系统根据文件的情况 自动创建 一个 FILE 结构体并填充其中的信息,使用者不必关心细节。
一般都是 通过一个 FILE 的指针来维护 这个 FILE 结构体,这样使用起来更加方便。
 
** FILE* pf; 文件指针 pf.**

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

随着我们 通过 该文件指针(FILE) 访问或者修改文件指针 指向的 某个文件文件信息区该结构体也会跟着发生变化
也就是说 文件在 被 读取 写入 的时候,文件的大小,当前文件的指令,等等都会 发生变化





6.文件的打开和关闭

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

在编写程序的时候,在打开文件的同时,都会返回 FILE* 的指针 指向 该文件(文件的信息区),也相当于 建立了 指针 和 文件 的关系。
ANSIC 规定使用 fopen 函数 来 打开文件,fclose 来关闭文件。

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

// int close( FILE* stream );


mode(打开方式):

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




让我们开始实践吧!

程序一(fopen函数):

#include<stdio.h>
int main()
{
	//打开文件(test.txt)

	// 相对路径 写法
	fopen("test.txt", "wb");  	//  . 表示当前路径

	fopen("../test,txt", "wb");
	//  .. 表示当前路径的上一级路径, 比如说  文件 1  包含该程序,文件2 包含 文件1 和 test.txt
	

	fopen("../../test.txt", "wb");// 当前路径的上上路径

	//绝对路径的写法
	fopen("G:\\程序\\operator file\\operator file\\test.txt", "wb");
	//  这里的双斜杠 \\  是为了 让 \ 单纯就是 斜杠。因为 \ 与其它字符相结合,会形成转义字符
	return 0;

}



程序二(fclose函数):

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	//打开文件(test.txt)

	// 相对路径 写法
	FILE*pf = fopen("test.txt", "r"); // 在编写程序的时候,在打开文件的同时,都会返回 FILE* 的指针 指向 该文件(文件的信息区)
	// 也就是我们需要一个 FILE 类型的指针来接收
	//	但是fopen 有打开失败的可能性,打开失败 fopen 会返回一个 空指针

	if (!pf)// 如果 pf 为 NULL == 0,为假,取反 为真,执行 if 语句
	{
		printf("%s\n",strerror(errno));//输出错误信息
		return 0;// 程序结束
	}
	// 走这里说明打开成功
	// 读文件  "r"(只读)
	// 关闭文件
	fclose(pf);// 跟 free  函数 相似,free是释放动态空间(还给操作系统), fclose 的作用时 关闭 文件
	pf = NULL;// fclose 保护文件,free 保护 保护系统
	return 0;

}



程序三(“w”打开方式):

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	//打开文件(test.txt)

	// 相对路径 写法
	FILE*pf = fopen("test.txt", "w"); // 在编写程序的时候,在打开文件的同时,都会返回 FILE* 的指针 指向 该文件(文件的信息区)
	// 也就是我们需要一个 FILE 类型的指针来接收
	//	fopen 有打开失败的可能性,打开失败 fopen 会返回一个 空指针
	// 打开方式 "w" 在指定文件不存在时,它创建一个文件,并返回它的地址,如果文件存在,且有内容的前提下,运行程序 它新建一个文件,而老文件的内容就销毁了
	
说白了,就是如果你 当前文件中 不包含 test 文件时,且 打开方式 为 "w"(只写),它自动 生成 一个 test 的 文本文件

	// 所以并不会执行 if 语句 显示 报错
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 走这里说明打开成功
	// 读文件  "r"
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;

}






7.文件的顺序读写

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




字符输出函数 fputc : int fputc( int c, FILE *stream );

在这里插入图片描述

在运行该程序之前,保证test。txt 文件 内容为空,这样方便我们观察现象
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (!pf)
	{
		printf("%s\n",strerror(errno));
		return 0;
	}
	// 写文件
	fputc('b', pf);// 输出一个 字符 到 文件 离去
	fputc('i', pf);
	fputc('t', pf);
  // 
  关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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





字符输入函数 fgetc

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 读文件
	printf("%c ", fgetc(pf));// b
	printf("%c ", fgetc(pf));// i
	printf("%c ", fgetc(pf));// t
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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





那我们能不能让它像以前一样,键盘输入,屏幕显示?

答案是可以的。不过在实现之前,我们需要了解一下

从键盘输入 
输出到屏幕
键盘 & 屏幕 都是外部设备

把 键盘 称为 标准输入设备 - stdin
把 屏幕 称为 标准输出设备 - stdout
是一个程序 默认打开 的 两个流设备

除此之外,只要程序运行起来,它默认打开三个流
stdin
stdout
stderr:标准输出(设备)文件,对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。在C中,程序执行时,一直处于开启状态。

 以上三个都默认打开的,这三个流的类型 都是 FILE* 的



让我们通过程序来了解下吧

#include<stdio.h>
int main()
{
	int ch = fgetc(stdin);// 从键盘输入,为什么要用 int ch 呢,MSDN 查看
	fputc(ch,stdout);// 在 屏幕上 输出
	return 0;

}

在这里插入图片描述



文本输入函数 fgets : char *fgets( char *string, int n, FILE *stream );

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


 我已经提前在 test.txt 写了
 bit
 HELLO

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	char buf[1024] = { 0 };// 创建一个数组,来存储 字符串
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 读文件
	fgets(buf,1024,pf);// 把 pf指向的 文件信息区 中 字符串,存储到 buf 数组 中
	//printf("%s\n",buf);// bit
	                   //
	                   // 请按任意键继续. . .
	                   //你会发现中间有个空格
	                   
	// 打印 一行数据 bit\n
	printf("%s",buf);  // 把'\n'去掉 ==printf(buf);  
	//puts(buff)       // 则 
	                   // bit
	                   // 请按任意键继续. . .
	                   // 说明 buf 里面其实本来拥有一个 换行 (这点是需要注意的)

	// 打印第二行数据 HELLO ,HELLO  没有 \n, 因为\n 只拥有一个
	fgets(buf, 1024, pf);
	printf("%s", buf);
	// puts(buf)

	// 如果用 puts 你会发现每次打印都换行,因为 puts  天生就会在打印完之后,将其换行的 

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






文本输出函数 fputs :  fputs : int fputs( const char *string, FILE *stream );

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	char buf[1024] = { 0 };
	FILE* pf = fopen("test.txt", "w");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 写文件
	fputs("hello", pf);// 把 hello 写到 pf 里
	fputs("world", pf);
	                  // 自行点开 文件 看效果(helloword)
	                  // fputs 不会自动帮你换行,换行的话,需要我们在字符串后 加 \n
	fputs("\n",pf);
	fputs("hello\n", pf);// 把 hello 写到 pf 里
	fputs("world\n", pf);

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

在这里插入图片描述


文本输出函数 fputs(标准输入输出形式)

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{   // 从键盘读取一行文本
	char buf[1024] = { 0 };
	fgets(buf,1024,stdin);// 从标准输入读取
	fputs(buf,stdout);// 输出到 标准输出流(屏幕)

	// 此写法 等价于 gets(buf);
	//               puts(buf);
	return 0;
}

在这里插入图片描述



格式化输出函数 fprintf 所有输入流

                         int printf(const char *format[, argument]...);// 针对 标准输出流
int fprintf(FILE *stream, const char *format[, argument]...);// 所有输出流

你会发现 fprintf 和 printf 之间,就相差一个 流(FILE结构体的指针)。

#include<stdio.h>
#include<string.h>
#include<errno.h>

struct s
{
	int n;
	float score;
	char arr[10];
};

int main()
{                       // 不加 f ,默认为 double 类型
	struct s s = { 100, 3.14f, "bit" };
	FILE* pf = fopen("test.txt", "w");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 格式化的形式写文件
	fprintf(pf, "%d %f %s", s.n, s.score, s.arr);// fprintf 就是比 printf 多了个 流(FILE 结构 的 指针 pf )
把 结构成员的值 以 格式化数据 存入 pf 指向 文件信息区(文件)

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

在这里插入图片描述





格式化输入函数 fscanf 适用所有输出流(标准 和 文件)

                   int scanf(const char *format[, argument]...);
    int fscanf(FILE *stream, const char *format[, argument]...);

跟 fprintf 一样的,这里不多说 。直接看程序

#include<stdio.h>
#include<string.h>
#include<errno.h>

struct s
{
	int n;
	float score;
	char arr[10];
};

int main()
{                 
	struct s s = { 0 };
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 格式化的输入数据
	fscanf(pf, "%d %f %s", &(s.n), &(s.score), s.arr);// 从 pf 里 拿出 三个数据,分别以 %d %f %s 的格式,赋予 结构体成员变量
	printf("%d %f %s\n", s.n, s.score, s.arr);// 打印结构体成员变量

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

}






让我们看看它们两个(fprintf 和 fscanf)的标准输出输入。

#include<stdio.h>
#include<string.h>
#include<errno.h>

struct s
{
	int n;
	float score;
	char arr[10];
};

int main()
{                       
	struct s s = { 0 };
	fscanf(stdin, "%d %f %s", &(s.n), &(s.score), s.arr);// 从 stdin(键盘) 输入 三个数据,分别以 %d %f %s 的格式,赋予 结构体成员变量
	fprintf(stdout, "%d %.2f %s", s.n, s.score, s.arr);// 打印结构体成员变量


	return 0;

}

在这里插入图片描述





对比一组函数(其实说白了就是相互转换,写进去,再拿出来):

fscanf / sscanf
fprintf / sprintf
scanf / printf 是针对 标准输入流 / 标准输出流 的 格式化输入 / 输出 语句
fscanf / fprintf 是针对 所有 输出/输入 流 的 格式化输入/输出 语句


sscanf / sprintf , 在比较这一对 之前,我先来了解一下,这两个函数

sscanf 函数:   Read formatted data from a string. 从一个字符串里,把 格式化数据 拿出来
int sscanf(const char *buffer, const char format[, argument] …);
比 scanf 多了 一个 const char
buffer

sprintf 函数:  Write formatted data to a string. 写 格式化数据 给 一个字符串
int sprintf(char *buffer, const char format[, argument] …);
比 printf 多了 一个 const char
buffer

#include<stdio.h>

struct s
{
	int n;
	float score;
	char arr[10];

};

int main()
{
	struct s s = { 100, 3.14f, "abcdef" };
	struct s tmp = { 0 };// 创建一个结构体变量,用来存储 sscanf 函数 将字符串 转化 成格式化 的数据
	char buf[1024] = { 0 };// 见一个字符数组,用来 存储 sprintf 函数 把 格式化数据  写成 成字符串 类型的 数据

// 先写进去,再读,没东西怎么读

	// 格式化 的 数据 转换 字符串 存储到 buf
	sprintf(buf, "%d %f %s", s.n, s.score, s.arr);// sprintf 将这里的数据转化为成字符串类型
	//printf("%s\n", buf);// 100  3.1400 abedef
	                  //这里的 100  3.14 abcdef 都是转化成了字符串的

	//从 buf 中 读取 格式化 的 数据 到tmp
	sscanf(buf, "%d %f %s", &(tmp.n), &(tmp.score), tmp.arr);//将 buf 中的 数据,再重新转换为 它们 原来的类型
	printf("%d %f %s", tmp.n, tmp.score, tmp.arr);
	return 0;
}

在这里插入图片描述
经过上面讲解的,相信大家已经理解差不多到位了,让我们分析分析 这两个函数的用途。

sscanf / sprintf

// sscanf 是 从 字符串 中读取 格式化 的数据(把字符串中数据,转化成格式化数据,赋给其它变量)
// sprintf 是把 格式化数据 输出成(存储到)字符串(把 格式化的数据,转化成(或者说 写成)字符串形式 存入某块空间(文件)里,有或者输出到(打印)外端(屏幕))。






二进制输出 fwrite :   size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
buffer
Pointer to data to be written 指向要写入数据的指针

size
Item size in bytes  元素 的 大小单位  byte
  
count
Maximum number of items to be written  最多要被写 的 元素个数

stream
Pointer to FILE structure 指向 FILE 结构体的指针 


程序如下:

#include<stdio.h>

struct s
{
	char name[20];
	int age;
	double score;// score  分数/成绩
};

int main()
{
	struct s s = { "张三", 20, 55.6 }; 
	FILE* pf = fopen("test.txt", "wb");
	if (!pf)
	{
		return 0;
	}
	//  "wb" 二进制形式写文件
	fwrite(&s, sizeof(struct s), 1, pf);
	// test,txt 里面的内容为: 张三                   吞烫烫K@
	
	//张三的 二进制,ASCII 形式 存储都一样的,其他就不是了,所以 后面的内容看不懂,是因为该数据 是以二进制写进的

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



二进制输入 fread: size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
buffer
Storage location for data   存储数据的位置

size
Item size in bytes  元素大小,单位字节

count
Maximum number of items to be read 最多 读取多少 个 元素

stream
Pointer to FILE structure 指向 FILE 结构体 的 指针


程序如下:

#include<stdio.h>

struct s
{
	char name[20];
	int age;
	double score;// score  分数/成绩
};

int main()
{

	struct s tmp = { 0 };
	FILE* pf = fopen("test.txt", "rb");
	if (!pf)
	{
		return 0;
	}
	//  二进制形式读文件
	fread(&tmp, sizeof(struct s), 1, pf);// 从 pf 里拿出 一个 大小为 sizeof(struct s) 的结构体,将其值赋给 结构体变量 tmp

	printf("%s %d %lf\n", tmp.name,tmp.age, tmp.score);

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







8.文件的随机读写

说到随机读写就要涉及到一个函数  fseek

现在就让我们来了解下 fseek 函数吧

fseek 函数 :
Moves the file pointer to a specified location.  将文件指针移动指定位置

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

stream
Pointer to FILE structure    FILE 结构体 的 指针

offset
Number of bytes from origin  从原点开始的字节数(偏移量)

origin
Initial position     起始位置  (文件指针的当前位置)   

origin 有下面几种写法:

SEEK_CUR
Current position of file pointer  文件指针的当前的位置

SEEK_END
End of file  文件的末尾

SEEK_SET
Beginning of file 文件起始位置


程序实践:

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");// test.txt 的内容为 abcdef
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//  1.定位文件指针:根据 文件指针的位置 和 偏移量 来定位文件指针
	fseek(pf, 4, SEEK_CUR);// 从文件当前的位置,偏移量为 4, 指向 e

	//fseek(pf, -2, SEEK_END);// 从文件的末尾位置,想要得到 e ,偏移量应该 为 -2(文件指针指向 f 的后面,也就是偏移量为 0 的位置)

	//  2.读取文件
	int ch = fgetc(pf);
	printf("%c\n", ch);// e

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






ftell 函数: long ftell(FILE *stream);

Gets the current position of a file pointer. 得到文件指针 目前的位置
( 返回 文件指针 相对于 起始位置 的 偏移量 )

程序一:

#include<stdio.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 返回 文件指针 当前的位置
	int pos = ftell(pf);//文件指针 没有 fseek 处理,所以 文件指着 位置 指向 起始位置(偏移量为 0 的位置)

	printf("%d\n", pos);//    0

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


程序二:

#include<stdio.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 1.定位文件指针
	fseek(pf, 4, SEEK_CUR);

	// 返回 文件指针 当前的位置
	int pos = ftell(pf);// 因为前已经 fsek ,文件指针 指向 偏移量为 4 的位置
	printf("%d\n", pos);//    4

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


程序三:

#include<stdio.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 1.定位文件指针
	fseek(pf, 4, SEEK_CUR);// 此时文件位置指向 偏移量为 4 的位置,指向 e
	int ch  = fgetc(pf);// fgetc 读取到了 e, 文件指针pf指向下一个位置,偏移量为 5 的位置

	// 返回 文件指针 当前的位置
	int pos = ftell(pf);// 因为前已经 fgets 一次,文件指针 不再指向 偏移量为 4 的位置,而是偏移量为 5 的位置
	 // pos == 5

	printf("%c\n", ch); //    e
	printf("%d\n", pos);//    5

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






rewind 函数:让文件指针 回到起始位置


程序实践:

test.txt 内容 abcdef
#include<stdio.h>
#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	// 1.定位文件指针
	fseek(pf, 4, SEEK_CUR);// 此时文件指针指向 偏移量为 4 的位置,即指向 e

	//这时 让文件指针回到起始位置
	rewind(pf);

	// 此时再 fgetc ,读取的应该是 a
	int ch = fgetc(pf);


	printf("%c\n", ch); //    a
	fclose(pf);
	pf = NULL;
	return 0;
}







9.文件结束的判定

feof 函数 : int feof(FILE *stream);


被错误使用的 feof 函数 :

#include<stdio.h>
int main()
{
	//EOF(-1) - end of file 文件结束标志
	// 那么 feof  是用来判断 文件的结束,其实不是

	FILE* pf = fopen("test.txt","r"); //  在执行该程序之前,把 test.txt  文件里面的内容先清空
	                                 // 因为如果文件里什么都没有,那么我们第一次 fgetc 读到就是 EOF
	if (!pf)
	{
		return 0;
	}
	int ch = fgetc(pf);
	printf("%d\n", ch);// -1 == EOF(文件结束标志),说明文件结束的位置 放了一个 EOF

	fclose(pf);
	pf = NULL;


	return 0;
}


牢记:在文件读取过程中,不能使用 feof 函数 的 返回值 直接用来判断文件的 是否结束。 而是 应用于 当前文件 读取结束 的时候,判断是 读取失败 结束,还是 遇到文件尾 结束(就是说 我们已经知道文件读取已经结束了,但是 是 什么原因 导致 文件 结束的)

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

例如:
  1. fgetc 判断是否为 EOF
  2. fgets 判断返回值是否为 NULL

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

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


程序实践:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int c;// 用来接收 fgetc 的返回值
	FILE* pf = fopen("test.txt", "r");
	if (!pf)// 所以必定 运行 if 语句
	{
		perror("File opening failed");// 打印错误信息,最后我为其讲解一下
		return EXIT_FAILURE/*表示 -1 ,表示文件打开失败的状态值*/;
	}// 如果 要打开的文件 不存在,程序到这里 打印错误信息就结束了
	
	// 打开成功
	while ((c = fgetc(pf)) != EOF) // 读一个,打印一个,如果 读到 'EOF == -1 ',跳出 while 循环语句
	{ // 标准 I/O 读取文件循环 
		putchar(c);
	}
	
	//判断什么时候结束的
	if (ferror(pf))// 如果 流(pf),没有出错,ferror 返回 0(读取成功),除此之外 返回 一个 非0 的数 (读取失败)
	{ 
		puts("I/O error when reading");// test.txt 文件在读写时, 非正常结束读取
		// 这里是读取成功,所以不执行 if 语句
	}
	
	else if (feof(pf))// feof 判断  pf  是不是  因为遇到(读取到) EOF 而结束的
		// feof 返回的值 为 非零,说明是因为遇到 EOF 而结束的
		// 如果当前位置不是文件结束,则返回0。没有返回信息
	{
		puts("End of file reached successfully");// 因为该文件是因为 读到 EOF 结束的,所以,返回一个 非零的值
		// 执行该语句,打印语句内容,表示  文件是读取遇到了 EOF(文件的结束标志),正常结束的,
	}
	fclose(pf);
	pf = NULL; 
	return 0;
}







二进制文件 与 文本文件差不多,就是读取的差别

while ((size_t c = fread(void *buffer, size_t size, size_t count, FILE *stream)) >=1)
fread返回实际读取的完整项的数量,如果发生错误或在达到count之前遇到文件结束,则可能小于count。使用feof或ferror函数来区分读错误和文件结束条件。如果size或count为0,fread返回0,并且缓冲区内容不变。
也就是说 如果 count 为 0的话,肯定表示读取完成,如果读取异常结束,count 会大于0,说明还有数据没有读完。







最后我们了解 一下 perror 函数

先举个例子:

#include<stdio.h>
#include<errno.h>// 错误码 库
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (!pf)
	{
		//printf("%s\n", strerror(errno));// strerror - 把 错误码 对应的错误信息 的 字符串 得治 返回
		return 0;    // strerror 函数 需要配合 errno.h 头文件,才能打印错误信息
	}
	return 0;
}





再来看看 perror 函数

#include<stdio.h>
int main()
{
	FILE* pf = fopen("test2.txt", "r");// test2.txt  文件是不存在的
	if (!pf)// 所以肯定是打开失败的,执行 if 语句
	{
		perror("hehe");// 屏幕 上 打印 hehe: No such file or directory
		               // 由此 不难看出:这里的 hehe  就是 问题的名字
		              // 而且 perror 该函数不需借助 strerror 函数(转换错误码信息)和 errno.h(提供错误码) 头文件,还有 printf 打印函数(打印错误信息)
		              // 直接就能直接打印出 错误信息
		return 0;
	}
	// 读文件
	// 关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

由此可以看出 perror 其实可以看成, strerror 和 printf,还有 errno.h 的结合体(自动获取错误码,将其传化为错误信息的地址,并将其打印),唯一缺点就是 你用它,它就会打印错误信息,不可以不打印(很任性)。而 strerror 函数 可以控制不打印错误信息(只要不使用打印函数就行(如:printf,puts等)。


本文就此结束。

在这里插入图片描述

  • 32
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dark And Grey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值