欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

C语言——文件操作

最编程 2024-08-13 08:31:56
...

为什么使用文件

文件是存放在硬盘上的。当数据存放在内存中时,程序结束,数据就会丢失;

若想实现数据持久化,就要把数据存放在电脑硬盘上.

文件分类

程序文件:

源程序文件(例:后缀为.c)、目标文件(例:windows下后缀为.obj)、可执行程序.

数据文件:

我们写的源文件中的代码可能会涉及操作一个文件,比如向文件中写入内容,

该文件的内容是程序运行时读写的内容,该文件就是数据文件

例:

image.png

文件名

一个文件要有唯一的文件标识.

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

例:C:\code\test.txt

(C盘里的一个文本文件)

文件信息区

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息

(文件名、文件状态等)

这些信息是保存在一个结构体变量中,该结构体类型是系统声明,取名FILE.

image.png

打开一个文件时,

系统会根据文件的情况自动创建一个FILE类型变量,并自动填充其中信息,不用关心细节.

文件指针/文件类型指针

一般通过FILE指针来维护这个结构体变量.

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

通过文件指针可以找到某个文件的文件信息区,而文件信息区又强行与文件相关联,

所以可以通过文件指针进行文件操作.

文件的打开和关闭

要给瓶子灌水/放水,总要先打开瓶盖,最后拧上瓶盖,文件操作也是如此.

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

ANSIC规定用fopen函数来打开文件,用fclose函数关闭文件.

fopen函数

原型:

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

用法:

filename是文件名,mode是打开方式.

部分打开方式如下:

打开方式 含义 若文件不存在
"r"(只读) 打开一个已经存在的文本文件 出错
"w"(只写) 打开一个文本文件,若文件存在则销毁文件中的内容 建立一个新文件
"a"(追加) 向文本文件尾部添加数据 建立新文件

例:

FILE* pa = fopen("C:\\code\\test.txt", "r")//两个\转义为一个\

若文件存在:fopen函数会在内存中创建与该文件C:\code\test.txt相关联的文件信息区,

把文件信息区填好,并返回该文件信息区的起始地址.

若文件不存在:打开失败,返回空指针NULL.

    FILE* pa = fopen("C:\\code\\test.txt", "r");
    if(NULL == pa)//判空操作
    {
        perror("fopen");
        return 0;
    }
    else
    …………………………

fclose函数

原型: int fclose(FILE* stream)

用法:(接上面的代码)

fclose(pa);//关闭文件
pa = NULL; //pa及时置为空指针

若关闭成功,返回0.

文件内容的读写

我们电脑上有许多外部设备——例如键盘、屏幕、硬盘、网卡......

每种外部设备都有所差异,所以操作这些外部设备的方法不一样.

比如把数据打印到屏幕(把数据写到屏幕上)、从键盘中读取数据......

此时我们还要知道各种设备怎么读写.

为了降低学习成本,设计者在各种外部设备的上层封装了 流.

我们不用关心流怎么把数据放到对应的设备中,或者怎么从对应的设备中获取数据.

要读取信息时,从对应的流里读;要写信息时,把数据写到对应的流中。

流会自动找到对应的外部设备进行读写信息.

例如,我要打印信息到屏幕上——就会先把数据写到标准输出流中,标准输出流会把数据写到屏幕

image.png

注意:一个C程序运行起来,下面三个流是默认打开的,它们的类型竟然都是FILE*.

标准输入流stdin(关联键盘).

标准输出流stdout(关联屏幕).

标准错误流stderr(关联屏幕).

而文件流(关联硬盘中的文件)需要编程者自己打开,即文件指针。////

文件内容的顺序读写

按照一定的顺序进行读或写。

接下来先关注文件流

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

什么是输出?把程序的数据传输到外部。(把程序的数据写到流中,例如写文件,把信息写到文件中)

什么是输入?把外部数据传给程序。(从流中读取数据,例如读文件,读取文件信息到程序中)

fputc函数

原型:

int fputc(int c, FILE* stream)

功能:

写一个字符到 流 中.(例:可以把写一个字符到文件中)

使用:

        //以写的方式打开文件
	FILE* pa = fopen("D:\\cde.txt", "w");
	if (pa == NULL)
	{
		perror("fopen");
		return;
	}
	//写内容到文件中去
	fputc('a', pa);//注意会按顺序写abc
	fputc('b', pa);
	fputc('c', pa);
	//关闭文件
	fclose(pa);
	return 0;

image.png

        //再次运行一次,会发现文件之前的内容丢失,因为以"w"方式打开,文件已存在会先销毁文件内容
        FILE* pa = fopen("D:\\cde.txt", "w");
	if (pa == NULL)
	{
		perror("fopen");
		return;
	}
        for (int i = 'k'; i <= 'z'; i++)
	{
            fputc(i, pa);
	}
        flose(pa);
        pa=NULL;
        return 0;

image.png

fgetc函数

原型:

int fgetc(FILE* stream)

功能:

从流中读取一个字符,读取成功则返回字符的ANSIC码值.(可用来读文件)

