文件操作详解

为什么使用文件

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

什么是文件

磁盘(硬盘)上的文件是文件。
但是在程序设计中,我们一般说的文件有两种:程序文件、数据文件(从文件的功能的角度来分类的)。

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

  2. 数据文件
    文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或输出内容的文件。
    我们主要讨论的是数据文件。

  3. 文件名
    一个文件要有一个唯一的文件标识,以便用户识别和引用。
    文件名包含3部分:文件路径+文件名主干+文件后缀
    例如: c:\code\test.txt
    这里的c:\code\就是文件路径,test就是文件名主干,.txt就是文件后缀
    为了方便起见,文件标识常被称为文件名。

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

二进制文件和文本文件

根据数据的组织形式,数据文件被称为文本文件或者二进制文件
我们可以看看一个test.c生成的目标文件test.obj它就是二进制文件,我们在文本编辑器上是看不懂的,而一个文本文件以ASCII码存储的,我们可以在文本编辑器上看懂。
在这里插入图片描述

所以数据在内存以二进制的形式存储,如果不加转换的输出到外存的文件中,我们是看不出内容的,这就是 二进制文件 ,如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。
以ASCII字符的形式存储的文件就是 文本文件
一个数据在文件中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII码形式存储,也可以使用二进制形式存储。如整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),二进制形式输出,则在磁盘上只占4个字节。
在这里插入图片描述

文件的打开和关闭

文件的基本操作

  1. 打开文件
  2. 读/写文件
  3. 关闭文件
  1. 流和标准流

  • 在这里插入图片描述
    我们程序储存在内存中的数据需要输出到各种外部设备上,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,程序员需要知道各种硬件设备的输入输出,所以为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想像成流淌着字符的河。
    C程序针对文件、画面、键盘的数据输入输出操作都是通过流操作的。
    程序员不需要知道流是如何与各种硬件设备进行数据传输,这是C语言程序底层和操作系统需要处理的,我们只需要将数据写入流和从流读取数据即可。
    一般情况下,我们要想向流里写数据,或者从流里读取数据,都是需要打开流,然后操作。
  1. 标准流
    我们为什么从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
    那是因为我们C语言程序在启动的时候,默认打开了3个流:
  • stdin 标准输入流,大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
  • stderr 标准错误流,大多数环境中输出到显示器界面。

这是默认打开了这三个流,我们使用scanf,printf等函数就可以直接进行输入输出操作的。
stdin、stdout、stderr三个流的类型是: FILE* 通常称为文件指针。
C语言中,就是通过FILE*的文件指针来维护流的各种操作的。

文件指针

缓存文件系统中,关键的概念是“ 文件类型指针 ”,简称“ 文件指针 ”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名 FILE
例如:VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型声明:

struct _iobuf{
		char *_ptf;
		int _cnt;
		char *_base;
		int _flag;
		int _file;
		int _charbuf;
		int _bufsiz;
		char *_tmpfname;
		};
typedef struct _iobuf FILE;  

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样用起来更加方便。
如果我们要创建一个FILE*指针变量,可以这样写:

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

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。所以我们可以通过文件指针变量能够直接间接找到与它关联的文件。
比如:
在这里插入图片描述

  1. 文件的打开和关闭
    文件在读写之前应该先 打开文件 ,在使用之前结束之后应该 关闭文件
    在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件关系。
    ANSI C规定使用 fopen 函数来打开文件,fclose 函数来关闭文件。
//打开文件
FILE * fopen ( const char * filename, const char * mode );

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

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

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

什么是输出数据,什么是输入数据
在这里插入图片描述
接下来使用代码帮助我们理解

