C语言笔记第15篇:文件操作

news2024/11/24 15:51:39

1、为什么使用文件?

如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。

2、什么是文件?

磁盘(硬盘)上的文件就是文件。

但是程序设计中,我们一般谈两个文件,分别是程序文件、数据文件(从文件的角度来分类的)。

2.1 程序文件

程序文件包括源程序文件(后缀为.c)、目标文件(windows环境后缀为.obj),可执行文件(windows环境后缀为.exe)。

2.2 数据文件

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

本章讨论的是数据文件。

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

2.3 文件名

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

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

例如:c:\code\test.txt

为了方便起见,文件标识常被称为文件名

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

根据数据的组织形式,数据文件被称为文本文件或者二进制文件

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

如果要求在外出上以ASCII的形式存储,则需要再存储前转换,以ASCII字符的形式存储的文件就是文本文件

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

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

比如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。

代码栗子:

#include <stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("test.txt", "wb");//打开文件
	fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

4、文件的打开和关闭

4.1 流和标准流
4.1.1

程序的数据是要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。C程序真的文件、画面、键盘灯的数据输入输出操作都是通过流操作的。

一般情况下,我们要想向流里写数据,或者从流里读数据,都是要打开流,然后操作。

4.1.2 标准流

文件操作时我们需要自己打开文件(流),当操作完后需要自己关闭文件(流),那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语言程序在启动的时候,默认打开了3个流:

  • stdin - 标准输入流,大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout - 标准输出流,大多数环境中输出值显示器界面,printf函数就是将信息输出到标准输出流中。
  • stderr - 标准错误流,大多数环境中输出到显示器界面。

这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作的。

stdin、stdout、stderr 三个流的类型是:FILE*,通常称为文件指针

C语言中,就是通过FILE*的文件指针来维护流的各种操作的。

4.2 文件指针

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

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

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

struct _iobuf{
       char *_ptr;
       int _cnt;
       char* _base;
       int _flag;
       int _file;
       int _charbuf;
       int _bufsiz;
       char* tmpfname;
};
typedef struct _ioduf FILE;

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

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE类型的变量,并填充其中信息,该结构体类型的变量里存放着我们需要打开的文件的信息,因此被称为文件信息区。使用时不必关心细节。开辟好文件信息区后便会返回该信息区的地址,我们需要FILE*类型的指针来接收这个地址,这个FILE*类型指针就是流,属于文件的流。

一般都是通过FILE指针来维护这个FILE结构变量,这样使用更加方便。

FILE* PF;//文件指针变量

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

比如:

4.3 文件的打开和关闭

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

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

ANSI C规定使用fopen来打开文件,fclose来关闭文件。

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

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

fopen的函数声明:参数1:filename是所需的文件名,参数2:mode是打开流的形式,是输入还是输出。返回类型:FILE*是一个文件信息区的地址,通过该地址找到文件信息区访问文件。

fclose的函数声明:参数:ftream是我们打开文件时用来接收fopen返回值是创建的变量,将这个变量所存储的地址传参过去就可以回收文件信息区所占用的空间,就是关闭文件

fopen函数的参数2mode的打开形式是什么意思呢?怎么表示打开形式呢?

mode表示文件的打开模式,下面都是文件的打开模式:

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

"r+"(读写)

为了读和写,打开一个文本文件出错
"w+"(读写)为了读和写,建立一个新的文本文件建立一个新的文件
"a+"(追加)打开一个文本文件,在文件尾部进行读写建立一个新的文件
"rb+"(读写)为了读和写,打开一个二进制文件出错

"wb+"(读写)

"ab+"(追加)

为了读和写,建立一个新的二进制文件

打开一个二进制文件,在文件尾部进行读和写

建立一个新的文件

建立一个新的文件

注:fopen也是会打开失败的,如果打开失败,则返回空指针NULL。打开成功,则返回开辟好后的文件信息区的地址,所以使用前一定要判断一下。