若读取过程中 遇到一个错误 或者 读到文件末尾,返回EOF(end of file).

image.png

使用:

        //接上面的文件内容——已经写进去'k'~'z'
        FILE* pa = fopen("D:\\cde.txt", "r");//此时改为读文件
	if (pa == NULL)
	{
		perror("fopen");
		return;
	}
        int ch = 0;
	while (( ch = fgetc(pa) ) != EOF)
	{
         //先把fgetc(pa)读到的字符的ASCIC码赋给ch, 再判断ch是否为EOF
         //如果是EOF,循环停止
         //如果不是,把读到的字符打印出来
		printf("%c", ch);
	}

运行效果:

image.png

fputs函数

原型:

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

功能:

string:要输出的字符串;stream:流.

把一个字符串输出到流中,可以直接把一个字符串写入到文件中(最终效果).

使用:

        FILE* pf = fopen("D:\\cde.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 0;
	}
	fputs("shxmll", pf);//写一个字符串;
	fputs("ysgz\n", pf);//再写一个字符串,后面加换行符
	fputs("你好世界",pf);
	fclose(pf);

运行效果:

image.png

fgets函数

原型:

char *fgets(char *string,int n, FILE *stream)

功能:

将从流中最多读到的(n-1)个字符放到string中。

string:(存放读取的字符串)的位置

n:最大读取的字符个数

stream:从哪个流中读取数据

返回值:读到的字符串的(存储首地址)

当遇到文件结束标志EOF或一个错误时,会返回NULL

注意:

1 实际上,fgets最多会从文件中读取(n-1)个字符,放到string中,并添加'\0'.

        FILE* pf = fopen("D:\\cde.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 0;
	}
	char arr[20] = "xxxxxxxxxx";
	fgets(arr, 4, pf);
        fclose(pf);

image.pngimage.pngimage.png

2 fgets一次只读一行,但文本文档中第一行后还隐含一个换行字符,所以fgets也把它读进arr。

image.pngimage.pngimage.png

使用:

        FILE* pf = fopen("D:\\cde.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 0;
	}
	char arr[50] = { 0 };
	while (fgets(arr, 50, pf) != NULL)//用一次fgets后,下一次用就会读取文件的下一行
	{
                //只要不返回NULL,读取成功
		printf("%s", arr);
	}
        fclose(pf);

运行效果

最后一行没放'\n'

image.png

fprintf函数

原型:

int fprintf(FILE *stream, const char *format[,argument]...)

功能:

可以将格式化的数据打印/写到所有输出流中。

类比printf, printf是将格式化的数据打印/写到屏幕中(标准输出流)

例:

    typedef struct S//先声明一个学生结构体
    {
            char name[10];
            int age;
            double weight;
    }S;
    int main()
    {
           FILE* pf = fopen("text3.txt", "w");//在源文件路径下创建一个文件text3.txt
           if (pf == NULL)
           {
                 perror("fopen");
                 return 1;
           }
           S stu = { "xm", 20, 55.5 };
           //把内容写到文件上
           fprintf(pf, "%s %d %.2lf", stu.name, stu.age, stu.weight);
           //把内容写到屏幕上
           printf("%s %d %.2lf", stu.name, stu.age, stu.weight);
           return 0;
    }

运行效果:

image.png

image.png

注意:

fprintf的格式化输出之间没有空格,那么打印到文件中也是密密麻麻的。

fscanf函数

原型:

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

功能://

可以从所有输入流中,读取格式化的数据。

类比scanf, scanf可以从键盘中读取格式化的数据。

例:

        S stu = { 0 };//初始化学生结构体变量为0
        
        //从之前的text3.txt中读取信息
	fscanf(pf, "%s%d%lf", stu.name,&(stu.age), &(stu.weight));
        
        //打印到屏幕上
	fprintf(stdout, "%s %d %.2lf", stu.name, stu.age, stu.weight);

运行效果:

image.png

fwrite函数

原型:

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

功能:

将buffer这块空间的count个size字节大小的数据 以二进制的方式 写到流中

size_t size —— 一个数据的大小(单位byte)

size_t count —— 要写多少个数据

注意:

以二进制的方式写入,之后就要以二进制的方式读取,必须严格匹配.

使用:

