C语言文件操作详解

Posted 流浪孤儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言文件操作详解相关的知识,希望对你有一定的参考价值。

目录

文件的概念

 文件的分类

 数据文件的分类

文本文件

 二进制文件

 文件的操作

 文件的打开与关闭

 打开文件

 关闭文件

 文件指针

文件名 

文件缓冲区

fopen函数的第二个参数

 文件的顺序读写

 文件的顺序读写函数 

 fputc与fgetc函数

 fputs与fgets函数

 fscanf与fprintf函数

 sscanf与ssprintf

 fwrite与fread

文件的随机读写 

fseek函数 

ftell函数

remind函数 

求一个文件的大小

文件判定函数

 ferror函数

 feof函数


文件的概念

 文件的分类

文件是一组相关数据的集合。在C程序设计中,按文件的内容可以分为两类:

一类是程序文件,它是程序的源代码,比如图中test..c就是程序文件,当然windows下通过编译器产生的目标文件(.obj)、程序文件(.exe)都是程序文件

另一类是数据文件,它是程序运行时需要的原始数据及输出的结果,比如如图中的data.txt

这两类文件都保存在磁盘上,随时可以使用。

程序文件的操作自然不需要讲解了,这里讲的是关于数据文件的操作,即如何在程序文件中通过相关的函数管理数据文件

 数据文件的分类

 按数据的存储形式,数据文件可以分为文本文件二进制文件两类。

文本文件

 上图中的data.txt就是文本文件

文本文件,也称为ASCII文件,是一种字符流文件。文件由一个个字符首尾相接而成,其中每个字符占1字节,存放的是字符的·ASCII码 。

例如,一个int型的整数-25612,它在内存中是以二进制形式存放的,占4个字节,而在文本文件中,它是按书写形式-25612存放的,每个字符占一个字节,总共要占6个字节。

在程序文件中若要将该数写入文本文件中,首先要将内存中4字节的二进制数转换成6个字符的ASCII码;若要将该数从文本文件读进内存即程序文件要从文本文件中读取该数据,首先要将这6个字符转换成4字节的二进制数,当然转换的过程操作系统会帮我们完成。

 二进制文件

二进制文件中的数据 是按其在内存中的存储形式存放的。

例如,一个double型的常数1.0,不管其书写形式如何,在内存中及二进制文件中均占8字节。

由于二进制文件在输入\\输出时,不必进行转换,故效率高,但二进制文件只能供机器阅读,无法人工阅读;而且由于不同的计算机系统对数据的二进制表示也各有差异,因此可移植性差。一般用二进制文件来保存数据处理的中间结果。

 

通过上图的程序文件生成二进制文件test.txt并向其写入数据"张三", 20, 99.2f

 

 可见二进制文件无法直接供人工阅读

 文件的操作

 文件的打开与关闭

文件在读写之前需要我们自己先打开文件,在使用结束之后需要我们自己关闭文件。 

 打开文件

用标准库函数fopen,它通知编译器系统三个信息:需要打开的文件名;使用文件的方式(只读、只写还是既可以读又可以写);返回一个文件指针给编译器。

函数原型:FILE *fopen(const char *path, const char *mode);

返回值:打开文件失败返回NULL,成功返回一个文件指针

 关闭文件

用标准库函数fclose,它的功能是把数据真正写入磁盘(数据可能还在缓冲区中),切断文件指针与文件之间的联系,释放结构体变量。如果不关闭多半会丢失数据而且同时打开的文件数目是有限制的。(当程序正常运行结束时,系统也会自动关闭所有已打开的文件)

函数原型:int fclose(FILE *fp);

注意fclose函数不会使fp指针置为NULL,因此为了防止被误用我们需要手动的将fp=NULL;

相信刚才小伙伴会有三个疑问:文件指针是啥?为什么fopen的第一个参数是path(路径),不应该是文件名吗?缓冲区是啥?请客官们细细往下看。

 文件指针

 操作系统声明了一个结构体

