文件操作(详解)

前言:内容包括:为什么使用文件,什么是文件,文件的打开和关闭,文件的顺序读写,文件的随机读写,文本文件和二进制文件,文件读取结束的判定,文件缓冲区

目录

为什么使用文件

什么是文件

程序文件

数据文件

文件名

文件的打开和关闭

实例: 以写("w")的形式打开文件

 文件的顺序读写

实例:

fputc

fputc适用于文件流: 

fputc适用于标准输出流:stdout(屏幕): 

fgetc

fgetc适用于文件流: 

fgetc适用于标准输入流:stdin(键盘) :

fputs 

fputs适用于文件流: 

fputs适用于标准输出流:stdout(屏幕):

fgets

fgets适用于文件流: 

fgets适用于标准输入流:stdin(键盘):

fprintf

fprintf适用于文件流: 

fprintf适用于标准输出流:stdout(屏幕): 

fscanf

fscanf适用于文件流: 

fscanf适用于标准输入流:stdin(键盘):

对比一组函数:

sprintf

sscanf 

 fwrite

fread

文件的随机读写 

 fseek

 ftell

rewind 

​编辑

 文本文件和二进制文件

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

文本文件:以ASCII字符的形式存储的文件

数据在内存中的存储:

文件读取结束的判定  

feof:当文件已经读取结束后,判断读取失败的原因是否是:遇到文件末尾结束

文本文件读取结束的判定: 

fgetc 判断是否为 EOF 

fgets 判断返回值是否为 NULL

二进制文件读取结束的判定:

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

 文件缓冲区


为什么使用文件

数据是存放在内存中的,当程序退出时数据不会被保留,而使用文件我们可以将数据直接存在电脑的硬盘上,做到了数据的持久化

什么是文件

磁盘中的文件就是文件,文件分为两种:程序文件,数据文件

程序文件

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

数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,
或者对其输出内容的文件
比如我们在设计一个通讯录时,录入的联系人信息除非我们主动删除外会永久保存下来,那么就需要将录入的信息保存到文件中(对文件的写操作(输出))

当我们存入3个联系人信息,退出通讯录时,文件中会保留这3个联系人的信息,下一次我们重新打开通讯录时可以看到之前存入的3个联系人信息,这就需要读取文件的内容(对文件的读操作(输入))

文件名

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

文件的打开和关闭


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

通过一个FILE类型的指针FILE*来维护FILE结构的变量

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

pf指针指向某个文件的文件信息区(一个FILE结构体类型的变量),通过该文件信息区中的信息就能访问某个文件,故而通过文件指针变量就能找到与它关联的文件
文件操作(详解)_第1张图片

fopen函数:打开文件

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

第一个参数:要打开文件的文件名

第二个参数:打开的方式(读/写) 

打开的方式:

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

注意:“w”的形式打开文件,若是文件没有存在,则建立一个新的文件

                                             若是文件已经存在,则原有文件中的内容会被销毁(不复存在) 

fclose函数:关闭文件

int fclose ( FILE * stream );

实例: 以写("w")的形式打开文件

#include
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	//写文件

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

文件操作(详解)_第2张图片

 可以看到最开始我并没有创建test.txt文件

程序执行之后:

文件操作(详解)_第3张图片

 文件的顺序读写

功能 函数名 适用于
字符输入函数 fgetc 所有输入流:文件流/标准输入流(stdin)
字符输出函数 fputc 所有输出流:文件流/标准输出流(stdout)
文本行输入函数 fgets 所有输入流:文件流/标准输入流(stdin)
文本行输出函数 fputs 所有输出流:文件流/标准输出流(stdout)
格式化输入函数 fscanf 所有输入流:文件流/标准输入流(stdin)
格式化输出函数 fprintf 所有输出流:文件流/标准输出流(stdout)
二进制输入 fread 文件
二进制输出 fwrite 文件

读写文件:文件流 

终端设备(屏幕):标准输出流(stdout)

键盘:标准输入流(stdin)

屏幕:标准错误流(stderr)