typedef struct Student
{
	char name[10];
	int age;
	double weight;
}Student;
int main()
{
	//"wb"是以二进制的方式写文件,若文件存在也会先销毁文件内容
	FILE* pf = fopen("text3.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	Student stu = { "veri", 35, 66.6 };
	fwrite(&stu, sizeof(Student), 1, pf);
	fclose(pf);
	return 0;
}

用记事本打开文件后,会发现有很多内容很古怪,这是因为记事本不用来读取二进制内容。

但之后用二进制的方式读取文件也能读取到有效信息

image.png

fread函数

原型:

size_t fread(void *buffer, size_t size, size_t count, FILE *stream)

功能:

以二进制的方式从 流 读取count个size字节大小的数据放到buffer中。

返回值为成功读取的数据个数【所以size和count不能写反】

使用:

        //"rb"是以二进制方式读取文件
	FILE* pf = fopen("text3.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	Student stu = { 0 };
	while( fread(&stu, sizeof(Student), 1, pf) == 1)//每次读取一个数据(每个数据大小是sizeof(Student)),读取成功返回1,失败返回0
            printf("%s %d %.2lf", stu.name, stu.age, stu.weight);
        fclose(pf);    

运行效果:

打印出之前二进制写入的内容

image.png

文件内容的随机读写

以上的函数都默认从文件的第一行开始有序读写数据。

文件内部指针

在操作一个文件进行读写时,会有一个内部指针指向下一个将要读写的位置。

默认在打开文件时,该文件内部指针是指向第一行第一列的。

每进行一次读写时,该内部指针会自动移动,指向下一次读写的位置。

如果能改变文件内部指针的位置,就能实现读写任意地方的内容

fseek函数

原型:

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

功能:

概括:移动文件内部指针到特定的位置

origin 有3个值

1、 SEEK_CUR 文件内部指针当前位置

2、 SEEK_END 文件末尾

3、 SEEK_SET 文件开始位置

offset —— 偏移量。若为正,代表向右偏移;为负,向左偏移

例:

//pf和pa是2个不同的文件指针

feek(pf, 3, SEEK_CUR);//将文件内部指针向右移3个单位

feek(pa, -2, SEEK_END);//将文件内部指针移到文件末尾向左2个单位的位置

注意:

fseek只是把文件内部指针移到指定的位置,并不会进行读写;

但如果以追加方式打开文件,feek不起作用。

使用:

要读取的文件

image.png

        FILE* pf = fopen("D:\\cde.txt", "r");
	char re1 = fgetc(pf);
	char re2 = fgetc(pf);
	fseek(pf, 3, SEEK_CUR);//使文件内部指针向右偏离3个字符
	char re3 = fgetc(pf);
	printf("%c%c%c", re1, re2, re3);

输出效果

image.png

拓展:

文件末尾位置:可以理解为 【没有任何数据可以读写】的开始位置

        FILE* pf = fopen("D:\\cde.txt", "w");
	fputc('a', pf);
	fseek(pf, 3, SEEK_CUR);//使文件内部指针向右偏离3个字符
	fputc('c', pf);
	fseek(pf, 3, SEEK_END);//使文件内部指针移到文件结束的后面3个字符
	fputc('k', pf);
	//注意:文件里并不存在EOF,这只是一种状态,在没有可读取的数据时,还想读取数据,
	//就会返回EOF,但写文件就可以在没有数据的地方写
	fclose(pf);

程序运行后文件情况:

image.png

rewind函数

原型:

void rewind(FILE* stream)

功能:

将文件内部指针移动到文件开始位置。

实际上也能用 fseek(pf, 0, SEEK_SET)代替,但rewind(pf)更简便.

ftell函数

原型:

long ftell(FILE* stream)

功能:

返回文件内部指针当前位置离文件开始位置的偏移量.

文件读取完成的判断

feof函数

原型:

int feof(FILE* stream)

功能:

判断当前文件内部指针是否指向文件末尾。是:返回非0值;否:返回0。

通常用来判断读取文件完成后,是读到文件末尾结束,还是遇到了错误提前结束。

例:

image.png

        FILE* pf = fopen("D:\\cde.txt", "r");
	char w = 0;
	while ( ( w=fgetc(pf) ) != EOF)
	{
		printf("%c\n", w);
	}
	if (feof(pf) == 0)
	{
		printf("读取文件过程中遇到错误!!!\n");
	}
	else
	{
		printf("正常读取文件\n");
	}
	fclose(pf);

image.png

怎么判断文件全部数据读取完成?

1、文本文件读取结束:

fgetc的返回值是EOF,fgets的返回值是NULL

2、二进制文件读取结束:

fread的返回值小于读取的数据个数。

    //一般用循环一个数据一个数据来读,读到返回1;没有读到,就读取结束返回0
    while(fread(&stu, sizeof(Student), 1, pf) == 1)
    {
        printf(".....");
    }

文件缓冲区

系统自动为正在使用的文件开辟一块文件缓冲区【不同于文件信息区】

从内存向硬盘中的文件输出数据时(写文件时),会把数据先写到文件缓冲区中,

装满缓冲区后(或刷新缓冲区)才会输出到文件中;

读取文件时,会把读到的数据先放到缓冲区中,

装满缓冲区后(或刷新缓冲区)才会把数据逐个给对应的程序变量

但输入和输出的缓冲区不一样

image.png

        //证明缓冲区的存在
        FILE* pf = fopen("D:\\cde.txt", "w");
	fputs("abcde", pf);
	Sleep(10000);//程序睡眠10秒,在这10秒的时间里打开文件,会发现文件并没有写入数据
	printf("缓冲区刷新完成\n");//此时再打开文件,文件才会有数据
	fclose(pf);//fclose也会刷新文件缓冲区

为什么要及时关闭文件?fclose会刷新文件缓冲区,防止有数据在内存的缓冲区中,导致数据丢失。

当然也能用fflush(pf)手动刷新文件缓冲区。