例如, VS2008编译环境提供的 stdio.h 头文件中有以下的文件类型声明:
struct _iobuf
    char *_ptr;
    int    _cnt;
    char *_base;
    int    _flag;
    int    _file;
    int    _charbuf;
    int    _bufsiz;
    char *_tmpfname;
;
typedef struct _iobuf FILE;  

 当我们使用fopen函数打开一个文件时,操作系统帮我们创建了FILE类型的结构体变量,如何通过fopen将该结构体变量的地址进行返回,因此我们需要定义一个FILE类型的指针接收该结构体变量的地址。

该结构体变量用来存放文件的相关信息(如文件的名字,文件状态及 文件当前的位置等)

FILE类型的指针便是文件指针 ,通过该文件指针来管理该文件

文件名 

 一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识包含3部分:文件路径 + 文件名主干 + 文件后缀
为了方便起见,文件标识常被称为文件名

 

 现在明白fopen的第一个参数为啥是path了吧

文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,
所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

从内存向磁盘输出数据会先送到内存中的输出缓冲区,装满输出缓冲区后才一起送到磁盘 上(如果提取关闭文件或者程序结束即使该缓冲区未满也可将数据送到磁盘上)。
从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存的输入缓冲区,通过函数再从输入缓冲区逐 个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的
 

fopen函数的第二个参数

其第二个参数为文件的使用方式,有如下几种

文件使用方式

含义

指定文件的状态

“r”(只读)

为了输入数据,打开一个已经存在的文本文件

文件不存在或不允许读出错(即返回NULL)

“w”(只写)

为了输出数据,打开一个文本文件(原内容清空)

文件不存在则建立一个新的文件,若存在,则原内容清空,从起始位置写

“a”(追加)

向文本文件尾添加数据

文件不存在建立一个新的文件,若存在则在原有内容之后写入数据

“rb”(只读)

为了输入数据,打开一个二进制文件

文件不存在或不允许读出错(即返回NULL)

“wb”(只写)

为了输出数据,打开一个二进制文件

文件不存在则建立一个新的文件,若存在,则原内容清空,从指定位置写

“ab”(追加)

向一个二进制文件尾添加数据

文件不存在建立一个新的文件,若存在则在原有内容之后写入数据

“r+”(读写)

为了读和写,打开一个文本文件

文件不存在出错(返回NULL),存在则读与写总是从文件的起始位置开始(其文件的起始位置可以被位置函数给设置),写新的数据时,只覆盖数据所占的空间,其后的老数据并不丢失

“w+”(读写)

为了读和写,打开一个文本文件(原内容清空)

不存在则建立一个新的文件(其文件的起始位置可以被位置函数给设置)

“a+”(读写)

打开一个文件,在文件尾进行读写

文件不存在建立一个新的文件,若存在则在原有内容之后写入数据,在文件尾部添加新的数据之后可以从头开始读(要移动文件位置指针)其文件的起始位置可以被位置函数给设置

“rb+”(读写)

为了读和写打开一个二进制文件

文件不存在出错(返回NULL),存在则读与写时其文件的起始位置可以被位置函数给设置,写新的数据时,只覆盖数据所占的空间,其后的老数据并不丢失

“wb+”(读写)

为了读和写,打开一个二进制文件(原内容清空)

不存在则建立一个新的文件,读与写时其文件的起始位置可以被位置函数给设置

“ab+”(读写)

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

文件不存在建立一个新的文件,若存在则在原有内容之后写入数据,在文件尾部添加新的数据之后,读的文件的起始位置可以被位置函数给设置

 文件的顺序读写

文件的顺序读写函数 

功能

函数名

适用于

字符输入函数

fgetc

所有输入流

字符输出函数

fputc

所有输出流

文本行输入函数

fgets

所有输入流

文本行输出函数

fputs

所有输出流

格式化输入函数

fscanf

所有输入流

格式化输出函数

fprintf

所有输出流

格式化输入函数

sscanf

字符串

格式化输出函数

sprintf

字符串

