总言
C语言:文件操作相关。
文章目录
1、文件是什么?为什么需要文件?
1.1、为什么需要文件?
模拟实现通讯录时,会面临这样一个实际问题:在通讯录中把信息记录下来后,只有在我们自己选择删除数据的情况下,数据才不复存在,否则需要长久保留数据。
这就涉及到了数据持久化的问题,一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
1.2、文件是什么?
1)、文件分类
磁盘上的文件是文件。但在程序设计中,从文件功能的角度来看,一般文件有两种:程序文件、数据文件。
程序文件:包括源程序文件(后缀为.c
),目标文件(windows环境后缀为.obj
),可执行程序(windows环境后缀为.exe
)。
数据文件: 文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
2)、文件名称
一个文件要有一个唯一的文件标识,以便用户识别和引用。为了方便起见,文件标识常被称为文件名。
文件名包含3部分:文件路径+文件名主干+文件后缀
D:\日常\TIM\Tencent Files\All Users\test.txt
文件路径:D:\日常\TIM\Tencent Files\All Users\
文件名主干:test
文件后缀:.txt
2、文件的打开与关闭
2.1、文件指针
1)、文件指针是什么?
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE
。
不同的C编译器的FILE
类型包含的内容不完全相同,但是大同小异。每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE
结构的变量,并填充其中的信息,使用者不必关心细节。
VS2019下FILE声明:
#ifndef _FILE_DEFINED
#define _FILE_DEFINED
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
#endif
VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
一般通过一个FILE的指针
来维护这个FILE结构的变量
,这样使用起来更加方便。
2)、文件指针的作用
我们可以创建一个FILE*
的指针变量,如下:
FILE* pf;
定义pf
是一个指向FILE类型数据的指针变量。可以使pf
指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
2.2、文件打开和关闭:fopen、fclose
1)、fopen、fclose函数介绍
相关函数链接:fopen
fopen
FILE * fopen ( const char * filename, const char * mode );
const char * filename
:所需要打开的文件名称。
const char * mode
:打开文件的方式,此处类型为字符指针,用于存放字符串首字符的地址。
FILE *
:在内存中创建一个与打开文件有关的文件信息区,同时返回一个文件指针,该指针指向文件信息区的起始地址(结构体)。
NULL
:如果文件打开失败,则返回空指针,同动态内存一致,需要进行判空操作,若错误则显示错误原因,并结束程序。
相关链接:fclose
fclose
int fclose ( FILE * stream );
FILE * stream
:向指定要关闭的FILE指针
2)、使用举例
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//“r”此处为字符串,用双引号,r是一种文件读取模式
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//……
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
情形一:
情形二:
情形三:绝对路径与相对路径说明
假如要打开的文件不在当前路径下,需要使用对应路径打开:
2.3、文件使用方式
以下mode
会在后续学习中慢慢用到。
3、文件的顺序读写
3.1、字符输入输出:fputc、fgetc
3.1.1、fputc
相关函数链接:fputc
将字符character
按顺序写入对应的文件流stream
中,此处使用的是文件指针FILE *
。
如果失败则返回EOF
,成功则返回写入的字符(作为 unsigned char
强制转换为 int
)。
int fputc ( int character, FILE * stream );
//int character:尽管参数类型是 int,但通常传递的是 char 类型的值,或者是一个可隐式转换为 int 的表达式。
演示如下:
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
for (char ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.1.2、fgetc
相关函数链接:fgetc
从对应的文件指针指向的文件中,按顺序依次读取字符。若遇到文件结尾/错误则返回EOF,若成功则返回字符读取(无符号字符unsigned char 整值提升为 int 值)。
int fgetc ( FILE * stream );
使用演示:
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
int ch;
while ((ch = fgetc(pf)) != EOF)// 连续读取
{
printf("%c ", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.2、适用于所有流:stream的简单介绍
3.2.1、什么是“流”
1)、引入
我们知道各种各样的外部设备,比如键盘、屏幕、U盘、硬盘、网卡等,数据信息的读写是在它们之间进行交换的。
但不同的设备其读写方式可能存在差异,如果直接让它们彼此进行数据输入输出的交换,使用效率会大幅降低。因此,为了简化这一过程,我们在这些外设间引入一个中间商,即流,用于输入输出。
这样一来,我们在各种数据交换时,就不必关心流与这些外设间是如何做到数据信息交换的,相当于一种封装。
2)、介绍
C语言中,术语“流”表示任意输入的源或任意输出的目的地。它是一种连续的数据序列,可以是输入流(从数据源流向程序),也可以是输出流(从程序流向数据目的地)。
在C语言中,流通常通过文件指针(FILE *
)来引用,这些指针指向了由标准I/O库函数(如fopen、fread、fwrite、fclose等)管理的缓冲区或设备。
相关演示:
int main()
{
int ch = fgetc(stdin);// 从标准输入中读取(键盘中读取)
printf("%c \n", ch);
fputc(ch, stdout);// 显示到标准输出中(显示在屏幕上)
return 0;
}
3.2.2、流的作用
1、抽象化:流为不同的输入输出设备提供了一个统一的接口,使得程序可以以相同的方式处理来自不同源的数据。
2、缓冲: 为了提高效率,标准I/O库通常会在内存中为流维护一个缓冲区。这意味着,当程序向输出流写入数据时,数据首先被写入缓冲区,直到缓冲区满或显式地刷新缓冲区时,数据才会被实际写入到目标设备。同样,从输入流读取数据时,也是先从设备读取到缓冲区,然后再从缓冲区读取到程序中。这种缓冲机制减少了实际进行物理I/O操作的次数,从而提高了效率。
3、简化编程:通过使用流,程序员可以编写出更加简洁、易于理解和维护的代码。他们不需要关心数据的具体传输方式,只需要通过标准的I/O函数进行读写操作即可。
3.2.3、流的类型
在C语言中,流主要分为以下几种类型:
标准输入输出流: 包括标准输入(stdin
,对应键盘输入)、标准输出(stdout
,对应屏幕输出)和标准错误输出(stderr
,也对应屏幕输出,但通常用于错误信息)。
文件流: 通过fopen
等函数打开的文件对应的流。这些流可以用于读写文件。
其他类型的流: 在某些情况下,还可能涉及到网络流、内存流等,但这些通常不是标准C语言直接提供的,而是由特定的库或框架支持。
3.3、文本输入输出:fputs、fgets
3.3.1、fputs
相关函数链接:fputs
相关演示:
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
fputs("落霞与孤鹜齐飞,", pf);
fputs("秋水共长天一色。\n", pf);
fputs("渔舟唱晚,响穷彭蠡之滨;\n", pf);
fputs("雁阵惊寒,声断衡阳之浦。\n", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
演示结果如下:可以看到,没加换行符,fputs
会将当前内容放置于上此文尾.若加了换行符则另起一行。
3.3.2、fgets
1)、基本介绍
相关函数链接:fgets
fgets
函数用于从指定的文件流中读取一行数据,包括换行符(如果有的话),并存储在给定的字符串中。
char * fgets ( char * str, int num, FILE * stream );
参数介绍:
str
:指向字符数组的指针,该数组将存储从stream
中读取的字符串。
num
:指定最多读取的字符数(包括最后的空字符\0
)。这意味着实际上最多可以读取num-1
个字符,第num
个字符位置会被\0
(字符串结束符)占据。
stream
:指向FILE
对象的指针,该对象标识了要从中读取数据的输入流。例如,stdin代表标准输入流。
返回值: 成功时,fgets
返回str
的指针。如果读取过程中遇到文件结束(EOF)或发生错误,且没有读取到任何字符,则返回NULL。可以通过feof(stream)
和ferror(stream)
函数分别检查是否遇到了文件结束或发生了错误。
2)、相关演示
演示一:
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[255];
//读写文件
fgets(arr, 254, pf);
printf("%s\n", arr);
while (fgets(arr, 254, pf)!=NULL)
printf("%s", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
演示二:验证fgets会保留一位将\0
放入
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[10]="XXXXXXXXXX";
//读写文件
fgets(arr, 5, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.4、格式化输入输出:fscanf、fprintf
3.4.1、fprintf
1)、基本介绍
相关函数链接:fprintf
相同点说明:
1、fprintf
函数和printf
函数,都是向输出流中写入可变数量的数据项,并且利用格式串来控制输出的形式。这两个函数的原型都是以...
符号结尾的,表明后面还有可变数量的实际参数。
①、format
是一个格式字符串,用于指定如何格式化后续的参数。
②、...
表示一个可变数量的附加参数,这些参数的值将被格式化并写入对应的流中。
2、这两个函数的返回值是写入的字符总数,若出错则返回一个负值(EOF)。
不同点说明:
printf
函数始终向stdout
(标准输出)写入,即打印到屏幕上;
fprintf
函数适用于任意流,可将相关内容写入到指定的stream
中。
//printf函数的调用,等价于fprintf函数把stdout作为第一个实际参数而进行的调用。
printf("Name: %s, Age: %d, Height: %.2f\n", name, age, height);
fprintf(stdout,"Name: %s, Age: %d, Height: %.2f\n", name, age, height);
2)、相关演示
struct st
{
char name[20];
int age;
double score;
};
int main()
{
//创建一个学生数据,要求:将该学生数据的内容存入指定文件中
struct st s1 = { "张三",25,98.00 };
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
fprintf(pf,"%s %d %lf", s1.name, s1.age, s1.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.4.2、fscanf
1)、基本介绍
相关函数链接:fscanf
相同点说明:
1、fscanf
函数和scanf
函数都是从输入流读入数据,并且使用格式串来指明输入的格式。格式串的后边可以有任意数量的指针(每个指针指向一个对象)作为额外的实际参数。输入的数据项(根据格式串中的转换说明)进行转换并且存储在指针指向的对象中。
2、如果发生输入失败(即没有输入字符可以读)或者匹配失败(即输入字符和格式串不匹配),那么..scanf
函数会提前返回。(在C99中,输入失败还可能由编码错误导致。编码错误意味着我们试图按多字节字符的方式读取输入,但输入字符却不是有效的多字节字符)。这两个函数都返回读入并且赋值给对象的数据项的数量。如果在读取任何数据项之前发生输入失败,那么会返回EOF。
不同点说明:
scanf
函数始终是从标准输入流中读取数据,一般是键盘;
fscanf
函数适用于任意流,可从指定的流stream
中读取数据。
//scanf函数的调用,等价于以stdin作为第一个实际参数的fscanf函数调用。
scanf("%s %d %f", name, &age, &height);
fscanf(stdin, "%s %d %f", name, &age, &height);
2)、相关演示
相关演示:
struct st
{
char name[20];
int age;
double score;
};
int main()
{
//创建一个学生数据,要求:从指定文件中读取相关数据信息填入该结构体中
struct st s1 = {0};
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
fscanf(pf, "%s %d %lf", s1.name, &(s1.age), &(s1.score));
//注意,name为数组名,本身就是地址,故无需取地址操作符,其它二者读取时与scanf别无差异
fprintf(stdout, "%s %d %lf", s1.name, s1.age, s1.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.4.3、扩展补充:printf、scanf格式说明
3.4.3.1、printf格式说明
来源:C语言程序设计现代方法
3.4.3.2、scanf格式说明
3.5、与字符串相关的输入输出:sscanf、sprintf
3.5.1、对比说明
1)、scanf/printf、fscanf/fprintf、sscanf、sprintf对比说明
scanf
:针对标准输入输出流的格式化输入函数,Read formatted data from stdin
printf
:针对标准化输入输出流的格式化输出函数,Print formatted data to stdout
scanf
int scanf ( const char * format, ... );
printf
int printf ( const char * format, ... );
fscanf
:针对所有输入流的格式化输入函数,Read formatted data from stream
fprintf
:针对所有输出流的格式化输出函数,Write formatted data to stream
fscanf
int fscanf ( FILE * stream, const char * format, ... );
fprintf
int fprintf ( FILE * stream, const char * format, ... );
sscanf
:把一个字符串数据转换为格式化数据,Read formatted data from string
sprintf
:把一个格式化的数据转换为字符串数据,Write formatted data to string
sscanf
int sscanf ( const char * s, const char * format, ...);
sprintf
int sprintf ( char * str, const char * format, ... );
3.5.2、使用介绍
1)、sprintf函数介绍
相关函数链接:sprintf
sprintf
函数将格式化的数据写入字符串str
中,而不是输出到控制台。它的功能类似于printf,但printf是将格式化的数据输出到标准输出(通常是控制台),而sprintf是将数据写入一个字符串。
int sprintf(char *str, const char *format, ...);
参数说明:
str
:指向要写入的字符串的指针,即目标字符串的缓冲区。
format
:格式控制字符串,用于指定如何将后续的数据格式化后写入str指向的字符串中。
...
:可变参数,用于提供要格式化的数据。
返回值:成功时,返回写入字符串的字符数(不包括终止的空字符)。如果发生错误,则返回一个负数。
2)、sscanf函数介绍
相关函数链接:sscanf
sscanf
函数从字符串str
中按照format
指定的格式读取数据,并将读取到的数据存储到对应的变量中。
int sscanf(const char *str, const char *format, ...);
参数说明:
str
:指向要读取数据的字符串的指针。
format
:格式控制字符串,类似于scanf中的格式控制字符串,用于指定如何从str中读取数据。
...
:可变参数,用于存储从str中读取并格式化的数据。
返回值: 成功匹配并赋值的参数个数。如果没有成功匹配任何参数,则返回0。如果发生错误,则返回EOF。
3)、使用举例
struct st
{
char name[20];
int age;
double score;
};
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写操作
struct st s1 = { "李四",23,94.5 };//原数据
struct st stof = { 0 };//未赋有效值的结构体:用于存放从字符串中转换过来的格式化数据
char ftos[255];//字符数组:用于存放从格式化数据中转换得到的字符串
//使用sprintf,将格式化数据转换成字符串
sprintf(ftos, "%s %d %lf", s1.name, s1.age, s1.score);
printf("ftos数组中:%s\n", ftos);
//使用sscanf,将字符串转换为格式化数据
sscanf(ftos, "%s %d %lf", stof.name, &(stof.age), &(stof.score));
printf("stof结构体中:%s %d %lf\n", stof.name, stof.age, stof.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.6、二进制输入输出:fread、fwrite
3.6.1、文本文件和二进制文件
3.6.1.1、基本介绍
1)、数据文件分类
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换直接输出到外存,就是二进制文件。
如果要求在外存上以特定的编码方式(如ASCII、UTF-8等)存储,则需要在存储前转换。这类文件就是文本文件。
2)、二进制文件和文本文件有什么区别
1)、 在存储方式上:
①、二进制文件:直接以二进制形式存储数据,即数据的原始字节序列。这种存储方式不依赖于特定的字符编码,而是直接反映了数据的物理表示。
②、文本文件:基于字符编码的文件,常见的编码有ASCII、Unicode等。文本文件将字符数据按照特定的编码方式转换为二进制形式进行存储,但存储的内容仍然是可读的字符序列。
2)、在内容表示上:
①、二进制文件:可以包含任意类型的数据,如图像、音频、视频、可执行文件等。这些数据以二进制形式直接存储,不依赖于字符编码,因此可以表示更为复杂和丰富的信息。
②、文本文件:仅包含ASCII码或其他编码的字符数据,以人类可读的字符序列来表示数据。文本文件的内容通常是文本信息,如源代码、文档、日志等。
3)、在处理方式上
①、二进制文件:在处理二进制文件时,通常需要使用特定的解码器或软件来读取和解释文件中的数据。这是因为二进制文件中的数据可能包含复杂的数据结构或格式,需要按照特定的规则进行解析。
②、文本文件:可以直接使用文本编辑器(如记事本、Notepad++等)打开和编辑,无需特殊软件。文本文件的内容以字符序列的形式呈现,因此可以直接阅读和修改。
4)、编码与解码 (后续简单演示了一下)
①、二进制文件:编码方式是基于值的,即直接以二进制形式表示数据。解码时需要按照特定的文件格式规范进行解析,以恢复原始数据。
②、文本文件:编码方式是基于字符的,即将字符数据按照特定的编码方式转换为二进制形式进行存储。解码时则按照相应的编码方式将二进制数据转换回字符序列。
3)、数据在内存中的存储方式
字符型数据,一律以ASCII形式存储;
数值型数据,既可以用ASCII形式存储,也可以使用二进制形式存储。
3.6.1.2、深入理解
以整数10000为例子:
同理:
一个测试代码: 我们以二进制的形式将正数10000写入文件中
#include <stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("D:\\Daily\\test.txt", "wb");
fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
fclose(pf);
pf = NULL;
return 0;
}
假如用文本文件的形式打开,可以看到是乱码:
现在我们在VS中以二进制的形式打开,需要做一点设置,操作如下:
在C语言中,fread
和 fwrite
是用于二进制文件输入输出的函数,它们分别用于从文件中读取数据和向文件写入数据,且这些数据是以二进制形式处理的,而不是像 fscanf 和 fprintf 那样以格式化文本形式处理。
以下将来介绍这两个函数。
3.6.2、fwrite
1)、基本介绍
相关函数链接:fwrite
fwrite
函数将 ptr
指向的数组中的数据写入到指定的文件流 stream
中。写入的数据项的数量(元素个数)由 count
指定,每个数据项的大小(每个元素的大小)由 size
指定。
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数说明:
ptr
:指向一个数组,该数组含了要写入文件的数据。
size
:每个数据项的大小(以字节为单位)。
count
:要写入的数据项的数量。
stream
:指向 FILE 对象的指针,该对象标识了要被写入数据的文件。
返回值: 若成功,则返回写入的数据项的数量。如果发生错误,则可能返回一个小于请求数量的值。
2)、相关演示
struct st
{
char name[20];
int age;
double score;
};
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写操作
struct st s1 = { "李四",23,94.5 };
fwrite(&s1, sizeof(struct st), 1, pf);
//含义:将s1中的数据输出至pf中,个数为1个,每个大小为sizeof(struct st)
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.6.3、fread
1)、基本介绍
相关函数链接:fread
fread
函数从指定的文件流 stream
中读取数据,并将读取的数据存储在 ptr
指向的数组中。读取的数据项的数量由 count
指定,每个数据项的大小由 size
指定。
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数说明:
ptr
:指向一个数组,该数组用于存储从文件中读取的数据。
size
:每个数据项的大小(以字节为单位)。
count
:要读取的数据项的最大数量。
stream
:指向 FILE 对象的指针,该对象标识了要被读取的文件。
返回值: 若成功,返回读取的数据项的数量,这可能会小于请求的数量(例如,如果到达文件末尾)。如果发生错误,则返回 0
。
2)、相关演示
相关演示:
struct st
{
char name[20];
int age;
double score;
};
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写操作
struct st s1 = { 0 };
fread(&s1, sizeof(struct st), 1, pf);
printf("%s %d %lf\n", s1.name, s1.age, s1.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4、文件的随机读写
4.1、fseek
1)、基本介绍
在C语言中,fseek
函数是用于重新定位文件指针到文件的指定位置,以实现文件的随机读写。这意味着,我们可以在不从头开始读取整个文件的情况下,直接跳到文件的任意位置进行读取或写入操作。相关函数链接:fseek
参数说明:
stream
是指向 FILE 对象的指针,该对象标识了要操作的文件。
offset
是从 origin
指定的位置开始的偏移量,单位是字节。它可以是正数或负数。
origin
是起始位置,它决定了 offset 的起始点。 origin
可以是以下三个常量之一:
SEEK_SET 文件的开头(偏移量从文件开始计算)。
SEEK_CUR 当前文件指针的位置(偏移量从当前位置开始计算)。
SEEK_END 文件的末尾(偏移量从文件末尾向前计算;通常用于将文件指针移动到文件末尾之前的一个位置)。
返回值:
成功时,fseek 返回 0。
失败时,返回非零值,并设置 errno 以指示错误原因。
2)、演示一:关于随机位置读取
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//文件读写
//
//文件中存储的例句:turn lemon into lemonade.
for (int i = 0; i < 4; i++)
{
printf("%c", fgetc(pf));//turn
}
printf("\n");
//至此,文件指针指向trun lemon中的空白字符
//演示一:以当前文件指针作为参考点
fseek(pf, -1, SEEK_CUR);//当前指向turn中的n
printf("文件向左偏移一位后:%c\n", fgetc(pf));//fgetc读取后后挪一位,到空白字符
fseek(pf, 1, SEEK_CUR);//当前指向为lemon中的l
printf("文件向右偏移一位后:%c\n", fgetc(pf));//fgetc读取后后挪一位,到e
printf("\n");
//演示二:以文件头作为参考点,此时偏移量不能为负数
fseek(pf, 3, SEEK_SET);//当前指向trun中的n
printf("文件向右偏移三位后:%c\n", fgetc(pf));
printf("再次读取一位:%c\n", fgetc(pf));
printf("\n");
//演示三:以文件尾作为参考点,此时偏移量不能为正数
fseek(pf, -3, SEEK_END);//当前指向为lemonade.中的d
printf("文件向左偏移三位后:%c\n", fgetc(pf));
printf("再次读取一位:%c\n", fgetc(pf));
printf("\n");
//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3)、演示二:关于随机位置写入
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//文件读写
//
for (char ch = 'a'; ch <='z'; ch++)
{
fputc(ch,pf);
}
//演示一:以当前文件指针作为参考点
fseek(pf, -22, SEEK_CUR);
fputc('S', pf);
//演示二:以文件头作为参考点,此时偏移量不能为负数
fseek(pf, 6, SEEK_SET);
fputc('O', pf);
//演示三:以文件尾作为参考点,此时偏移量不能为正数
fseek(pf, -3, SEEK_END);
fputc('S', pf);
//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4.2、ftell
1)、基本介绍
ftell
函数主要作用是,获取文件位置指针当前位置相对于文件首的偏移字节数,即返回当前文件位置。相关函数链接:ftell
这在处理文件时,特别是在随机方式存取文件时非常有用,因为文件位置可能频繁地前后移动,使得程序难以直接确定文件的当前位置。
long int ftell ( FILE * stream );
参数:
FILE *stream
是一个指向FILE对象的指针,该对象标识了一个打开的文件流。
返回值: ftell
函数返回一个长整型(long)值,表示从文件开头到当前文件指针位置的字节数。如果发生错误,则返回-1L
,并设置errno
以指示错误原因。
文件模式说明: ftell
函数通常用于二进制模式(以"rb
"或"wb
"模式打开的文件)下的文件操作,但在文本模式下也能使用,但需注意换行符的处理可能会影响到结果。
使用场景举例:
1、确定文件当前位置: 在随机读写文件时,经常需要知道当前文件指针的位置,ftell函数就能满足这一需求。
2、计算文件大小: 通过将文件指针移动到文件末尾(使用fseek函数),然后调用ftell函数,可以获取文件的大小(以字节为单位)。
2)、相关演示
代码演示如下:
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//文件读写
//
for (char ch = 'a'; ch <='z'; ch++)
{
fputc(ch,pf);
}
fseek(pf, -22, SEEK_CUR);
fputc('S', pf);
long int pos = ftell(pf);
printf("%ld\n", pos);
//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4.3、rewind
1)、基本介绍
rewind
函数的主要作用是将文件内部的位置指针重新指向一个流(数据流/文件)的开头,并清除与文件流相关的错误和EOF(文件结束)标志。
这相当于调用fseek(stream, 0, SEEK_SET)
函数,即将文件指针移动到文件的起始位置。相关函数链接:rewind
void rewind(FILE *stream);
参数: FILE *stream
是一个指向FILE对象的指针,该对象标识了一个打开的文件流。
返回值: rewind
函数没有返回值。
对该函数的功能详细描述:
1、重新定位文件指针: 将文件内部的位置指针(不是文件指针,而是指向当前读写字节的指针)移动到文件的开头。随着对文件的读写操作,文件的位置指针会向后移动,而rewind函数则可以将它重置到文件的起始位置。
2、清除错误和EOF标志: 除了重新定位文件指针外,rewind函数还会清除与文件流相关的任何错误和EOF标志。这意味着,如果之前因为读取到文件末尾或发生错误而设置了EOF标志,调用rewind函数后,这些标志将被清除,文件流将处于可以继续读写的状态。
使用场景举例:
1、覆盖文件内容: 在写入文件后,如果希望覆盖原有内容,可以先使用rewind函数将文件指针移回文件开头,然后再进行写入操作。
2、重复读取文件: 在读取文件内容后,如果需要再次从头开始读取文件,可以使用rewind函数将文件指针移回文件开头。
2)、相关演示
相关演示:
int main()
{
//打开文件
FILE* pf = fopen("D:\\Daily\\test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//文件读写
//
for (char ch = 'a'; ch <='z'; ch++)
{
fputc(ch,pf);
}
fseek(pf, -22, SEEK_CUR);
fputc('S', pf);
long int pos = ftell(pf);
printf("%ld\n", pos);
rewind(pf);
printf("%ld\n", ftell(pf));
//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
5、其它相关内容
5.1、feof函数与文件读取结束判定
1)、关于feof函数说明
feof
函数是 C 语言标准库中的一个函数,用于检测文件读取操作是否已经达到了文件末尾(EOF, End Of File)。
需要注意的是,feof 并不是用来预测下一个读取操作是否会到达文件末尾的,而是用来在文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
int feof(FILE *stream);
参数: stream 是指向 FILE 对象的指针,该对象标识了一个打开的文件或其他输入/输出资源。
返回值: 函数返回 int 类型的值。如果流 stream 的文件位置指示器位于文件末尾,则返回非零值(通常是 1)。如果文件位置指示器没有位于文件末尾,或者发生错误,则返回零。但是,需要注意的是,当发生读取错误时,feof 也会返回 0,并且你需要使用 ferror 函数来区分是文件末尾还是读取错误。
演示如下:在这个示例中,我们使用 fgets
在一个循环中读取文件,直到它返回 NULL
,表示没有更多的数据可以读取。然后,我们检查 feof
来确认是否因为到达文件末尾而退出循环。
如果 feof
返回非零值,我们知道循环是因为 EOF
而正常结束的。
如果 feof
返回零, 并且 fgets 返回了 NULL,那么我们可能遇到了读取错误,可以使用 ferror
来进一步确认。
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "r");
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
// 处理读取到的数据
printf("%s", buffer);
}
// 检查是否因为到达文件末尾而退出循环
if (feof(fp)) {
printf("Reached end of file\n");
} else {
// 如果不是 EOF,则可能是读取过程中发生了错误
perror("Error reading file");
}
fclose(fp);
return 0;
}
2)、关于文件读取结束说明
根据文件类型的区别,判断文件读取结束具有一定区别:
对文本文件读取是否结束,有两种判断方法:1、判断返回值是否为 EOF;2、判断返回值是否为 NULL 。
例如:fgetc
函数中,判断文件读取结束是看是否返回 EOF
,而fgets
判断文件读取结束是看返回值是否为 NULL
。
对二进制文件读取是否结束,可通过返回值是否小于实际要读的个数来判断。
例如:fread
判断返回值是否小于实际要读的个数。
5.2、文件缓冲区
1)、什么是文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件。所谓缓冲文件系统,是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
2)、为什么需要文件缓冲区
文件缓冲区是一个链接内存与硬盘的过渡段,从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。之所以要这样做,是因为内存和硬盘处理数据的速度不一,若直接从磁盘中读取数据输送到内存, 会面临频繁访问、占用内存资源的现象。故而在二者之间搭建出一个中间商。
需要注意的是,缓冲区的大小是根据C编译的系统来决定。