然后就是fclose函数,它是用来关闭文件的,当我们指向文件信息区的FILE*的指针变量pf传进去,关闭好文件后一定要记得将pf置为NULL,因为我们虽然使用fclose函数释放了文件信息区,将文件信息区所占的内存还给操作系统了。但是指针变量pf始终是指向这块内存的,如果解引用访问使用这块内存就是非法访问了,所以当我们关闭文件后就把pf置为NULL。

注:如果以只读" w " 或" wb " 的形式打开文件,如果这个文件本身有数据,则会被清空,因为需要从头写入文件,所以要谨慎的使用只读的形式。

文件的打开方式:

文件打开有两种路径,一种是相对路径,一种是绝对路径

相对路径:

' . '表示当前路径,".."表示上一级路径

如果我们要打开的文件和程序所在的文件在一个路径下的话可以直接使用文件名打开,例如:

FILE* pf = fopen("test.txt","r");

因为没有路径表示编译器便会自动在程序文件相同路径的位置找该文件。

如果该程序文件在许多级文件内存储,如果我们要打开的文件也在这个多级文件中,但是在程序文件所在文件的上一级的上一级的位置,我们可以这样访问,例如:

FILE* pf = fopen(".\\..\\..\\test.txt","r");

一个‘ . ‘表示当前路径,两个 ".." 表示上一级路径。

还是将test.txt存放在当前数据文件所在的文件的上一级的上一级的位置,只不过我在这个位置又新建了一个文件夹叫hehe,然后我将test.txt放入这个hehe文件夹中,我们有什么方法可以访问呢:

FILE* pf = fopen(".\\..\\..\\hehe\\test.txt","r");

".\\..\\..\\hehe\\test.txt"意思就是在当前路径 ' . ' 的上一级 " .. " 的上一级" .. " 路径下的文件夹"hehe"里的文件"test.txt"。

绝对路径:

必须填写文件对应的路径,通过这个路径来找到对应的文件

但当我们想要打开其他路径的文件比如桌面上的文件时,我们就需要额外的输入路径,让编译器通过该路径找到对应的文件,例如:

FILE* pf = fopen("C:\\Users\\zpeng\\Desktop\\test.txt","w");
//绝对路径

在文件名前面添加一条路径,就可以根据这个路径找到对应文件。

场景1:当需要打开的文件和当前程序文件都是一个路径时,比如程序文件的项目是需要创建在一个文件夹中的,如果存在同一个文件夹,则不用填写路径。

场景2:当需要打开的文件和程序文件不在同一个文件夹,则需要在文件名前面添加上路径。

总结:文件路径也分为两个,分别是绝对路径相对路径

绝对路径:是在文件和程序文件位置不同时需要填写完整的路径来访问。

相对路径:是和程序文件在同一个文件里的,可能不一级文件,但是位置是有关联的,被称为相对路径

4.4 文件指针的概念

这里要说一下文件是有文件指针的,文件指针决定读取或写入的操作时从哪个位置开始的,如果程序开始运行并且使用过一次函数来访问当前文件信息区的文件了,文件指针就会发生改变,因为文件指针需要访问下一个位置的数据。

假设文件信息区的地址由变量pf来接收,那它的文件指针始终都不会重新开始,方便下一次调用文件访问函数可以从当前位置继续向后访问,所以没访问一次,文件指针会自动向后指向。除非是程序结束、使用rewind函数 或者是 又创建了一个文件信息区,否则当前pf关联的文件的文件指针始终都不会重新指向起始位置。

5、文件的顺序读写

5.1 顺序读写函数介绍
函数名功能适用于
fgetc字符输入函数所有输入流
fputc字符输出函数所有输出流
fgets文本行输入函数所有输入流
fputs文本行输出函数所有输出流
fscanf格式化输入函数所有输入流
fprintf格式化输出函数所有输出流
fread二进制输入文件
fwrite二进制输出文件