c程序默认会打开三个流:stdin stdout stderr

实例:

fputc

int fputc ( int character, FILE * stream );

fputc适用于文件流: 

1 打开文件

2 写文件 

3  关闭文件

文件操作(详解)_第4张图片

 可以看到在未对文件进行写(输出)操作时,test.txt中的文件没有内容

#include
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	//写文件-将26个字母写进test.txt中
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		fputc('A'+i, pf);
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件操作(详解)_第5张图片

fputc适用于标准输出流:stdout(屏幕): 

int main()
{
	fputc('A', stdout);
	fputc('B', stdout);
	return 0;
}

 文件内容被写进26个字母

fgetc

int fgetc ( FILE * stream );

fgetc适用于文件流: 

1 打开文件

2 读文件 

3  关闭文件

文件操作(详解)_第6张图片

现在的test.txt文件中存放了26个字母, 现在我们读取这26个字母,打印在屏幕上

#include
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	//写文件-将26个字母写进test.txt中
	int ch = 0;
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		ch = fgetc(pf);
		printf("%c ", ch);
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

fgetc适用于标准输入流:stdin(键盘) :

int main()
{
    printf("请输入字符:");
	int ch = fgetc(stdin);//从键盘上读取字符
	printf("字符:%c\n", ch);//打印字符
	return 0;
}

文件操作(详解)_第7张图片

fputs 

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

fputs适用于文件流: 

1 打开文件

2 写文件

3 关闭文件

文件操作(详解)_第8张图片

 最开始test.txt文件中的内容为空

#include
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	//将"hello world"写进test.txt中
	fputs("hello world\n", pf);
	fputs("hello\n", pf);
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件操作(详解)_第9张图片

fputs适用于标准输出流:stdout(屏幕):

#include
int main()
{
	fputs("hello", stdout);
	return 0;
}

 

fgets

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

num:最多读num个,但实际上只会读取num-1个,因为\0占了一个空间,只能存放num-1个字符 

fgets适用于文件流: 

#include
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	char arr[20] = { 0 };
	fgets(arr,5,pf);
    //读取5个字符,实际上只会读取4个,即hell
	printf("%s", arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 

fgets适用于标准输入流:stdin(键盘):

#include
int main()
{
	char arr[20] = { 0 };
	printf("请输入:");
	fgets(arr, 6, stdin);
	printf("%s", arr);
	return 0;
}

fprintf

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

fprintf适用于文件流: 

1 打开文件

2 写文件

3 关闭文件

文件操作(详解)_第10张图片

 开始时文件内容为空

#include
struct S
{
	int n;
	char c;
	char name[20];
};
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	struct S s = { 1,'A',"zhangsan"};
	fprintf(pf, "%d %c %s", s.n, s.c,s.name);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件操作(详解)_第11张图片

fprintf适用于标准输出流:stdout(屏幕): 

#include
int main()
{
	int n = 100;
	char c = 'A';
	fprintf(stdout, "%d %c", n, c);
	return 0;
}

fscanf

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

fscanf适用于文件流: 

1 打开文件

2 读文件

3 关闭文件

文件操作(详解)_第12张图片

文件中已存在如上信息,我们可以通过fscanf读取其中的数据,打印到屏幕上

#include
struct S
{
	int n;
	char c;
	char name[20];
};
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");//打印错误信息
		return 1;
	}
	struct S s = {0};
	fscanf(pf,"%d %c %s", &(s.n), &(s.c), s.name);
	printf("%d %c %s", s.n, s.c, s.name);

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

fscanf适用于标准输入流:stdin(键盘):

#include
int main()
{
	int n = 0;
	char c = 0;
	printf("请输入:");
	fscanf(stdin, "%d %c", &n, &c);
	fprintf(stdout, "%d %c", n, c);
	return 0;
}

对比一组函数:

scanf/fscanf/sscanf
printf/fprintf/sprintf

scanf:针对标准输入流(stdin)的格式化的输入函数

printf:针对标准输出流(stdout)的格式化的输出函数