#include <stdio.h>
int main()
{
	FILE* pf;
	//打开文件
	pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//读文件
	//......

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

当程序所在的当前目录下没有该文件,就会打印出错的信息。
在这里插入图片描述

文件的顺序读写

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

上面说的适用于所有输入流一般适用于标准输入流和其他输入流(如文件输入流);所有输出流一般指适用于标准输出流和其他输出流(如文件输出流)。
接下来我们逐个来学习

  • fputc
int fputc ( int character, FILE * stream );

fgetc就是把character写进stream流中,函数返回值是int,当成功写入,则返回字符,也就是ASCII码,当遇到文件末尾或者出现错误,则返回EOF

我们通过代码来理解

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件,把a到z写进去
	char ch = 0;
	for (ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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

  • fgetc
int fgetc ( FILE * stream );

fgetc从流中读取字符,并返回ASCII码,为什么返回值不是char,而是int呢
如果成功读取字符,则返回的是字符的ASCII码值–char
如果遇到文件末尾,或读取失败的时候则会返回EOF(-1),整型-1,而不是字符型,为了顺利返回EOF,所以是int。

我们通过代码来理解

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}
	return 0;
}

在这里插入图片描述

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

fputs就是将str所指向的字符串写入到流中,遇到 \0 结束。
如果成功读取则返回一个非负整数,如果读取失败则返回EOF(-1)。
fputs写完不会自动换行,需要将 \n 写入。