以上第三列表格适用于:所有输入流所有输出流文件,意思是每个对应函数的参数里有一个FILE*类型的指针变量参数,也就是流,所以都要有对应的流。所有输入流包括:标准输入流、文件流,所有输出流包括:标准输出流、文件流,二进制文件读写函数只能传文件流。我们也可以使用以上适用于标准输出流的函数数据通过标准输出流输出到屏幕上去,也可以使用以上适用于标准输入流的函数将我们从键盘输入的数据通过标准输入流读取出来,所以要记住,这些函数不仅仅是作用于文件的读取和写入

以上所有函数的声明:

int fputc(int character, FILE* stream);
int fgetc(FILE* stream);
int fputs(const char* str, FILE* stream);
char* fgets(char* str, int num, FILE* stream);
5.1.1 fputc的使用

fputc的声明:

int fputc(int character, FILE* stream);

fputc函数:参数1:character是需要输出的字符。参数2:stream是FILE*类型的指针,可以是标准输出流或者是对应文件的流。

fputc函数的功能:通过参数2的指向的文件信息区里的信息访问文件,并将参数1的字符输出到当前文件,一次只能写一个字符。

fputc函数的使用:

#include <stdio.h>
#include <string.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
    char str[] = "hello world";
    ine len = strlen(str);
	int i = 0;
    for(i = 0; i < len; i++)
    {
          fputc(str[i], pf);//将"hello world"一个一个输出到文件
    }
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

那我们也可以通过该函数将字符输出到屏幕上,就像printf一样:

#include <stdio.h>
int main()
{
    fputc('a',stdout);经过标准输出流直接将字符'a'输出到屏幕上
    return 0;
}

所以这里也就证明了FILE*类型的指针变量接收的文件信息区的地址是文件的流,顺序读写函数的参数FILE* stream是流,至于什么的流就看自己想怎么操作。

5.1.2 fgetc的使用

fgetc的声明:

int fgetc(FILE* stream);

fgect函数:参数:stream不用说就是流,但仅限于所有输入流,或文件的流,因为fgetc需要从输入流中获取数据。

fgetc函数的功能:将对应的输入流传参过去,getc会读取输入流中的字符,标准输入流是需要我们来输入字符,文件流是fgetc自己读取文件中的字符。

fgetc函数的使用:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
    char c = 0;
    while(c = fgetc(pf) != EOF)//会不断地向文件后读取数据
    {
         printf("%c",c);
    }
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

那我们也可以通过该函数读取我们键盘输入的字符,就像scanf一样:

#include <stdio.h>
int main()
{
	char c = fgetc(stdin);
	printf("%c\n", c);
	return 0;
}
int c = fgetc(stdin);
等价于 
int c = getchar();

到这里相信大家也都知道了这些函数可以通过标准输入流来获取我们键盘输入的数据或标准输出流将数据输出到屏幕上,那么下面的函数就不用在举这个例子了。

5.1.3 fputs的使用

fputs的声明:

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

fputs函数:参数1:str是需要输出的字符串,参数2:stream是FILE*类型的指针,可以是标准输出流或者是对应文件的流。

fputs函数的功能:将字符串根据输出流输出到对应的位置

fputs函数的使用:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	char str[] = "hello world";
	fputs(str, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

5.1.4 fgets的使用

fgets的声明:

char* fgets(char* str, int num, FILE* stream);

fgets函数:参数1:str是存储fgets从输入流读取的数据空间的地址,参数2:num是需要拷贝从输入流读取的字符的个数,参数3:stream是FILE*类型的指针,可以是标准输入流或者是对应文件的流。

fgets函数的功能:从参数3的输入流中读取num个字符拷贝到str。

如果fgets读取失败会返回一个空指针NULL,所以我们使用该函数时也可以判断一下有没有读取成功。

fgets的使用:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	char* str = (char*)malloc(10 * sizeof(char));
	fgets(str, 10, pf);
	printf("%s\n", str);
	fclose(pf);
	pf = NULL;
	return 0;
}