二进制输入

fread

文件

二进制输出

fwrite

文件

 fputc与fgetc函数

 函数原型:int fputc(int c, FILE *stream);

功能是将一个字符输出到指定的位置

返回值:输出成功返回用户传入的字符,失败返回EOF(是个宏、文件结束的标志,值为-1)

 fputc函数的第一个参数是待输出的字符,第二个参数该字符输出的位置即我们定义的文件指针,。

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "w");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	char arr[] =  "hello world!!!" ;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//写文件
	for (int i = 0; i < sz; i++) 
		if (fputc(arr[i], fp) < 0) 
			perror("write failed");
			return 2;
		
	

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

函数原型:int fgetc(FILE *stream);

功能:从指定位置读取一个字符

返回值:读取成功返回该字符,若把文件读完了或者发生错误返回EOF

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	char arr[20] =  0 ;
	//写文件
	for (int i = 0; i < 20; i++) 
		if ((arr[i]=fgetc(fp))<0) 
			printf("read end\\n");
			break;
		
	

	for (int i = 0; i < 20; i++) 
		printf("%c", arr[i]);
	
	printf("\\n");
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;

fputs与fgets函数

 函数原型:int fputs(const char *s, FILE *stream);

参数:第一个参数是待输出的字符串,第二个参数该字符串输出的位置 

功能:将一个字符串输出到指定的位置

返回值:成功返回非负数,失败返回EOF

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "w");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	if (fputs("hello world!!!", fp) < 0) 
		perror("write failed");
		return 2;
	

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

函数原型:char *fgets(char *s, int size, FILE *stream);

参数:第一个参数是你所读取到的数据的储存位置

           第二个参数是你要读取的字符个数

           第三个参数是你要读取的文件的文件指针,

功能:从指定位置读取指定字符个数的数据储存到指定位置。

返回值:成功则返回用于储存数据的位置的地址,发生错误或是读取到了文件末尾,则返回一个空指针(NULL)。

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	char arr[20] =  0 ;
	//写文件
	if (fgets(arr, 20, fp) == NULL) 
		perror("read failed");
		return 2;
	

	for (int i = 0; i < 20; i++) 
		printf("%c", arr[i]);
	
	printf("\\n");
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
fgets函数读取到第n - 1个字符时都没有遇到换行符(’\\n’),则返回读取到的n - 1个字符,并在末尾加上一个‘\\0’一同返回( 实际读到有效字符n-1个
在fgets函数读取到第n个字符之前,若读取到换行符(’\\n’),则停止读取,读取带回的字符包含换行符。

 fscanf与fprintf函数

函数原型:int fprintf(FILE *stream, const char *format, ...);

对比int printf(const char *format, ...);发现fprintf比printf多了一个FILE*类型的参数而已

printf将内容输出到显示器上,而fprintf将内容输出到stream中 ,两者没有其它区别

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "w");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	fprintf(fp, "%s\\n", "HELLO WORLD");
	fprintf(fp, "%s\\n", "HELLO EVERYONE");

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

 

函数原型:int fscanf(FILE *stream, const char *format, ...);

对比int scanf(const char *format, ...);发现fscanf比scanf多了一个FILE*类型的参数而已 

scanf是从键盘获取数据,fscanf是从stream中获取数据,两者没有其它区别

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	char arr[30] =  0 ;
	fscanf(fp, "%s", arr);

	for (int i = 0; i < 30; i++) 
		printf("%c", arr[i]);
	
	printf("\\n");
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;

 sscanf与ssprintf

这两者的头文件为<string.h> 

 函数原型:int sscanf(const char *str, const char *format, ...);

       功能:从str字符串中获取数据进行输出

 函数原型 :int sprintf(char *str, const char *format, ...);

       功能:将数据输出到str中

#include<stdio.h>
#include<string.h>

int main() 

	char arr[20] =  0 ;
	char arr1[20] =  0 ;
	sprintf(arr, "%s", "hello world!!!");
	sscanf(arr, "%s", arr1);
	printf("%s\\n", arr);
	printf("%s\\n", arr1);
	return 0;

 fwrite与fread

