C C语言文件操作

前言

之前编写通讯录程序时,由于数据都是保存在内存上,每次关闭通讯录后,都需要重新再输入信息。有没有办法将数据持久化的保存在硬盘中呢?这就涉及到文件操作的知识。本文将介绍文件操作相关函数的使用,并对之前写过的通讯录程序进行修改。

文件分类

从文件功能的角度来分类:

1. 程序文件

比如在用vs写代码时创建的源文件(.c),目标文件(windows环境后缀为.obj),执行文件(.exe)

2. 数据文件

比如程序运行需要从中读取数据的文件,或者需要写入数据进去的文件。

此前我所写的所有程序都是以终端为数据文件,如printf是在有需要时将数据打印在屏幕上,scanf是将键盘上的输入数据读取进程序文件中。现在不过是将终端变化为数据文件,这样就好理解多了。

文件名

文件名是唯一的文件标识,有了文件名才能准确找到文件的位置并进行操作。
文件名包含三部分:文件路径+文件名主干+文件后缀

比如:D:\Study\新建文本文档.txt

文件指针

进行文件操作时,我们需要一个桥梁沟通程序和我们希望进行操作的数据文件。这个桥梁就是文件指针FILE*
每个被使用的文件都会在内存中开辟一段文件信息区域,用于存放文件的相关信息,如文件名,文件的状态,当前的位置等。而FILE是编译器提供的一个结构体类型,文件信息区的内容被保存在FLIE类型的变量里。

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

文件操作函数

1. 文件的打开和关闭

文件打开,使用函数fopen,关闭使用fclose。

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

当fopen失败时,会返回空指针。因此最好加上判空
其中,const char * mode是指输入对文件操作的不同指令字符,即“打开方式”。

打开方式汇总如下表:

比如我要打开文件,并读取文件中数据,则:

FILE* pf = fopen("test.txt","r");//用只读方式打开test.txt文件
FILE* pf = fopen("D:\\Study\test.txt","r");//当使用文件路径进行访问时,需要在盘符后多加一个'\'进行转义

2. 文件的顺序读写

关于文件操作,我们可以使用以下函数:

顾名思义,fgetc和fputc是向文件输出\从文件中读取字符的函数,c == char。当成功时,fgetc和fputc会返回其得到\写入字符的ASC码。并将文件的位置标识符后移一位。

同理,fgets和fputs中,s == string。

表中函数各功能与返回值均有所不同,在此不一一介绍。

2.1 两组输入/输出函数的对比

为了更深入的理解各个函数的功能,接下来对scanf/fscanf/sscanf;printf/fprintf/sprintf这两组函数的异同之处进行比较测试。

其中,scanf、printf和fscanf、fprintf的区别在于,后两者引入了
以scanf和fscanf为例,查看函数定义可知:

scanf函数是从标准输入流——即键盘上读取数据的。
fscanf(stdin,"%d",&a);scanf("%d",&a);的效果相同,stdin就是标准输入流的意思。
如此一来fscanf的功能就很明确了——从一个输入流中读取数据到目标变量中。
用例如下:
先创建一个文件test.txt。将其内容编辑为15并保存退出。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

