C语言学习笔记 | 进阶 | 文件操作详解(万字精心制作)

在这里插入图片描述
纵有疾风起,人生不言弃。本文篇幅较长,如有错误请不吝赐教,感谢支持。

一.为什么使用文件?

我们所写的程序都是在内存上运行的,运行结束后所有的数据都会被销毁,使用文件我们可以将数据直接存放在电脑的硬盘上,即便程序结束运行,我们依然可以通过文件来找到这些数据,甚至再次运行程序时依然能使用这些数据,这样做到了数据的持久化。

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

字符一律以ASCII形式存储,数值型数据一般是二进制形式存储。

二.文件的分类

从不同的角度可对文件进行不同的分类:

①从用户的角度看

文件可分为普通文件和设备文件两种

普通文件是指保存在磁盘或其他外部介质上的一个有序数据集,可以是源文件(后缀为.c)、目标文件(windows环境后缀为.obj)、可执行程序可执行程序(windows环境
后缀为.exe),也可以是一组待输人处理的原始数据
,或者是一组己输出的结果数据。源文件、目标文件、可执行程序等可称为程序文件,输人、输出数据可称为数据文件
在这里插入图片描述

设备文件是指与主机相连的各种外部设备,如显示器、打印机、键盘等。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和通常把显示器定义为标准输出文件,一般情况下,在屏幕上显示有关信息就是向标准输出文件中输出数据。如 printf()、putchar()两数就是这类输出。键盘通常被看作标准的输入文件,从键盘上输人就意味着从标准输人文件上输入数据,如 scanf() 、getchar()函数就属于这类输人。

②从文件的编码方式来看

文件可分为 ASCII 文件和二进制文件两种。

ASCII 文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,该字节用于字符对应存放对应的ASCII码值,不管内存的数据是怎么存储的,只要输到ASCII文件就都转换为 ASCII码值。
ASCII 文件优点体现在字符数据上
①每个字节对应一个字符,便于逐个处理,一个汉字就是一个字符,我一个字符一个字符读,因此人能读懂文件内容
②用DOS命令TYPE可显示文件的内容。
③由于 ASCII 码值的标准是统一的,文件易于移植。
ASCII 文件缺点:数据在内存中不是按宇符形式存储的,是以二进制的形式存储的,存入时要将二进制转化为十进制,再将十进制的每位按照字符存储起来,读出时要将十进制转化为二进制,都增加了系统的开销。

二进制文件是按二进制编码方式来存储文件的,和内存的存储形式一样,例如,数据1678 的存储形式为00000110 100011,二进制文件虽然也可在屏幕上显示,但其内容无法读懂。C编译系统在处理这些文件时,不区分类型,都看成是宇符流,按字节进行处理。
二进制文件的优点:占用外存空问少;从内存到文件或从文件到外存,可直接传输,提高了文件的存取效率。
二进制文件的缺点:一个字节不对应一个宇符,不能直接输出字符形式,用户很难读懂其中的含义

实例: ASCII 文件和二进制文件
在这里插入图片描述
解释:
10000(十进制)在内存中是以二进制的形式存储的,其形式如上。

如果按照ASCII形式存储,要将内存中的10000(二进制形式)先转换为十进制的10000,然后再将10000(十进制形式)给看成5个字符组成,分别是1,0,0,0, 0.然后将5个字符的ASCII码值的二进制依次存入文件,如上图所示。你单取一个字节的数据就是一个字符,因此你能看懂。

10000按照二进制形式存储,直接将内存中的10000(二进制形式)存入文件,其形式如上图,单取出一个字节的数据,你是看不懂的。因为一个字节不对应一个字符。

③文件名:

文件名包含3部分∶文件路径 + 文件名主干 + 文件后缀
例如︰ c : \code\text.txt
文件路径: c : \code
文件名主干: test
文件后缀: .txt

我们要了解此时所称呼的文件名,实际上包括以上3部分内容,而不仅仅是文件名主干。
这边我来举例一些常见的文件后缀方便大家去了解文件的性质:
txt(文本文件)dat(数据文件)xlsx(excel生成的文件)exe(可执行文件)jpg(图片文件)等……

三. 文件指针

只要打开一个文件,都会在内存中开辟了一个相应的文件信息区。文件信息区用于存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等) 这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
文件指针:指向文件信息区,类型为FILE*。
在这里插入图片描述

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