函数原型:size_t fwrite(const void *ptr,                                                                                                                            size_t size,                                                                                                                                size_t nmemb,
                                    FILE *stream);

参数:第一个参数ptr是指向某个数据块的指针(该数据块用来提供数据),第二个参数是这个数据块的字节数,第三个参数是数据块的个数,第四个参数是数据输出的目标位置

功能:以二进制形式向文件存入数据

返回值:返回实际写入的完整项数,如果发生错误,该数可能小于nmemb

#include<stdio.h>

struct Stu

	char name[20];
	int age;
	float score;
;

int main()

	struct Stu s =  "张三", 20, 99.2f ;
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	
		perror("open file for writting");
		return 1;
	
	//写文件
	fwrite(&s, sizeof(struct Stu), 1, pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;

 函数模板:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数:第一个参数ptr指向的某个数据块(该数据块是用来接收数据的),第二个参数是这个数据块的字节数,第三个参数是数据块的个数,第四个参数是用来提供数据的

功能:向二进制文本中读取数据

返回值:成功读取的元素总数。如果这个数字与nmemb参数不同,则可能是在读取时发生了读取错误或到达了文件结束

#include<stdio.h>

struct Stu

	char name[20];
	int age;
	float score;
;

int main()

	struct Stu s =  0 ;
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	
		perror("open file for reading");
		return 1;
	
	//写文件
	fread(&s, sizeof(struct Stu), 1, pf);
	printf("%s\\n%d\\n%f\\n", s.name, s.age, s.score);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;

文件的随机读写 

 文件的顺序读写函数,是对文件中的数据进行顺序输入或输出操作的函数。但是若只能规规矩矩地,从前到尾对文件进行输入输出操作,那么就比较笨拙,为了能更加灵活地对文件进行输入输出操作,出现了可以对文件进行随机读写的文件操作函数。

fseek函数 

 函数原型:int fseek ( FILE * stream, long int offset, int origin );

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

参数:第一个参数既文件指针;第二个参数是相对于“某个位置”的偏移量,单位为字节;第三个参数是“某个位置”

 使用这个函数前需要理解一下在文件中进行顺序读写的过程

#include<stdio.h>

int main() 
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL) 
		perror("open file for writting");
		return 1;
	
	char arr[30] =  0 ;
	for (int i = 0; i < 7; i++) 
		arr[i] = fgetc(fp);
	
	
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;

 

 每一次进行fgetc系统都会记录上次读取的位置,标记位置的我们叫做文件位置指针

文件的随机读写操作就是通过移动文件位置指针来实现的

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

fseek中第三个参数可以用系统提供的宏来指定 

origin:
        SEEK_CUR   即Current position of file pointer当前位置
        SEEK_END   即End of file文件末尾的位置
        SEEK_SET   即Beginning of file 文件开始的位置

#include<stdio.h>

int main()
	FILE* pf = fopen("test.txt", "w+");
	if (pf == NULL)
	
		return 1;
	
	fputs("ABCDEFG", pf);
	fseek(pf, 4, SEEK_SET);//将文件位置指针在文件开始位置处向右移动4个字节
	char a = fgetc(pf);//a为E,调用fgetc函数后文件位置指针在F处
	fseek(pf, -4, SEEK_CUR);//将文件位置指针从当前位置向左移动4个字节
	char c = fgetc(pf);//c为B,调用fgetc函数后文件位置指针在C处

	int ret = ftell(pf);//此时文件位置指针相对于文件开始位置的偏移量为2
	printf("a=%c\\n", a);
	printf("b=%c\\n", c);
	printf("ret=%d\\n", ret);
	fclose(pf);
	pf = NULL;
	return 0;

执行结果:

 ftell函数

 函数模型:long ftell(FILE *stream);

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

上述代码已经使用了ftell函数,这里不再进行讲解

remind函数 