void test()
{
	int a = 0;
	//打开文件
	FILE* pf = fopen("D:\\test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	//使用
	fscanf(pf, "%d", &a);//将文件中信息读取进变量a中
	printf("%d", a);
	//关闭文件
	fclose(pf);
	pf = NULL;
}

int main() {
	test();
	return 0;
}

运行后我们就会惊喜的发现,屏幕上输出了15。

同理,fprintf的功能即为将a中的数据打印进目标文件中。用例如下:

先将test.txt中数据清除,再运行以下代码:

void test()
{
	int a = 15;
	//打开文件
	FILE* pf = fopen("D:\\test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}

	//使用
	fprintf(pf, "%d", a);

	//关闭
	fclose(pf);
	pf = NULL;
}
int main() {
	test();
	return 0;
}

运行后打开文件,发现文件内容已被修改为15。

而sscanf和sprintf两个函数与上述的区别则在于,这两个函数并非从流中读取\写入数据,而是从字符串中读取\写入格式化的数据。


用例如下:

struct s
{
	char name[10];
	int age;
	double score;
};
void test()
{
	char arr[20] = { 0 };
	struct s s1 = { "zhang",20,90.1 };
	//使用sprintf,将结构体内容转化为字符串并存入字符数组。
	sprintf(arr, "%s %d %lf", s1.name, s1.age, s1.score);
	//打印arr内容看是否成功存入
	printf("%s", arr);

	//使用sscanf,从字符串中读取格式化数据
	struct s s2 = { 0 };
	sscanf(arr, "%s %d %lf", s2.name, &(s2.age), &(s2.score));
	//打印s2看是否成功读取
	printf("%s %d %lf", s2.name, s2.age, s2.score);
}
int main() 
{
	test();
	return 0;
}

运行程序后得:
在这里插入图片描述
说明从字符串读取\写入的功能均测试成功。

3. 文件的随机读写

3.1 fseek()

此处介绍文件随机读写所用到的函数:fseek
在这里插入图片描述
查看函数定义可知,fseek是用来改变文件指针位置的函数。其第一个参数为文件指针,第二个参数为偏移量,第三个参数表示开始添加偏移 offset 的位置,可选择三个值:

  1. SEEK_SET 文件的开头
  2. SEEK_CUR 文件指针的当前位置
  3. SEEK_END 文件的末尾

当fseek()成功时,返回0,否则返回非零值。
比如,我们可以如此使用fseek:

//文件随机读写
void test()
{
	FILE* pf = fopen("D:\\test.txt", "r+");//注意此处需要使用读写,r+和 w+都可以。
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}

	
	fputs("Hello, Mike!", pf);
	fseek(pf, -5, SEEK_END);//从文件的末尾向前偏移5个字符
	fputs("Jill!", pf);
	fseek(pf, 0, SEEK_SET);
	//这里也可使用rewind(pf),将文件指针位置回到文件起始位。

	char arr[50] = { 0 };
	fread(arr, 1, 12, pf);//还有一个'\0'
	printf("%s\n", arr); 

	fclose(pf);
	pf = NULL;
}
int main() {
	test();
	return 0;
}

运行结果为:
在这里插入图片描述
测试成功。

3.2 ftell()

ftell()函数功能为:告诉我们指定流位置标识符当前位置是多少。

其用例如下:
void test()
{
	FILE* pf = fopen("D:\\test.txt", "r+");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}

	
	fputs("abcde", pf);
	fseek(pf, -3, SEEK_END);//从文件的末尾向前偏移3个字符
	long int pos = ftell(pf);
	printf("%ld", pos);

	fclose(pf);
	pf = NULL;
}
int main() {
	test();
	return 0;
}

运行结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/1dda1d1554b54595b6f7a0c96a9a0415.png

3.3 ferror()、feof() - 文件读取结束的判定

ferror()函数的功能,是通过其返回值判断,是否因文件读取时出错导致的文件读取结束。
feof()函数的功能,是通过其返回值来判断,是不是读取到文件末尾导致的结束。

ferror()的返回值:
在这里插入图片描述
feof()的返回值:
在这里插入图片描述
用例:

void test()
{
	//事先在文件中存入abcde
	FILE* pf = fopen("D:\\test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}

	int c = 0;
	while ((c = fgetc(pf)) != EOF)//通过判断fgetc的返回值是否为EOF,确定文件是否读取结束
	{
		//putchar(c);//打印在屏幕上
		printf("%c", c);
	}
	printf("\n");
	//文件读取结束后的判定
	if (ferror(pf))
	{
		printf("pf流有错。\n");
	}
	if (feof(pf))
	{
		printf("成功到达文件末尾。\n");
	}

	fclose(pf);
	pf = NULL;
}

运行结果为:
在这里插入图片描述

对通讯录程序的修改

通过对文件操作的学习,现将通讯录程序修改为使用文件进行存储的版本。
此版本修改建立在之前发布在博客C/C++ 通讯录程序构思与实现中所展示的V1版本通讯录程序的基础上。
添加了两个函数功能:

  1. save_contact(contact* pc):当在主界面选择0退出通讯录时,调用本函数,会将本次通讯录存储的内容写入到文件contact.dat中。
  2. load_contact(contact* pc):在程序开始时,创建通讯录结构体后,对通讯录结构体进行初始化,初始化后调用本函数,加载存储在contact.dat中的数据。
void load_contact(contact* pc)
{
	FILE* pf = fopen("contact.dat", "r");
	if (pf == NULL)
	{
		perror("load_contact::fopen");
		return;
	}

	while (fread(pc->contact + pc->sz, sizeof(peoinfo), 1, pf))
	{
		pc->sz++;
	}

	fclose(pf);
	pf = NULL;
}
//初始化函数
void init_contact(contact* pc)
{
	pc->sz = 0;
	memset(pc->contact, 0, Con_MAX * sizeof(peoinfo));//初始化为0

	//读取通讯录
	load_contact(pc);
}
//保存通讯录到文件
void save_contact(contact* pc)
{
	FILE* pf = fopen("contact.dat", "w");
	if (pf == NULL)
	{
		perror("save_contact::fopen");
		return;
	}

	//写文件
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->contact + i, sizeof(peoinfo), 1, pf);
	}

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

经测试,第一次在通讯录程序中存储2个人信息后,关闭通讯录。再次打开时直接选择进行打印,得到结果如下:
在这里插入图片描述
测试成功。

总结

本文总结了我对文件管理相关知识的学习,以做记录。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值