fgets不管读取多少个字符,最后一定会额外拷贝一个结束字符' \0 ' 放入str中。 

5.1.5 fprintf的使用

fprintf是格式化函数,printf也是格式化函数

fprintf函数的声明:

int fprintf(FILE* stream,const char* format,...);

fprintf和printf有什么区别,我们再看一下printf函数声明:

int printf(const char* format,...);

我们可以发现printf和fprintf之间就差一个参数stream,stream就是流,我们可以将stream的参数修改为文件流,后面的参数就和printf一样,printf本身的输出流是标准输出流stdout,输出到屏幕上的,所以我们就将文件想象成正常使用printf将数据输出到屏幕,其他参数就和printf一样。

如果这样的话,那fprintf可以做到和printf等价:

int main()
{
    char c = 'a';
    int a = 10;
    char str[] = "hello world";
    printf("%c %d %s",c,a,str);
    等价于
    fprintf(stdout,"%c %d %s",c,a,str);
    return 0;
}

fprintf的使用:

#include <stdio.h>
struct S
{
	int n;
	float f;
	char arr[20];
};
int main()
{
	struct S s = { 100, 3.14f, "zhangsan" };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	fprintf(pf, "%d %f %s", s.n, s.f, s.arr);
	fclose(pf);
	pf = NULL;
	return 0;
}
5.1.6 fscanf的使用

fscanf和scanf的参数也是相似的,就像fprintf和printf一样:

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

fscanf的使用:

#include <stdio.h>
struct S
{
	int n;
	float f;
	char arr[20];
};
int main()
{
	struct S c = { 0 };
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	fscanf(pf, "%d %f %s", &(c.n), &(c.f), c.arr);//输出到变量c中
	printf("%d %f %s", c.n, c.f, c.arr);
	fclose(pf);
	pf = NULL;
	return 0;
}
5.1.7 fwrite的使用

fwrite函数声明:

size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);

fwrite函数:参数1:ptr是一个const void* 的指针,是可以处理任意类型的数据的地址,不管是整型、浮点型还是结构体类型的地址都可以接收。参数2:size是类型大小,单位是字节。参数3:count是类型变量的个数。参数4:stream必须是文件的流,不能是其他流。

fwrite函数功能:通过参数1的指针将指针指向的count个数量的size类型大小的二进制数据输出到stream流。简单来说就是将数据在内存中的二进制数据传输进流。它的流只能是文件,不能是其他流,例如标准输出流。

#include <stdio.h>
struct S
{
	int n;
	float f;
	char arr[20];
};
int main()
{
	struct S s = { 200, 3.14f, "zhangsan" };
	FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "wb");//以二进制写的形式打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//
	//使用
	fwrite(&s, sizeof(struct S), 1, pf);//以二进制的形式写入文件
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
5.1.8 fread的使用

fread函数声明:

size_t fread(void* ptr, size_t size, size_t count, FILE* stream);

可以看到fread的函数声明和fwrite的函数声明是极其相似的。

fread函数和fwrite函数的区别:不同的就是前面那个void*的指针,fwrite是const修饰的,因为只是想读取它指向的空间里的数据并不想更改,所以使用了const。而fread是需要一个指针,通过这个指针指向的空间来接收读取的值,所以不能是const修饰。

fread函数:参数1:ptr是一个void* 的指针,是可以处理任意类型的数据的地址,不管是整型、浮点型还是结构体类型的地址都可以接收。参数2:size是类型大小,单位是字节。参数3:count是类型变量的个数。参数4:stream必须是文件的流,不能是其他流。

fread函数功能:通过seteam文件流将文件中的count个数量的size类型大小的二进制数据输入到ptr中。简单来说就是将文件中的二进制数据输入到ptr空间。它的流只能是文件,不能是其他流,例如标准输出流。