函数模型: void rewind(FILE *stream);

功能:将文件位置指针移动到文件起始位置

#include<stdio.h>

int main()
	FILE* pf = fopen("test.txt", "w+");
	if (pf == NULL)
	
		return 1;
	
	fputs("ABCDEFG", pf);
	fseek(pf, 4, SEEK_SET);//将文件位置指针在文件开始位置处向右移动4个字节	
	printf("fseek:%d\\n", ftell(pf));
	rewind(pf);
	printf("rewind:%d\\n", ftell(pf));

	fclose(pf);
	pf = NULL;
	return 0;

 执行结果:

 求一个文件的大小

#include<stdio.h>

int main()
	FILE* pf = fopen("test.txt", "w+");
	if (pf == NULL)
	
		return 1;
	
	fputs("ABCDEFG", pf);
	fseek(pf, 0, SEEK_END);//将文件位置指针移动到文件末尾(文件末尾在G之后)
	printf("文件大小:%d\\n", ftell(pf));

	fclose(pf);
	pf = NULL;
	return 0;

执行结果:

 字符串“ABCDEFG”是八个字节的大小,可是为什么 是7呢?因为'\\0'是字符串的结束标志,操作系统将ABCDEFG写入文件后碰到了字符串的结束标志就停止写入了

如果想要将'\\0'放入文件中可以使用fputc函数

#include<stdio.h>

int main()
	FILE* pf = fopen("test.txt", "w+");
	if (pf == NULL)
	
		return 1;
	

	fputc('\\0', pf);
	fseek(pf, 0, SEEK_END);//将文件位置指针移动到文件末尾
	printf("文件大小:%d\\n", ftell(pf));

	fclose(pf);
	pf = NULL;
	return 0;

或者这样

#include<stdio.h>

int main()

	FILE *fp = fopen("text_7_1.txt", "w+");
	if (!fp)
	
		perror("fopen");
		return 1;
	
	char frr[5] = "ABCD";
	fwrite(frr, sizeof(char), 5, fp);//指定长度

	fclose(fp);
	fp = NULL;
	return 0;

这里对文件末尾要补充的是:文件末尾你可以理解为一个EOF(文件结尾标识符 EOF),一旦函数读取到了EOF就可以知道文件的有效数据读完了,EOF自然不属于有效数据之内。这里的文件大小指的是文件的有效数据的字节数,意思就是说即使一个空文件也是有大小的,比如记录了这个文件的创建日期,修改日期等待,不过这些数据不属于用户输入的“有效数据”。

 

文件判定函数

 ferror函数

 函数模板:int ferror(FILE *stream);

功能:判定文件读取过程中是否出错

返回值:没有出错返回0,出错返回非零值

 feof函数

函数模板:int feof(FILE *stream);

功能:判定文件读取操作结束时是否读取到文件结尾

返回值:文件读取操作结束时没有读取到文件结尾返回0,读取到文件结尾返回非零值

#include<stdio.h>

int main()

	FILE *fp = fopen("text_7_1.txt", "w+");
	if (!fp)
	
		perror("fopen");
		return 1;
	
	char frr[50] = "ABCD";
	char tmp[40] =  0 ;
	fwrite(frr, sizeof(char), 5, fp);
	rewind(fp);
	fgets(tmp, 20, fp);
	if (!ferror(fp))
	
		printf("存入成功\\n");
	
	if (feof(fp))	
	
		printf("到文件尾了\\n");
	
	fclose(fp);
	fp = NULL;
	return 0;

以上是关于C语言文件操作详解的主要内容,如果未能解决你的问题,请参考以下文章

C语言中程序的编译(预处理操作)+链接详解(详细介绍程序预编译过程)

C语言 -- 字符串详解

C语言之文件操作总结

用FastCopy怎么删除文件,请详解

C 语言文件操作 ( 使用 fread 和 fwrite 实现二进制文件的拷贝 | stat 统计文件大小 | feof 判定文件结尾 )

fread()函数如何判断是不是到文件末尾?