我们通过代码来理解

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("hello world", pf);
	fputs("you can do it", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

没有打印错误信息,表示成功打开文件,再来看看我们的test.txt。
在这里插入图片描述

我们可以看到我们第一次调用完fputs之后写完再一次调用fputs写入,并没有出现换行。

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("hello world\n", pf);
	fputs("you can do it", pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

当我们加入换行符之后,才会换行。

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

fgets是从流中读取 num-1 个字符到字符串中,为什么是num-1,因为要在末尾添加一个 \0 ,所以读取num-1个。
fgets读取到 \n 后不会继续往下读。
如果读取成功,则返回字符串首地址,如果读取失败,则返回NULL

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char str[10] = { 0 };
	fgets(str, 10, pf);
	printf("%s", str);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

可以看出读取的是九个字符
在这里插入图片描述
最后第十个放的是\0
如果我们读20个呢
先来看看我们的test.txt
在这里插入图片描述
如果输出20个,如果读到\n不停止,那应该是"hello world\nyou can "我们用代码来看

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char str[20] = { 0 };
	fgets(str, 20, pf);
	printf("%s", str);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们可以看到他读到\n就停了,如果我们想读取全部的内容,则需要再循环调用fgets这个函数并且打印来实现

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char str[20] = { 0 };
	while (fgets(str, 20, pf) != NULL)
	{
		printf("%s", str);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

  • fprintf
int fprintf ( FILE * stream, const char * format, ... );//把格式化的数据写到流里

int printf ( const char * format, ... );//把格式化的数据写到标准输出流(stdout)里

所以fprintf与printf的用法相似,只是前面需要加输出到哪个输出流里。
我们通过代码来理解:

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "zhangsan", 20, 97.5f };
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//把s的数据按照格式写进文件里
	fprintf(pf, "%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

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

  • fscanf
int fscanf ( FILE * stream, const char * format, ... );//从指定的流里读格式化的数据

int scanf ( const char * format, ... );//从标准输入流(stdin)里读格式化的数据

所以fscanf与scanf的用法相似,只是前面需要加指定从哪个输出流里读取。我们通过代码来理解

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct S s = { 0 };
	//读文件
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
	//知道文件的内容,所以格式不能写错,不然会读取错误
	//将读取的内容打印到显示器上
	printf("%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们都知道以上内容的函数都可以用于各种输出流,所以我们也可以用fwrite将他们显示在显示器上。

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	struct S s = { 0 };
	//读文件
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
	//知道文件的内容,所以格式不能写错,不然会读取错误
	//将读取的内容打印到显示器上
	fprintf(stdout,"%s %d %f", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
再举一个例子:用fputc显示字符在显示器上

#include <stdio.h>
int main()
{
	fputc('a', stdout);
	return 0;
}

在这里插入图片描述

  • 对比一组函数

scanf/fscanf/sscanf
printf/fprintf/sprintf

我们先来了解一下sscanf和sprintf

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

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

sscanf就是从字符串中读取格式化的数据。
sprintf就是将格式化的数据转换成字符串。

我们通过代码来理解。

#include <stdio.h>
struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "zhangsan",20,96.5f };
	char str[50];
	sprintf(str, "%s %d %f", s.name, s.age, s.score);
	printf("以字符串打印:%s\n", str);
	struct S t = { 0 };
	sscanf(str, "%s %d %f", t.name, &(t.age), &(t.score));
	printf("以格式化打印:%s %d %f", t.name, t.age, t.score);
	return 0;
}

在这里插入图片描述
我们总结和对比这三组函数

scanf 从标准输入流读取格式化的数据,
printf 向标准输出流写格式化的数据 。
fscanf 从指定的输入流上读取格式化的数据,
fprintf 把数据以格式化的形式打印在指定的输出流上。
sscanf 从字符串中读取格式化的数据
sprintf 将格式化的数据转换成字符串。

我们上面学的函数都是适用于所有输入或输出流,但是fread和fwrite只适用于文件输入或输出流。

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

ptr 指向一个数组,size指数组中每一个元素的大小,count指数组的大小,stream指写入哪个流中。
返回成功写入的个数

我们通过代码来理解。

#include <stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	//打开二进制文件
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	int sz = sizeof(arr) / sizeof(arr[0]);
	fwrite(arr, sizeof(arr[0]), sz, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
因为是二进制文件,所以我们在文本编辑器上没法看到内容,接下来我们用发fread进行读取并打印来验证。

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

ptr 数组的首地址,接受数据,size 每一个元素的大小,count 读取多少个元素,从哪个文件流中读取
返回读取的个数,所以我们可以通过最后一次读取的数目是否等于我们指定的数目来判断他是不是读到文件的结尾。

我们通过代码来理解

#include <stdio.h>
int main()
{
	int arr[5] = { 0 };
	//打开二进制文件
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int sz = sizeof(arr) / sizeof(arr[0]);
	fread(arr, sizeof(arr[0]), sz, pf);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们还可以这样写,通过返回读取的个数来输出打印。

#include <stdio.h>
int main()
{
	int arr[5] = { 0 };
	//打开二进制文件
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int i = 0;
	while (fread(arr+i, sizeof(arr[0]), 1, pf))
	{
		printf("%d ", arr[i]);
		i++;
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

文件的随机读写

  • fseek

fseek根据文件指针的位置和偏移量来定位文件指针,当我们打开一个文件时,文件指针默认在文件头。

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

offset 就是文件指针的偏移量,orgin就是算偏移量的起始位置,有三个值,文件的起始位置SEEK_SET,文件指针当前的位置SEEK_CUR,文件末尾SEEK_END
成功则返回0,不成功则返回非0。

我们通过代码来理解,先来看看我们test.txt中的内容
在这里插入图片描述

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	ch = fgetc(pf);
	printf("%c\n", ch);//打印的是 o

	fseek(pf, 3, SEEK_CUR);//onward and upward此时文件指针指向了n,再偏移3,打印的是 r
	ch = fgetc(pf);
	printf("%c\n", ch);

	fseek(pf, 3, SEEK_SET);//onward and upward此时文件指针指向了r,
							//但是是从文件头偏移3,文件指针指向了a,打印的是 a
	ch = fgetc(pf);
	printf("%c\n", ch);

	fseek(pf, -3, SEEK_END);//onward and upward此时文件指针指向了a,
							//但是是从文件末尾偏移-3(指针指向d的后面,往前移动3,停在了a),
							//文件指针指向了a,打印的是 a
	ch = fgetc(pf);
	printf("%c\n", ch);
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

在这里插入图片描述

  • ftell

ftell函数返回指针相对于起始位置的偏移量

long int ftell ( FILE * stream );

我们通过代码来理解

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	ch = fgetc(pf);
	printf("%c\n", ch);//打印的是 o
	int ret = ftell(pf);
	printf("%d\n", ret);//偏移量为1

	fseek(pf, 3, SEEK_SET);//onward and upward此时文件指针指向了r,
	//但是是从文件头偏移3,文件指针指向了a,打印的是 a
	ch = fgetc(pf);
	printf("%c\n", ch);
	ret = ftell(pf);
	printf("%d\n", ret);//偏移量为4

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

	return 0;
}

在这里插入图片描述
我们还可以通过这两个函数求文件字符串的长度

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ret = 0;
	fseek(pf, 0, SEEK_END);//文件指针指向了文件末尾
	ret = ftell(pf);
	printf("%d\n", ret);//onward and upward共17个字符

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

	return 0;
}

在这里插入图片描述

  • rewind

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

void rewind ( FILE * stream );

我们通过代码来理解

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	ch = fgetc(pf);
	printf("%c\n", ch);//打印的是 o

	fseek(pf, -3, SEEK_END);//onward and upward此时文件指针指向了o,
							//但是是从文件末尾偏移-3(指针指向d的后面,往前移动3,停在了a),
							//文件指针指向了a,打印的是 a
	ch = fgetc(pf);
	printf("%c\n", ch);

	rewind(pf);//回到文件的起始位置
	ch = fgetc(pf);
	printf("%c\n", ch);//此时打印的是 o

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

	return 0;
}

在这里插入图片描述

文件读取结束的判定

  1. 被错误使用的feof

在文件的读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。
feof的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。
当文件读取结束了,是什么原因导致结束的呢
有两个原因:可能是遇到文件末尾(feof),可能是读取的时候发生了错误(ferror);
当打开一个流时,这个流上有两个标记值:是否遇到文件末尾(feof检测这个标记值),是否发生错误(ferror检测这个标记值)。

  • feof
int feof ( FILE * stream );

当文件是因为读到文件结尾结束,feof检测到文件末尾那个标记值就返回一个非0的值,否则返回0。

  • feeror
int ferror ( FILE * stream );

当文件是因为发生错误结束,ferror检测到发生错误那个标记值就返回一个非0的值,否则返回0。

  1. 判断文件读取是否结束
  • 文本文件读取是否结束,判断返回值是否为 EOF (fgetc),或者 NULL (fgets)
  • 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

我们通过代码来理解:

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c", ch);
	}
	if (feof(pf))
	{
		printf("\n遇到文件末尾,读取正常结束\n");
	}
	if (ferror(pf))
	{
		perror("\nfgetc");
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//文件以读的方式打开,当我们进行写操作就会发生错误
	int ch = 0;
	for(ch = 'a'; ch <= 'z'; ch++)
	{
		fputc(ch, pf);
	}
	if (feof(pf))
	{
		printf("\n遇到文件末尾,读取正常结束\n");
	}
	if (ferror(pf))
	{
		perror("\nfputc");
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
我们再来完成一个文件拷贝的任务:

#include <stdio.h>
int main()
{
	//打开文件
	FILE* pfin = fopen("test1.txt", "r");
	if (pfin == NULL)
	{
		perror("fopen:test1.txt");
		return 1;
	}
	FILE* pfout = fopen("test2.txt", "w");
	if (pfout == NULL)
	{
		fclose(pfin);//走到这个语句,说明test.txt1已经打开
					//此时,test.txt2打不开,无法进行,应该将test.txt1关闭
		pfin = NULL;
		perror("fopen:test2.txt");
	}
	//将test1.txt拷贝到test2.txt中
	int ch = 0;
	while ((ch = fgetc(pfin)) != EOF)
	{
		fputc(ch, pfout);
	}
	//关闭文件
	fclose(pfin);
	fclose(pfout);
	pfin = NULL;
	pfout = NULL;
	return 0;
}

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

文件缓冲区

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

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值