fscanf:针对所有输入流(文件流/stdin)的格式化输入函数

fprintf:针对所有输出流(文件流/stdout)的格式化输出函数

sscanf:把字符串转换成格式化的数据

sprintf:把格式化的数据转换成字符串 

sprintf

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

输出数据到str 

#include
struct S
{
	int n;
	char c;
	char name[20];
};
int main()
{
	struct S s = { 100,'A',"wangwu" };
	//把格式化的数据转换成字符串
	char arr[40] = { 0 };
	sprintf(arr, "%d %c %s", s.n, s.c, s.name);
	printf("字符串形式: %s", arr);
	return 0;
}

arr数组中已经存放好了由格式化的数据转成的字符串形式

 

sscanf 

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

从s中读取数据 

#include
struct S
{
	int n;
	char c;
	char name[20];
};
int main()
{
	struct S s = { 100,'A',"wangwu" };
	//把格式化的数据转换成字符串
	char arr[40] = { 0 };
	sprintf(arr, "%d %c %s", s.n, s.c, s.name);
	printf("字符串形式: %s\n", arr);

	//字符串形式的数据转换成格式化的数据
	struct S tmp = { 0 };
	sscanf(arr, "%d %c %s", &(tmp.n), &(tmp.c), tmp.name);
	printf("格式化的数据:%d %c %s", tmp.n, tmp.c, tmp.name);
	return 0;
}

 

 fwrite

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

1 打开文件

2 写文件

3 关闭文件

文件操作(详解)_第13张图片

#include
struct S
{
	int n;
	char c;
	char name[20];
};

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写文件
	struct S s = { 100,'A',"zhangsan" };
	fwrite(&s, sizeof(struct S), 1, pf);

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

文件操作(详解)_第14张图片 

文本中存入的是二进制的信息 

fread

 

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

1 打开文件

2 读文件

3 关闭文件

文件操作(详解)_第15张图片

文本中已存在用fwrite写入的二进制信息

#include
struct S
{
	int n;
	char c;
	char name[20];
};

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	//写文件
	struct S s = {0};
	fread(&s, sizeof(struct S), 1, pf);
	printf("%d %c %s", s.n, s.c, s.name);

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

 

 

文件的随机读写 

 fseek

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

 根据文件指针的位置和偏移量来定位文件指针

offset:偏移量

origin:三种值

SEEK_SET 文件的起始位置
SEEK_CUR 文件指针的当前位置
SEEK_END 文件末尾

假设文件中原本就有: abcdef

文件操作(详解)_第16张图片

先读取文件中的a,b,c三个字符

文件操作(详解)_第17张图片 

原本再用fgetc读取一次,读取到的是d,但是我们现在想要读取到a,打印a:

从文件指针的当前位置(d的位置)开始,向前偏移3(-3)就可以抵达a的位置

#include
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);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

	ch = fgetc(pf);
	printf("%c\n", ch);//c

	//本来下一个会读取d,打印d
	//但是我现在就要打印b,使用fseek
	fseek(pf, -3, SEEK_CUR);
    //fseek(pf, 0, SEEK_SET); 这样也可
    //fseek(pf, -6, SEEK_END); 这样也可
	ch = fgetc(pf);
	printf("%c\n", ch);
	

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

文件操作(详解)_第18张图片

文件指针在d的位置,但是我们想要打印f:向后偏移2

#include
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);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

	ch = fgetc(pf);
	printf("%c\n", ch);//c

	//本来下一个会读取d,打印d
	//但是我现在就要打印b,使用fseek

	fseek(pf, 2, SEEK_CUR); 这样可以 向后偏移2
	//fseek(pf,5,SEEK_SET); 这样可以 向后偏移5
	//fseek(pf, -1, SEEK_END);这样可以 向前偏移1
	ch = fgetc(pf);
	printf("%c\n", ch);
	

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

 文件操作(详解)_第19张图片

 ftell

long int ftell ( FILE * stream );

 返回文件指针相对于起始位置的偏移量

 