#include <stdio.h>
struct S
{
	int n;
	float f;
	char arr[20];
};
int main()
{
	struct S s = { 200, 3.14f, "zhangsan" };
	FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "rb");//打开文件
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//
	//使用
	struct S c = { 0 };
	fread(&c, sizeof(struct S), 1, pf);//将文件中二进制的数据读取出来
	printf("%d %f %s", c.n, c.f, c.arr);
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
5.2 对比一组函数:

scanf / fscanf / sscanf

printf / fprintf / sprintf

  • scanf - 针对标准输入流(stdin)的格式化输入函数
  • printf - 针对标准输出流(stdout)的格式化输出函数
  • fscanf - 针对所有输入流的格式化输入函数
  • fprintf - 针对所有输出流的格式化输出函数

那sscanf和sprintf两个函数是干什么的呢?

sprintf的函数声明:

int sprintf(char* str, const char* format,...)

可以从参数上发现sprintf就比printf多了一个char*类型的参数,那具体功能是什么?

sprintf函数功能:将格式化数据输出到字符串中

sprintf和printf的区别:printf是将格式化数据输出到标准输出流也就是屏幕上,sprintf则是将格式化数据输出到一个字符串里

#include <stdio.h>
struct S
{
	int n;
	float f;
	char arr[20];
};
int main()
{
	struct S s = { 200, 3.14f, "zhangsan" };
	char arr[30] = { 0 };
	sprintf(arr, "%d %f %s", s.n, s.f, s.arr);//将格式化数据输出到字符串arr
	printf("%s\n", arr);//打印arr接收到的格式化数据
	return 0;
}

既然可以使用sprintf函数将格式化数据输出到字符串中,那我们是否可以使用sscanf函数将字符串中的格式化数据提取出来呢?答案是可以的。

sscanf函数声明:

int sscanf(char* str, const char* format,...);

sscanf函数功能:将字符串中的格式化数据读取出来

sscanf和scanf的区别:scanf是将格式化数据输入到标准输入流也就是屏幕上,sscanf则是将格式化数据从字符串里读取出来。

#inlcude <stdio.h>
struct S
{
	int n;
	float f;
	char arr[20];
};
int main()
{
    //将格式化的数据输出到字符串数组arr中
	struct S s = { 200, 3.14f, "zhangsan" };
	char arr[30] = { 0 };
	sprintf(arr, "%d %.2f %s", s.n, s.f, s.arr);//将格式化数据输出到字符串arr
	printf("%s\n", arr);

    //从arr这个字符串中读取出格式化的数据
    struct S c = { 0 };
	sscanf(arr, "%d %f %s", &c.n, &c.f, c.arr);
	printf("%d %f %s", c.n, c.f, c.arr);
	return 0;
}

6、文件的随机读写

 什么是文件的随机读写?文件的随机读写就是定位到我们想要的位置开始向后读写,从开头向后读写就是顺序读写。定位位置向后读写就是随机读写。

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

fseek函数:参数1就是stream文件的流。参数2offset就是偏移量,是某个位置开始的向后的偏移量处的位置开始向后读写。而参数三origin就是决定这某个位置。

参数3:origin有三种位置:

ContstantReference  position
SEEK_SETBeginning  of  file (文件的起始位置)
SEEK_CURCurrent  position  of  the  file  pointer(文件指针的当前位置)
SEEK_ENDEnd of file(从文件的末尾位置向前偏移)

是从这些位置开始向后计算偏移量的位置,从计算好偏移量的位置开始向后读取。

