C语言文件操作

目录

[1. 为什么使⽤⽂件?](#1. 为什么使⽤⽂件?)

[2. 什么是⽂件?](#2. 什么是⽂件?)

2.1、数据文件

2.2、程序文件

[3. ⼆进制⽂件和⽂本⽂件?](#3. ⼆进制⽂件和⽂本⽂件?)

[4. ⽂件的打开和关闭](#4. ⽂件的打开和关闭)

4.1、流和标注流

4.1.1流

4.1.2标准流

4.2、文件指针

[4.3 ⽂件的打开和关闭](#4.3 ⽂件的打开和关闭)

5.⽂件的顺序读写

5.1、fgetc函数

5.2、fputc函数

5.3、fputs函数

5.4、fgets函数

5.5、fprintf函数

5.6、fscanf函数

5.7、fwrite函数

5.8、fread函数

[5.9、 sprintf函数](#5.9、 sprintf函数)

5.10、sscanf函数

[6. ⽂件的随机读写](#6. ⽂件的随机读写)

6.1、fseek函数

6.1.1、SEEK_SET文件开头

6.1.2、SEEK_CUR文件指针的当前位置

[6.1.3、SEEK_END文件结束 *](#6.1.3、SEEK_END文件结束 *)

6.2、ftell函数

[6.3 rewind函数](#6.3 rewind函数)

[7. ⽂件读取结束的判定](#7. ⽂件读取结束的判定)

7.1、字符串文本例子

7.2、二进制文本例子

8.、⽂件缓冲区


1. 为什么使⽤⽂件?

我们所写的数据是保存在内存中的,并不是直接在磁盘保存,通过文件可以保存我们在内存中所需要的内容,没用文件的话,我们就无法保存文件。也就是说我们要将数永久保存,就可以使用文件。

2. 什么是⽂件?

文件分为两种:1、数据文件。2、程序文件

2.1、数据文件

数据文件是我们所需要读取的内容,在一个程序中,我们会读取里面的数据,读取出来的内容是数据文件

2.2、程序文件

比如:可执行文件(.exe),源程序文件(.c),目标文件(.obj)

有这些后缀的都是程序文件

3. ⼆进制⽂件和⽂本⽂件?

文本文件:把内容用字符来保存到文件当中,使用ascll字符的形式进行存储的,计算机保存数据的时候要进行转译

二进制文件:内容直接用二进制形式保存到文件当中,计算机可以认识并且直接读取的,不利于人类看

例题:目前大家无需知道代码是什么意思,这只是演示把二进制文件打开方式跟我们看见的内容是什么样子的。

int main()
{
	int a = 1000;
	FILE* pf = fopen("data.txt", "wb");//打开data.txt,如果目录没用就新建一个
	fwrite(&a, 4, 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

4. ⽂件的打开和关闭

4.1、流和标注流

4.1.1流

程序需要使用各种的外部设备,也需要输出给外部设备,在与不同的设备进行互相传输的时候,为了方便程序员对各种设备进行调试,我们抽象出流这个概念,我们可以把流想象处流淌着字符的河

C程序针对文件,画面,键盘等的数据输入输出都是通过流来进行操作的

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

4.1.2标准流

标准流是我们C语言启动时候默认打开的

stdin-标准输入流,绝大情况从键盘输入,scanf函数就是从标准输入流进行读取数据的

stdout-标准输出流,绝大情况输出至显示器上,printf函数就是讲信息输出到标准输出流上

stderr-标准错误流,绝大情况输出至显示器上

三个流的类型是FILE*,通常指文件指针,通过FILE*来维护流的各种操作

4.2、文件指针

FILE*用与维护各种流,我们需要使用哪种流,通过创建FILE*指针我们就可以实现我们所要的效果,是输出屏幕还是从各种外部设备输入,还是打开关闭文件,写入文件等等操作

在使用每个文件的时候,就会创建一个文件信息区,用于存放文件的相关内容,这些内容都会保存到一块结构体中FILE*

struct _iobuf {
	char* _ptr;
	int _cnt;
	char* _base;
	int _flag;
	int _file;
	int _charbuf;
	int _bufsiz;
	char* _tmpfname;
};
typedef struct _iobuf FILE;//把结构体重命名为FILE

不同编译器的文件信息区大小不一样,我们可以通过FILE来维护这个结构的变量

比如我们创建一个FILE*指针

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

pf是指向FILE类型数据的变量,可以使pf指向某个文件的文件信息区,从而让我们可以使用这个文件,也就是说FILE* 可以帮我我们找到我们需要使用的文件

4.3 ⽂件的打开和关闭

fopen是一个打开文件的函数
fclose是一个关闭文件的函数

fopen
FILE * fopen ( const char * filename, const char * mode );
fclose
int fclose ( FILE * stream );

mode的意思是我们是读还是写,怎么读,怎么写。

|------------|-------------------|-----------|
| 文件使用方式 | 含有 | 如果指定文件不存在 |
| "r" (只读) | 为了输入,打开一个存在的文本文件 | 出错 |
| "w" (只写) | 为了输出,打开一个文本文件 | 创建新文件 |
| "a" (追加) | 向文本文件的末尾添加数据 | 创建新文件 |
| "rb" (只读) | 输入文件,打开一个二进制文件 | 出错 |
| "wb"(只写) | 输出文件,输出一个二进制文件 | 创建新文件 |
| "ab" (追加) | 向一个二进制文件的末尾添加数据 | 创建新文件 |
| "r+" (读写) | 为了读和写,打开一个文件 | 错误 |
| "w+"(读写) | 为了读和写,新建一个文件 | 创建新文件 |
| "a+" (读写) | 打开一个文件,在末尾进行读写 | 创建新文件 |
| "rb+"(读写) | 读写一个二进制文件 | 错误 |
| "wb+" (读写) | 创建一个读写的二进制文件 | 创建新文件 |
| "ab+" (读写) | 打开一个二进制文件,在末尾进行读写 | 创建新文件 |

例子:

int main()
{
	FILE* pf = fopen("data.txt", "w");   //通过指针FILE* pf找到并打开data.txt文件
	if (pf==NULL)  //判断pf是否指向NULL
	{
		perror("fopen");//perror可以把错误的信息打印到屏幕上
		return 1;  //在主函数中返回1表示不正常,返回0才正常
	}
	fclose(pf);//通过指针FILE* pf找到data.txt文件并关闭
	pf = NULL;//已经关闭了data.txt文件,创建的pf指针不能为空,否则就变为一个野指针
	return 0;
}

5.⽂件的顺序读写

输入流:磁盘中的文件数据源输入到内存里

输出流:内存中的内容存入硬盘中的文件里

以下函数默认读取与写入的内存地址是跟" .c"文件同一个目录下.

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

5.1、fgetc函数

int fgetc ( FILE * stream );

这个函数是用于打开文件并输出第一个字符,每输出一个字符光标会往前移动1

我们先创建一个文本文件.txt

与.c文件同一个目录创建一个data.txt文件,data.txt文件中写入abcdef这几个字符。

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf==NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	int ch = 0;      //因为文件存储的是ASCII码值,需要使用int类型的变量来接收ASCII码值
	ch = fgetc(pf);  //读取文件中一个字符,转换为ASCII码值,
	printf("%c\n", ch);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
} 

输出:

那么如何打印出整个data.txt文件中的字符。

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

	//读取文件
	while ((ch = fgetc(pf)) != EOF)  //EOF的意思是当文件读取都末尾的空值时,会停止读取
	{
		printf("%c ", ch);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

5.2、fputc函数

int fputc ( int character, FILE * stream );

这个函数可以把存储在内存上的内容写入到我们指定的文件当中

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//输出字符
	fputc('a', pf);//把字符a写入data.txt文件中
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

成功写入data.txt文件

5.3、fputs函数

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

这个函数可以把字符串给保存到文本文件中

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//输出字符
	fputs("hello world", pf);//把字符串写入data.txt文件中
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

5.4、fgets函数

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

这个函数是读取文本文件中字符,str表示字符串大小,num表示读取多少字符

//事先创建了一个data.txt文件,里面存入hello world字符串
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//输入字符
	char str[12];        
	fgets(str, 12, pf);    //把data.txt文件中的内容前12个字符存入str中
		printf("%s", str);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

5.5、fprintf函数

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

这个函数的使用方法跟printf有相同之处,这个函数输出文本文件的里面的内容。

struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { "lihua",18,97.3f };//结构体s中存放的内容
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//输出
	fprintf(pf, "%s   %d     %f", s.name, s.age, s.score);//把结构体中的内容存放到pf中
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

成功保存

5.6、fscanf函数

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

这个函数可以读取文件中的内容,我们就使用上一个函数中存放在文本文件中的内容做示范

struct S
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct S s = { 0 };//结构体s中存放的内容
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//输入
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));  //字符串无需取地址,因为会自动取首地址,把data.txt文件中的内容取出来放入结构体s中
	printf("%s  %d  %f", s.name, s.age, s.score);//打印存放在结构上s中的内容
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

浮点数无法做到精确取值,所有有余数是很正常的事情。

5.7、fwrite函数

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

这个函数把内容输出到文本文件中,使用二进制保存

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "wb");//wb是二进制输出
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//二进制输出
	int arr[5] = { 1,2,3,4,5 };
	fwrite(arr, sizeof(int), 5, pf);//把arr大小的文件,以int类型大小,保存5个字符/数字到pf中
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

可以看见,我们保存成功

5.8、fread函数

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

这个函数可以读取二进制文本文件,我们读取上一个函数创建的文本文件内容

int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "rb");//wb是二进制输出
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//二进制输出
	int arr[5] ;   //创建一个字符串
	fread(arr, sizeof(int), 5, pf);//读取pf指向的二进制文件,保存5个以int大小到arr数组中
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

5.9、 sprintf函数

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

这个函数是将数组,字符串,浮点型,变为一个字符串进行输出

struct S
{

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

int main()
{
	char str[100];  //用于存储结构体s中的内容
	struct S s = { "xiaoming" ,18,95.5 };
	sprintf(str, "%s %d %f", s.name, s.age, s.score);  //把结构体s中的内容放入str中
	printf("%s ", str);
	return 0;
}

输出:

5.10、sscanf函数

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

这个函数是将变量取出来,放入到另一个空变量中(类型与大小要相同)

struct S
{

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

int main()
{
	char str[100];
	struct S s = { "xiaoming" ,18,95.5 };
	struct S tmp = { 0 };
	sprintf(str, "%s %d %f", s.name, s.age, s.score);  //把结构体s中的内容放入str中
	sscanf(str, "%s %d %f", tmp.name, &(tmp.age),&(tmp.score));   //把str中的内容放入tmp结构体中
	printf("%s %d %f", tmp.name, tmp.age, tmp.score);    //打印结构体tmp中从值
	return 0;
}

输出:

6. ⽂件的随机读写

上面讲了顺序读写,我们这一节来讲随机读写

6.1、fseek函数

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

这个函数是使用光标来定位的,并不是从初始位置开始

offset是我们需要移动几个光标,origin移动光标的时候想从什么地方开始移动,上图中已经写了,SEEK_SET光标在文件开头,SEEK_CUR文件指针的当前位置,SEEK_END光标在文件的末尾

6.1.1、SEEK_SET文件开头

//在data.txt中存入abcdef内容
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = 0;
	fseek(pf, 4, SEEK_SET);  //从起始位置开始便便宜4个光标
	ch = fgetc(pf);
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

便宜四个位置就是e

6.1.2、SEEK_CUR文件指针的当前位置

//在data.txt中存入abcdef内容
int main()
{
	int ch = 0;
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	ch = fgetc(pf);           //fgetc运行完以后,光标会自动偏移一个位置
	fseek(pf, 4, SEEK_CUR);  //从当前位置开始偏移4个光标
	ch = fgetc(pf);
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

6.1.3、SEEK_END文件结束 *

//在data.txt中存入abcdef内容
int main()
{
	int ch = 0;
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(pf, -2, SEEK_END);     //光标从结尾开始运行,移动-2个位置
	ch = fgetc(pf);
	printf("%c", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

6.2、ftell函数

long int ftell ( FILE * stream );

这个函数是计算光标距离起始位置有多长

//在data.txt中存入abcdef内容
int main()
{
	int ch = 0;
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	fseek(pf, 4, SEEK_SET);     //从起始位置开始偏移4个光标
	ch = fgetc(pf);
	printf("%c\n", ch);
	printf("%d\n", ftell(pf));  //计算当前光标到起始位置的距离     
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

6.3 rewind函数

这个函数可以将光标恢复到起始位置

//在data.txt中存入abcdef内容
int main()
{
	int ch = 0;
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	fseek(pf, 4, SEEK_CUR);     //从当前位置开始偏移4个光标
	ch = fgetc(pf);
	printf("%c\n", ch);
	printf("%d\n", ftell(pf));
	rewind(pf);                 //光标恢复到起始位置
	printf("%d\n", ftell(pf));
	fclose(pf);
	pf = NULL;
	return 0;
}

输出:

7. ⽂件读取结束的判定

1、文本文件读取

我们可以使用feof这个函数来检查文件是否被正常读取

fgetc是否为EOF

fgets是否为NULL

如果是的话,那么文件读取没用文件,否则就有问题

2、二进制文件读取

二进制读取结束判断,判断返回值是否小于实际要读的数

7.1、字符串文本例子

//在data.txt中存入abcdef内容
int main()
{
	int ch = 0;
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	while ((ch = fgetc(pf)) != EOF) // 标准C I/O读取⽂件循环
	{
		putchar(ch );//putchar也是打印的意思,这个是打印变量
	}
	//判断是什么原因结束的
	if (ferror(pf))
		puts("\nI/O error when reading");   //puts是打印的意思
	else if (feof(pf))
		puts("\nEnd of file reached successfully");
	fclose(pf);
	pf = NULL;
	return 0;
}

7.2、二进制文本例子

enum { SIZE = 5 };
int main(void)
{
	double a[SIZE] = { 1.,2.,3.,4.,5. };
	FILE* fp = fopen("test.bin", "wb"); // 必须⽤⼆进制模式
	fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
	fclose(fp);
	double b[SIZE];
	fp = fopen("test.bin", "rb");
	size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
	if (ret_code == SIZE) {
		puts("Array read successfully, contents: ");
		for (int n = 0; n < SIZE; ++n)
			printf("%f ", b[n]);
		putchar('\n');
	}
	else { // error handling
		if (feof(fp))
			printf("Error reading test.bin: unexpected end of file\n");
		else if (ferror(fp)) {
			perror("Error reading test.bin");
		}
	}
	fclose(fp);
}

8.、⽂件缓冲区

文件缓冲区的意思我们可以理解为,在内存中存放的数据,存放到一定数量的时候,再写入硬盘里,如果没有文件缓冲区那么就需要时时刻刻把文件数据放入到硬盘里,这样太浪费算力了。

相关推荐
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
小白学大数据3 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
冰芒猓4 小时前
SpringMVC数据校验、数据格式化处理、国际化设置
开发语言·maven
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
杜杜的man4 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
java小吕布5 小时前
Java中Properties的使用详解
java·开发语言·后端
versatile_zpc5 小时前
C++初阶:类和对象(上)
开发语言·c++
尘浮生5 小时前
Java项目实战II基于微信小程序的移动学习平台的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·学习·微信小程序·小程序
ChoSeitaku6 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表