#include
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);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

	ch = fgetc(pf);
	printf("%c\n", ch);//c

	//现在文件指针在d的位置,相对于起始位置a,偏移量为3
	printf("偏移量:%d\n", ftell(pf));


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

 文件操作(详解)_第20张图片

 

rewind 

void rewind ( FILE * stream );

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

#include
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);//a

	ch = fgetc(pf);
	printf("%c\n", ch);//b

	ch = fgetc(pf);
	printf("%c\n", ch);//c

	//现在文件指针在d的位置,相对于起始位置a,偏移量为3
	rewind(pf);
	//让文件指针回到起始位置,则其相对于起始位置的偏移量为0
	printf("偏移量:%d\n", ftell(pf));


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

文件操作(详解)_第21张图片

 文本文件和二进制文件

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

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

文本文件:以ASCII字符的形式存储的文件

               (外存上以ASCII码的形式存储,则需要在存储前转换)

数据在内存中的存储:

字符:一律以ASCII形式存储

数值型数据:既可以用ASCII形式存储,也可以使用二进制形式存储
比如:

整数10000,以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节)

                     以二进制形式输出,则在磁盘上只占4个字节(10000是整型,占4个字节)

文件操作(详解)_第22张图片

文件读取结束的判定  

feof:当文件已经读取结束后,判断读取失败的原因是否是:遇到文件末尾结束

检查是否设置了与关联的文件结束指示符,如果设置了,则返回不同于零的值 

int feof ( FILE * stream );

ferror:检查是否设置了与关联的错误指示器,如果设置了,则返回与零不同的值

int ferror ( FILE * stream );

文本文件读取结束的判定: 

fgetc 判断是否为 EOF 

fgetc返回值分析:读取失败返回EOF

1 遇到文件末尾,返回EOF,同时设置一个状态,表明遇到文件末尾了,使用feof来检测这个状态

2 遇到错误,返回EOF,同时设置一个状态,表明遇到错误了,使用ferror来检测这个状态


文件中已有abcdef

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

	//读文件-abcdef
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ", ch);
	}

	printf("\n");

	if (feof(pf))
	{
		printf("遇到文件末尾\n");
	}
	else if (ferror(pf))
	{
		printf("遇到错误\n");
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文件操作(详解)_第23张图片 

 

fgets 判断返回值是否为 NULL

 fgets返回值分析:读取失败返回NULL

1 遇到文件末尾,返回NULL,同时设置一个状态,表示遇到了文件末尾,使用feof来检测这个状态

2 遇到了错误,返回NULL,同时设置一个状态,表示遇到了错误,使用ferror来检测这个状态

文件操作(详解)_第24张图片 

 

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

	//读文件-abcdef
	char arr[20] = { 0 };
	while (fgets(arr,7,pf))
	{
		printf("%s", arr);
	}
	printf("\n");

	if (feof(pf))
	{
		printf("遇到文件末尾\n");
	}
	else if (ferror(pf))
	{
		printf("遇到错误\n");
	}
	
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 文件操作(详解)_第25张图片

 

二进制文件读取结束的判定:


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

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


文件中已经存在二进制形式的100 

文件操作(详解)_第26张图片

#include
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int n = 0;
	int ret = fread(&n, sizeof(int), 1, pf);
	if (ret == 1)
	{
		printf("成功读取\n");
		printf("%d ", n);
	}
	else
	{
		if (feof(pf))
		{
			printf("遇到文件末尾\n");
		}
		else if (ferror(pf))
		{
			printf("遇到错误\n");
		}
	}

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

 

 

 文件缓冲区

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

 因为有缓冲区的存在,C语言在操作文件的时候,需要刷新缓冲区(将数据推送到硬盘)或者在文件操作结束的时候关闭文件(将数据推送到硬盘)

因为存在缓冲区,所以我们在写完代码执行程序时,可能会遇见这种情况:程序执行起来不能立马看到结果,而是要等待一会才能看到结果

以上就是关于文件操作的全部内容啦

你可能感兴趣的:(c语言知识,c语言)