例子:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//
	//使用
	fseek(pf, 6, SEEK_SET);//文件指针位置:起始位置向后偏移6个偏移量位置
	int ch = fgetc(pf);//读取当前文件指针位置的字符
	printf("%c", ch);

	fseek(pf, -3, SEEK_END);//文件指针位置:文件末尾向前偏移3个偏移量位置
	int ch = fgetc(pf);//读取当前文件指针位置的字符
	printf("%c", ch);

	fseek(pf, 5, SEEK_CUR);//文件指针位置:当前文件指针位置向后偏移5个偏移量位置
	int ch = fgetc(pf);//读取当前文件指针位置的字符
	printf("%c", ch);
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件里是存在文件指针的,正常情况下调用一次后该文件指针会向后指向,下一次调用是从后面继续向后访问。顺序读写函数是这样的。而随机读写函数是可以随机改变文件指针的指向,让文件指针改变位置从而进行读取或写入。

注:

1. 文件指针并不是我们熟知的C语言指针,而是一个表示文件位置的指针。

2. 偏移量为负数是向前偏移,偏移量为整数是向后偏移。

3. 不管文件指针的位置如何改变,文件都是自动的从前向后访问 

6.2 ftell

ftell的函数声明:

long int ftell(FILE* stream);

如果我们不知道当前的文件初始位置与文件指针之间的偏移量是多少时我们就可以使用ftell库函数,这个函数会计算好文件指针的偏移量并返回。

例子:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//
	fseek(pf, -3, SEEK_END);//文件指针位置:文件末尾向前偏移3个偏移量位置
	int ch = fgetc(pf);//读取当前文件指针位置的字符
	printf("%c\n", ch);

	
    int ret = stell(pf);//计算当前偏移量
    printf("%d\n",ret);
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

6.3 rewind

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

比如我随意用fseek来设置文件指针的位置导致乱了套,这时我们就可以使用rewind来让文件指针回到起始位置,功能比较简单,容易理解。

void rewind(FILE* stream);

例子:

#include <stdio.h>
int main()
{
	FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//
	fseek(pf, -3, SEEK_END);//文件指针位置:文件末尾向前偏移3个偏移量位置
	int ch = fgetc(pf);//读取当前文件指针位置的字符
	printf("%c\n", ch);

	//不知道当前文件指针的位置就重置
    rewind(pf);//重置文件指针位置
    int ch = fgetc(pf);//读取起始位置字符
    printf("%c\n",ch);
  
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

7、文件读取结束的判定

7.1 被错误使用的feof

牢记:在文件读取过程中,不能用 feof 函数的返回值直接来判断文件是否结束。

 feof 的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。

文件读取结束有两种原因:

1. 文件遇到末尾了

2. 文件读取错误了

1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc的错误),或者是NULL(gets的错误)

例如:

  • fgetc判断是否为EOF
  • fgets判断是否问NULL

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

例如:

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

注:fread的返回值是读取到的元素的个数。

7.2 ferror

feof是判断文件是否是因为读取到文件末尾而结束的,而ferror则是判断是否是因为读取失败而结束的,如果读取失败结束就返回1.

int ferror(FILE* stream);