struct _iobuf {
    
    
    char *_ptr;
    int  _cnt;
    char *_base;
    int  _flag;
    int  _file;
    int  _charbuf;
    int  _bufsiz;
    char *_tmpfname;
    short           level;	//缓冲区"满"或者"空"的程度 
	unsigned        flags;	//文件状态标志 
	char            fd;		//文件描述符
	unsigned char   hold;	//如无缓冲区不读取字符
	short           bsize;	//缓冲区的大小
	unsigned char   *buffer;//数据缓冲区的位置 
	unsigned        ar;	 //指针,当前的指向 
	unsigned        istemp;	//临时文件,指示器
	short           token;	//用于有效性的检查 

   };
typedef struct _iobuf FILE;//将这一结构体类型重名名为FILE
 FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
比如:
在这里插入图片描述
C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:

  • stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。
  • stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出信息到此终端。
  • stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到此终端。

四.打开和关闭文件

大家在使用纸质笔记本的时候,通常都是先打开,再进行读写,程序亦是如此,首先打开文件并定位到文件标头,然后找到要读取或写入的目标位置进行读写操作,最后将文件关闭。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

①fopen函数

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

函数功能: 打开文件
返回类型:如果打开成功,返回指向文件信息区的指针,如果返回失败,返回空指针NULL
const char *filename:filename(文件名,实际上包括3部分内容,而不仅仅是文件名主干。如果文件路径未写,则默认本路径)
constchar * mode:字符代表文件打开方式,如下图

文件打开方式表格:

文件使用方式 含义
“r”(只读) 以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)
“w”(只写) 以写方式打开文本文件(如果文件存在则清空文件,文件不存在则创建一个文件)
“a”(追加) 以追加方式打开文本文件,在末尾添加内容,若文件不存在则创建文件
“rb”(只读) 以只读方式打开一个二进制文件(不创建文件,若文件不存在则报错)
“wb”(只写) 以写方式打开一个二进制文件(如果文件存在则清空文件,文件不存在则创建一个文件)
“ab”(追加) 以追加方式打开二进制文件,在末尾添加内容,若文件不存在则创建文件
“r+”(读写) 以可读、可写的方式打开文本文件(不创建新文件)
“w+”(读写) 以可读、可写的方式打开文本文件(如果文件存在则清空文件,文件不存在则创建一个文件)
“a+”(读写) 以添加方式打开文本文件,打开文件并在末尾更改文件,若文件不存在则创建文件
“rb+”(读写) 以可读、可写的方式打开二进制文件(不创建新文件)
“wb+”(读写) 为了读和写,新建一个新的二进制文件(如果文件存在则清空文件,文件不存在则创建一个文件)
“ab+”(读写) 以添加方式打开二进制文件,打开文件并在末尾更改文件,若文件不存在则创建文件

说明:

(1)文件使用方式由“r”“w”“a”“t”“b”“+”6个字符组成,
各宇符的含义是:
r(read):读;
w(write):写;
a (append):追加;
t(text):文本文件,可省略不写
b(binary):二进制文件;
+:读和写。

(2)"r”以只读的方式打开一个文件,该文件必须已经存在,而且只能读取该文件而不是文件打开后进行写操作,文件打开后,指针指向第一个数据

(3)"w"只允许以写的方式打开一个文件。若要打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个同名新文件,使用这种方式要避免文件重名,防止误删文件

(4)若要向一个己存在的文件追加新的信息,只能用"a"方式打开文件。但此时该文件必须是存在的,否则将会出错。

(5)用"+"方式打开的文件可以读,也可以写。
(6)如果文件打开成功,返回指向 FILE结构的指针;如果文件打开出错,fopen返回空指针。为增强程序的可靠 性,常用下面的方法打开一个文件:

if (pf == NULL)
	{
    
    
		perror("fopen");//打开不成功报错
		return 1;
	}

②fclose函数

文件使用完必须及时关闭,使文件名与指针脱离关系,释放文件信息区和文件缓冲区。

//关闭文件
int fclose ( FILE * stream );

函数功能:关闭先前fopen()打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。
返回类型:关闭文件成功返回0,关闭文件失败返回EOF(值为 - 1)来报错
FILE * stream:文件指针,指向文件信息区

演示:
创建一个文件,名叫king.txt
在这里插入图片描述

#include <stdio.h>
int main()
{
    
    
	FILE* pf = fopen("king.txt", "r");//只读文件,不修改
	if (pf == NULL)
	{
    
    
		perror("fopen");//打开不成功报错
		return 1;
	}
	//打开成功
	//读文件
	fclose(pf);//关闭文件
	return 0;
}

五.文件缓冲区

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

六.文件的读写(有序)