文本文件读取结束判断:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int ch = 0;
	FILE* pf = fopen("C:\\Users\\linlu\\Desktop\\test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//
	while (ch = fgetc(pf) != EOF)
	{
		printf("%c ", ch);
	}
	printf("\n");
	
	//判断是什么原因结束的
	if (ferror(pf))//判断是否是读取失败导致结束的
	{
		puts("1/0 error when reading");
	}
	else if (feof(pf))//判断是否是读取到文件末尾结束的
    {
	    printf("End of file reached successfully");
    }
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

二进制文件的例子:

#include <stdio.h>
int main()
{
	double a[5] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
	FILE* pf = fopen("test.bin", "wb");//以输出二进制的形式打开
	fwrite(a, sizeof *a, 5, pf);
	fclose(pf);
	//
	double b[5];
	pf = fopen("test.bin", "rb");//以读取二进制的形式打开
	size_t ret_code = fread(b, sizeof *b, 5, pf);
	if (ret_code == 5){
		puts("Array read successfully,contents: ");
		for (int n = 0; n < 5; n++)
		{
			printf("%f ", b[n]);
		}
		putchar('\n');
	}
	else
	{
		//判断是什么原因结束的
		if (ferror(pf))//判断是否是读取失败导致结束的
		{
			puts("1/0 error when reading");
		}
		else if (feof(pf))//判断是否是读取到文件末尾结束的
		{
			printf("End of file reached successfully");
		}
	}
	//
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

8、文件缓冲区

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

#include <stdio.h>
#include <windows.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);
	printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区的函数,才将输出缓冲区的数据写到文件(磁盘)
	//注: fflush 函数在高版本的VS不能使用了
	printf("再睡眠10秒-此时再打开test.txt文件,发现文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注:fclose关闭文件时,也会刷新缓冲区
	pf = NULL;
	return 0;
}

这里可以得出一个结论:

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束时关闭文件,如果不做,可能导致读写文件问题。

C语言第15篇:文件操作到这里也就结束了,我们下一篇笔记再见,拜拜-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1830353.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 集合框架:LinkedList 的介绍、使用、原理与源码解析

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 014 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

【漏洞复现】畅捷通T+ App_Code.ashx 远程命令执行漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

什么是加密算法,有什么不同类型?

加密算法是一种数学函数或程序&#xff0c;它能够将原始的、可读的数据&#xff08;也称为“明文”&#xff09;转换为一种不可读的代码形式&#xff08;称为“密文”&#xff09;。这种转换是通过特定的算法和密钥来实现的&#xff0c;目的是保护数据的机密性和完整性&#xf…

HTML表单深度解析:构建互动的网页界面

表单是HTML中用于收集用户输入信息的重要元素&#xff0c;是网页与用户交互的关键组件。以下是一个典型的HTML表单示例&#xff0c;我们将会详细解析其中的各个元素及属性含义。 <form action"https://xx.xxx.xx/search" target"_self" method"ge…

CDN简介

CDN 的基本概念 CDN&#xff08;Content Delivery Network&#xff09;&#xff0c;即内容分发网络。 CDN是一种分布式网络架构&#xff1a;它由分布在不同地理位置的服务器组成网络&#xff0c;这些服务器协同工作以提供内容服务。 内容分发的核心目标 确保用户能够快速、可…

WordPress管理员后台登录地址修改教程,WordPress admin登录地址文件修改方法

我们使用WordPress时&#xff0c;管理员后台登录默认地址为“域名/wp-login.php”或“域名/wp-admin”&#xff0c;为了安全&#xff0c;一般会把此地址改掉&#xff0c;防止有人恶意来攻击咱的WordPress&#xff0c;今天出个WordPress后台登录地址修改教程&#xff0c;修改之后…

[oeasy]python0021_宝剑镶宝石_爱之石中剑_批量替换_特殊字符_特殊颜色

继续运行 &#x1f94b; 回忆上次内容 上次 运行了 game.py分析了 game.py也大致读懂了 game.py 这个 程序 可以进一步 进行修改吗&#xff1f;&#xff1f; 添加爱心 可以 把这个 ❤ 选中并复制 再粘贴到 虚拟机右侧的 剪贴板 然后 回到 游戏程序 进行修改和粘贴 按方向键h…

2024广州光亚展参展记录

参展总结 智控面板外观设计百家齐放&#xff0c;但始终逃不出几大设计元素的组合&#xff08;各种尺寸的屏、不同规则的按键切分、不同材质的面板材质&#xff09;&#xff1b;互联互通的趋势明显&#xff0c;接入米家、小度、涂鸦、HomeKit平台成为众多厂商的首选&#xff1b;…

JavaFX BorderPane布局

BorderPane布局顶部&#xff0c;底部&#xff0c;左&#xff0c;右或中心区域中的子节点。每个区域只能有一个节点。BorderPane的顶部和底部区域允许可调整大小的节点占用所有可用宽度。 左边界区域和右边界区域占据顶部和底部边界之间的可用垂直空间。 默认情况下&#xff0c…

一二三应用开发平台应用开发示例(3)——生成库表及后端代码

生成库表 前端页面的配置&#xff0c;也就是视图功能&#xff0c;我们先放一放&#xff0c;来看看生成库表和后端代码。 关闭实体配置界面&#xff0c;回到实体列表&#xff0c;勾选“文件夹”实体&#xff0c;点击“生成库表”&#xff0c;并确定。 系统提示成功后&#xff…

【Linux应用】Linux系统的设备管理——Udev

1.udev概述 udev是 Linux2.6内核里的一个功能&#xff0c;它替代了原来的 devfs&#xff0c;成为当前 Linux 默认的设备管理工具&#xff0c;能够根据系统中的硬件设备的状态动态更新设备文件&#xff0c;包括设备文件的创建&#xff0c;删除等。 udev以守护进程的形式运行&am…

python基础 002 - 1 基础语法

1 标识符&#xff08;identifier&#xff09;&#xff0c;识别码&#xff0c;表明身份 身份证&#xff0c;ID 定义&#xff1a;在编程语言中标识符就是程序员自己规定的具有特定含义的词&#xff0c;比如类名称、属性名称、变量名等&#xff0c; 在Python 中&#xff0c;pyt…

教学资源共享平台的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;老师管理&#xff0c;用户管理&#xff0c;成绩管理&#xff0c;教学资源管理&#xff0c;作业管理 老师账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用…

数组元素的内存地址计算【数据结构与算法C#版】

数组元素被存储在连续的内存空间中&#xff0c;这意味着计算数组元素的内存地址非常容易。给定数组内存地址&#xff08;首 元素内存地址&#xff09;和某个元素的索引&#xff0c;我们可以使用下方图 所示的公式计算得到该元素的内存地址&#xff0c;从而直接 访问该元素。 观…

python数据分析--- ch12-13 python参数估计与假设检验

python数据分析--- ch12-13 python参数估计与假设检验 1. Ch12--python 参数估计1.1 参数估计与置信区间的含义及函数版1.1.1 参数估计与置信区间的含义1.1.2 参数估计函数版1.1.3 参数估计函数版 1.2 Python单正态总体均值区间估计1.2.1 方差 σ 2 \sigma^2 σ2已知1.2.2 方差…

在 Blazor WebAssembly 中使用 EF Core 7 进行 CRUD 操作

如今&#xff0c;作为一名开发人员&#xff0c;如果我们想开发任何基于 Web 的应用程序&#xff0c;我们可以通过多种方式开发它们。现在&#xff0c;我们有几种选项来构建任何基于 Web 的应用程序&#xff0c;例如 MVC 框架、基于 API 的结构以及任何客户端框架&#xff0c;例…

HTML静态网页成品作业(HTML+CSS)——中华传统美德介绍网页(2个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;表格布局&#xff0c;未使用Javacsript代码&#xff0c;共有2个页面。…

sklearn 基础教程

scikit-learn&#xff08;简称sklearn&#xff09;是一个开源的机器学习库&#xff0c;它提供了简单和有效的数据分析和数据挖掘工具。sklearn是Python语言中最重要的机器学习库之一&#xff0c;广泛用于统计学习和数据分析。 以下是scikit-learn的基础教程&#xff0c;帮助您开…

Spring-kafka消费者消费的一些问题

前言 Spring Kafka 无缝集成了 Spring Boot、Spring Framework 及其生态系统中的其他项目&#xff0c;如 Spring Cloud。通过与 Spring Boot 的自动配置结合&#xff0c;开发者可以快速启动和配置 Kafka 相关的功能。无需编写大量样板代码即可实现 Kafka 的生产和消费功能&…

【面试干货】String、StringBuilder、StringBuffer 的区别

【面试干货】String、StringBuilder、StringBuffer 的区别 1、String2、StringBuffer3、StringBuilder4、性能对比5、使用建议 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;String、StringBuilder和StringBuffer是用…