先打开文件(注意打开文件的方式),在进行输入输出。
文件的读写:
文件的读,是从文件中读取(输入)信息,到内存中;文件的写,是由内存向文件写入(输出)数据。
在这里插入图片描述

输入/输出函数表

输入/输出函数的选用原则
从功能角度来说,fread()和 fwrite()两函数可以完成文件的任何数据输入/输出操作。,
方便起见,依据下列原则选用:

  • (1)输入/输出1个宇符(或宇节)数据时选用fgetc()和fputc()函数
  • (2)输入/输出1个字符串时选用fgets()和fputs()函数。
  • (3)输入/输出1个(或多个)不含格式的数据时选用 fread() 和fwrite()函数。
  • (4)输入/输出1个(或多个)含格式的数据时选用 fscanf()和 fprintf() 函数
功能 函数原型 适用于 功能
字符输入函数 int fgetc(FILE * stream); 所有输入流 从文件指针stream所指向的位置读取一个字符,读取完成后指针自动后移指向下一个字符,成功时返回该字符,否则返回EOF
字符输出函数 int fputc(int ch, FILE * stream); 所有输出流 将字符 ch 写入到文件指针stream所指向的位置,成功则返回字符本身并且stream自动后移一位,否则返回EOF
文本行输入函数 char * fgets(char * str, int size, FILE * stream);` 所有输入流 从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 ‘\0’ 作为字符串结束。
文本行输出函数 int fputs(const char * str, FILE * stream); 所有输出流 将str所指定的字符串写入到stream指定的文件中,字符串结束符 ‘\0’ 不写入文件。 成功时返回非0值,否则返回EOF
格式化输入函数 fscanf(FILE*pf, 格式控制字符串,地址列表) 所有输入流 按格式控制串所描述的格式,从 pf 所指向的文件中读取数据,送到指定的变量中。若输出操作成功,返回实际写入的字符,若输出操作失败,则返回EOF
格式化输出函数 fprintf(pf, 格式控制字符串,输出列表) 所有输出流 将输出项按照制定的格式写入 pf 所指向的文件中。若输入操作成功,返回实际读出的数据,若没有读到数据项,则返回0若文件结束或调用失败,则返回EOF
二进制输入 size_t fread(buffer, size, count, FILE*pf) 文件 fread函数是从pf所指向文件的当前位置开始,一次读入size个字节的数据,重复count次,并将读入的数据放入buffer指针指向的内存中,同时将读写位置指针向后移动size*count个字节,如果发现读取到的完整的元素个数小于指定的元素个数count,这就是最后一次读取。
二进制输出 size_t fwrite(buffer, size, count, FILE*pf) 文件 buffer是指针,fwrite0()函数是从 buffer 开始,一次输出 size 个字节,重复count 次,并将输出的数据存放到pf所指向的文件中;同时,将读写位置指针向后移动 size*count 个字节

按照字符读写文件fgetc()、fputc()

写字符函数fputc()函数原型:
int fputc(int ch, FILE * stream);

功能:
将ch转换为unsigned char后写入stream指定的文件中
参数:
ch:需要写入文件的字符
stream:文件指针
返回值:
成功:返回成功写入文件的字符
失败:返回EOF(-1)

实例演示:写字符函数fputc()

#include <stdio.h>
int main()
{
    
    
	FILE* pf = fopen("king.txt", "w");//w写文件
	if (pf == NULL)
	{
    
    
		perror("fopen");//打开不成功报错
		return 1;
	}
	//打开成功
	//开始写入
	char buf[] = "this is a test for fputc";
	int n = strlen(buf);
	for (int i = 0; i < n; i++)
	{
    
    
		//往文件pf写入字符buf[i]
		int ch = fputc(buf[i], pf);//写入成功则返回该字符
		printf("ch = %c\n", ch);
	}
	fclose(pf);//关闭文件
	return 0;
}

在这里插入图片描述

在这里插入图片描述
读字符函数fgetc()函数原型:

int fgetc(FILE * stream);

功能:
从stream指定的文件中读取一个字符
参数:
stream:文件指针
返回值:
成功:返回读取到的字符
失败:返回EOF(-1)

实例演示:读字符函数fgetc()

#include <stdio.h>
int main()
{
    
    
	FILE* pf = fopen("king.txt", "r");//只读文件,不修改
	if (pf == NULL)
	{
    
    
		perror("fopen");//打开不成功报错
		return 1;
	}
	//打开成功
	//开始读取
	char ch;
	while ((ch = fgetc(pf)) != EOF)
	{
    
    
		printf("%c", ch);
	}
	printf("\n");

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

在这里插入图片描述

按照行读写文件fgets()、fputs()

按照行写文件fputs()函数原型:
int fputs(const char * str, FILE * stream);

功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 ‘\0’ 不写入文件。
参数:
str:字符串
stream:文件指针
返回值: 成功:0
失败: EOF(-1)

实例:按照行写文件fputs()


#include <stdio.h>
int main()
{
    
    
	FILE* pf = fopen("king.txt", "w");//w写文件
	if (pf == NULL)
	{
    
    
		perror("fopen");//打开不成功报错
		return 1;
	}
	//打开成功
	//开始写入
	char* buf[] = {
    
     "123456\n", "bbbbbbbbbb\n", "ccccccccccc\n" };//以\n为标志,buf数组存放三行数据
	int i = 0;
	int n = 3;
	for (i = 0; i < n; i++)
	{
    
    
		int len = fputs(buf[i], pf);
		printf("len = %d\n", len);
	}
	fclose(pf);//关闭文件
	return 0;
}

运行结果:三行数据写入成功
在这里插入图片描述
在这里插入图片描述

按照行读文件fgets()函数原型:
char * fgets(char * str, int size, FILE * stream);

功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 ‘\0’ 作为字符串结束。
参数:
str:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL

实例:按照行读文件fgets()函数

#include <stdio.h>
int main()
{
    
    
	FILE* pf = fopen("king.txt", "r");//只读文件,不修改
	if (pf == NULL)
	{
    
    
		perror("fopen");//打开不成功报错
		return 1;
	}
	//打开成功
	// 读文件,按行读取
	char buf[100] = {
    
     0 };

	while (!feof(pf)) //文件没有结束
	{
    
    
		char* p = fgets(buf, sizeof(buf), pf);
		if (p != NULL)//验证是否读取成功
		{
    
    
			printf("buf = %s", buf);
		}
	}
	fclose(pf);//关闭文件
	return 0;
}

在这里插入图片描述

按照格式化文件fprintf、fscanf

按照格式化写文件fprintf函数原型:

int fprintf(FILE * stream, const char * format, 输出列表);

功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 ‘\0’ 为止。
参数:
stream:已经打开的文件
format:字符串格式,用法和printf()一样
返回值:
成功:实际写入文件的字符个数
失败:-1

实例:按照格式化写文件fprintf函数

#include <stdio.h>
struct S
{
    
    
	char arr[10];
	int a;	
};
int main()
{
    
    
	struct S s = {
    
    "zhangsan",45 };//结构体初始化
	FILE *pf = fopen("king.txt", "w");
	if (pf == NULL)
	{
    
    
		perror("fopen");
		return 0;
	}
	//格式化的形式写文件
	fprintf(pf, "%s %d ", s.arr, s.a);
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

在这里插入图片描述

按照格式化读文件fscanf函数原型

int fscanf(FILE * stream, const char * format, 地址列表);

功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
stream:已经打开的文件
format:字符串格式,用法和scanf()一样
返回值:
成功:参数数目,成功转换的值的个数
失败: - 1

实例:按照格式化读文件fscanf函数


#include <stdio.h>
struct S
{
    
    
	char arr[10];
	int a;	
};
int main()
{
    
    
	struct S s = {
    
     0 };//初始化结构体用于接受输出的结构体
	FILE* pf = fopen("king.txt", "r");
	if (pf == NULL)
	{
    
    
		perror("fopen");
		return 1;
	}
	//格式化的输入数据
	fscanf(pf, "%s %d ", &(s.arr), &(s.a));
	printf("%s %d\n", s.arr, s.a);
	fclose(pf);//关闭文件
	pf = NULL;	
	return 0;
}

在这里插入图片描述

按照块读写文件fread、fwrite

按照块写文件fwrite函数原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:以数据块的方式给文件写入内容
参数:
ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功写入文件数据的块数目,此值和 nmemb 相等
失败:0
总结:
buffer是指针,fwrite()函数是从 buffer 开始,一次输出 size 个字节,重复count 次,并将输出的数据存放到pf所指向的文件中;同时,将读写位置指针向后移动 size*count 个字节

实例:按照块写文件fwrite函数

#include <stdio.h>
typedef struct S
{
    
    
	char name[20];
	int age;
	int height;
}S;
int main()
{
    
    
	S s[3] = {
    
     {
    
    "Zhangsan", 20, 172},{
    
    "lisi", 21,180},{
    
    "强风吹拂king", 20, 185} };//"wb+"(读写)|	为了读和写,新建一个新的二进制文件
	FILE* pf = fopen("king.txt", "wb");
	if (pf == NULL)
	{
    
    
		perror("fopen");
		return 1;
	}
	//二进制的形式写文件
	int ret = fwrite(&s, sizeof(struct S), 3, pf);
	printf("ret = %d\n", ret);
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

在这里插入图片描述

为什么会出现这种情况?
字符串以二进制的形式写进去和以文本的形式写进去的内容是一样的,但对于整数就不一样了,所以我们看不懂,但电脑能看懂。

按照块读文件fread函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

功能:以数据块的方式从文件中读取内容
参数:
ptr:存放读取出来数据的内存空间
size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
失败:0
总结:
fread函数是从pf所指向文件的当前位置开始,一次读入size个字节的数据,重复count次,并将读入的数据放入buffer指针指向的内存中,同时将读写位置指针向后移动size*count个字节,如果发现读取到的完整的元素个数小于指定的元素个数count,这就是最后一次读取。

#include <stdio.h>
typedef struct S
{
    
    
	char name[20];
	int age;
	int height;
}S;
int main()
{
    
    
	 S s[3] = {
    
    0};//接收数据
	FILE* pf = fopen("king.txt", "rb");
	if (pf == NULL)
	{
    
    
		perror("fopen");
		return 1;
	}
	//二进制的形式写文件
	int ret=fread(&s, sizeof(struct S), 3, pf);
	printf("ret = %d\n", ret);
	for (int i = 0; i < 3; i++)
	{
    
    
		printf("%s %d %d\n", s[i].name, s[i].age, s[i].height);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

七.文件的读写(随机)

正常情况下我们对文件的读写都是按照顺序依次进行读写,当然也可以根据读写的需要, 人为地移动文件位置标记的位置。文件位置标记可以向前移、向后移, 移到文件头或文件尾, 然后对该位置进行读写,显然这就不是顺序读写了, 而是随机读写。

①fseek函数

fseek函数原型:根据文件指针的位置和偏移量来定位文件指针
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:

起始点 名字 用数字代表
文件开始位置 SEEK_SET 0
文件当前位置 SEEK_CUR 1
文件末尾位置 SEEK_END 2

在这里插入图片描述
返回值:
成功:0
失败:-1

实例:fseek函数
此时的king文件内部
在这里插入图片描述

int main()
{
    
    
	// 当前文件内容:abcdefg
	FILE* pf = fopen("king.txt", "r");
	if (pf == NULL)
	{
    
    
		perror("fopen()");
		return 1;
	}
	// 读文件
	fseek(pf, 2, SEEK_SET);//将pf定位到SEEK_SE向后偏移2个位置读取
	int ch = fgetc(pf);// c
	printf("%c\n", ch);

	fseek(pf, -3, SEEK_END);// 将pf定位到SEEK_END向前偏移3个位置读取
	ch = fgetc(pf);// e
	printf("%c\n", ch);

	// 经fgetc后,e被读取,文件指针偏移,当前文件指针指向f

	fseek(pf, 1, SEEK_CUR);// 从当前位置向后偏移1个位置读取
	ch = fgetc(pf);// g
	printf("%c\n", ch);

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

在这里插入图片描述

②ftell函数和rewind函数

ftell函数原型:
long int ftell ( FILE * stream );

功能:获取文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
返回值:
成功:当前文件流(文件光标)的读写位置
失败:-1

rewind函数原型:
void rewind(FILE *stream);

功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:
stream:已经打开的文件指针
返回值:
无返回值

举例:ftell函数

#include <stdio.h>
int main()
{
    
    
		// 当前文件内容:abcdef
	FILE* pf = fopen("king.txt", "r");
	if (pf == NULL)
	{
    
    
		perror("fopen()");
		return 1;
	}
	// 读文件
	int ch = fgetc(pf);// a读取完指向b
	ch = fgetc(pf);// b读取完指向c
	ch = fgetc(pf);// c读取完指向d

	int pos = ftell(pf);// d相对于起始位置偏移量为3
	printf("%d\n", pos);

	rewind(pf);// 回到起始位置a
	printf("%d\n", ftell(pf));// a相对于起始位置偏移量为0

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

在这里插入图片描述

八.文件读取(输入)结束的判定

文件结尾

在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

#define EOF (-1)

当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSIC提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
feof函数原型:
int feof(FILE * stream);

功能:检测是否读取到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置内容(上一个内容)。
参数:
stream:文件指针
返回值:
非0值:已经到文件结尾
0:没有到文件结尾

错误使用feof函数

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
读取(输入)函数都有自己单独的结束标志:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xqk_gkb/article/details/128881810