C语言实现通讯录——有关文件操作介绍

目录

1.前言

2.部分重点解析

3.具体代码


1.前言

提到通讯录管理系统,想必各位如果是计算机相关专业的同学都曾经被这个结课作业或多或少的支配过,那么今天我们就透过这个通讯录管理系统来为大家简单剖析一下有关文件的操作。

2.部分重点解析

在介绍文件操作之前,我们先简单实现一下不含文件操作版本的通讯录。

(1):简易菜单逻辑实现

在实现通讯录的具体各项功能前,我们应当先把他的“骨架”实现,这样才有利于我们建立一个清晰的项目逻辑,更有利于后续功能的有序实现。基于我们之前提到过的模块化设计思想,我们将以下代码放在test.c文件中实现

#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h"
void meum()
{
	printf("******************************\n");
	printf("** 1. add   2. del **\n");
	printf("** 3. search 4. modify **\n");
	printf("** 5. show  6. clear **\n");
	printf("** 7. sort  0. exit **\n");
	printf("******************************\n");
}

int main()
{
	int input = 0;
	Contact con;
	IntiContact(&con);
	do
	{
		meum();
		printf("请选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case ADD://添加
			AddContact(&con);
			break;
		case DEL://删除
			DeleteContact(&con);
			break;
		case SEARCH://查找
			FindContact(&con);
			break;
		case MODIFY://修改
			ModifyContact(&con);
			break;
		case SHOW://展示
			ShowContact(&con);
			break;
		case CLEAR://清空
			ClearContact(&con);
			break;
		case SORT://排序
			SortContact(&con);
			break;
		case EXIT://退出
			printf("退出程序\n");
		    DestroyContact(&con);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	}
	while (input);
	return 0;
}

有关以上代码我们还要结合contact.h这个头文件来看

#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#include<errno.h>
#include<malloc.h>
#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 20
#define ADDR_MAX 20
#define TELE_MAX 12
#define DEFAULT_SZ 3
enum Option//定义操作的枚举,比起用数字更直观
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	CLEAR,
	SORT
};
typedef struct PeoInfo//定义联系人信息的结构体
{
	char name[NAME_MAX];
	int age;
	char sex[SEX_MAX];
	char addr[ADDR_MAX];
	char tele[TELE_MAX];
}PeoInfo;

typedef struct Contact//嵌套定义通讯录结构体
{
	//PeoInfo data[MAX];
	PeoInfo* data;
	int sz;//当前人数
	int capacity;//最大容量
}Contact;

void IntiContact(Contact* pc);
void AddContact(Contact* pc);
void ShowContact(const Contact* pc);
void DeleteContact(Contact* pc);
void FindContact(Contact* pc);
void ModifyContact(Contact* pc);
void ClearContact(Contact* pc);
void SortContact(Contact* pc);
void DestroyContact(Contact* pc);

为什么我们需要这个头文件呢?答案是为了代码的后期维护,这样我们所实现的函数或者有关宏定义只需要全部存放在这里面,我们需要用到的时候就只需要引这一个我们自己的头文件就好,不需要重复引用,减少了代码冗余,同时我们如果需要修改某些定义时也可以直接在此修改,更利于我们的代码维护。

接下来我们进入最关键的核心函数实现,该代码我们存放于contact.c文件下

#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h";

void CheckCapacity(Contact* pc)//检测通讯录是否已满
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity * 2) * (sizeof(PeoInfo)));//增容操作
		if (ptr != NULL)
		{
			pc->data = ptr;
			pc->capacity *= 2;
			printf("增容成功\n");
		}
	}
}

void IntiContact(Contact* pc)//初始化
{
	assert(pc);
	pc->sz = 0;
	pc->data = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo));
	if (pc->data == NULL) //检查分配是否成功
	{
		printf("%s\n", strerror(errno));
		return;
	}
	pc->capacity = DEFAULT_SZ;
}


void AddContact(Contact* pc)
{
	assert(pc);
	CheckCapacity(pc);
	printf("请输入姓名\n");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入年龄\n");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入性别\n");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话\n");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入地址\n");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("添加成功\n");
}

void ShowContact(const Contact* pc)
{
	assert(pc);//确保传入参数有效
	if (pc->sz == 0)
	{
		printf("当前通讯录为空\n");
		return;
	}
	int i = 0;
	printf("%-10s\t%-5s\t%-5s\t%-13s\t%-20s\t\n", "名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-5d\t%-5s\t%-13s\t%-20s\t\n", 
			pc->data[i].name,pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

int FindByname(const Contact* pc,char name[])//搜索
{
	for(int i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}

void DeleteContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX] = { 0 };
	if (pc->sz == 0)
	{
		printf("通讯录已空,删除失败\n");
		return;
	}
	printf("请输入待删除联系人姓名\n");
	scanf("%s", name);
	int pos = FindByname(pc, name);
	if (pos == -1)
	{
		printf("该联系人不存在\n");
	}
	else
	{
		for (int j = 0; j < pc->sz - 1; j++)
		{
			pc->data[j] = pc->data[j + 1];
		}
		pc->sz--;
		printf("删除成功\n");
	}
}

void FindContact(Contact* pc)
{
	int i = 0;
	char name[NAME_MAX] = { 0 };
	int pos = 0;
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录已空,无法查找\n");
		return;
	}
		printf("请输入要查找人的名字:>");
	    scanf("%s", name);
		pos = FindByname(pc, name);
	if (pos == -1)
	{
		printf("要查找的条目不存在\n");
		return;
	}
	printf("%15s\t%5s\t%5s\t%12s\t%20s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%15s\t%5d\t%5s\t%12s\t%20s\n", pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

void ModifyContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX] = { 0 };
	int pos = 0;
	if (pc->sz == 0)
	{
		printf("通讯录已空,无法修改\n");
		return;
	}
	printf("请输入要修改的联系人姓名\n");
	scanf("%s", name);
	pos = FindByname(pc, name);
	if (pos == -1)
	{
		printf("要修改的联系人不存在,修改失败\n");
		return;
	}
	printf("请输入要修改什么信息(1-名字,2-年龄,3-性别,4-电话,5-住址):>");
	int msg = 0;
	scanf("%d", &msg);
	switch (msg)
	{
	case 1:
		printf("请输入新的姓名:>");
		scanf("%s", pc->data[pos].name);
		break;
	case 2:
		printf("请输入新的年龄:>");
		scanf("%d", &pc->data[pos].age);
		break;
	case 3:
		printf("请输入新的性别:>");
		scanf("%s", pc->data[pos].sex);
		break;
	case 4:
		printf("请输入新的电话:>");
		scanf("%s", pc->data[pos].tele);
		break;
	case 5:
		printf("请输入新的地址:>");
		scanf("%s", pc->data[pos].addr);
		break;
	default:
		printf("输入有误,修改失败\n");
		return;
	}
	printf("修改成功\n");
}

void ClearContact(Contact* pc)
{
	assert(pc);
	IntiContact(pc);
	printf("初始化成功\n");
}

void SortContact(Contact* pc)//通讯录排序
{
	PeoInfo tmp;
	for (int i = 0; i < pc->sz - 1; i++)//此处采用冒泡排序,也可换用效率更高的快排
	{
		for (int j = 0; j < pc->sz - i - 1; j++)
		{
			if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
			{
				tmp = pc->data[j];
				pc->data[j] = pc->data[j + 1];
				pc->data[j + 1] = tmp;
			}
		}
	}
	printf("排序完成\n");
}

void DestroyContact(Contact* pc)//退出通讯录后释放内存,避免内存泄漏
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

有关核心函数的实现大家可参照上述参考代码和注释,但是在上述通讯录完成后,我们可以发现一个问题,在退出程序后通讯录内的信息也随着内存的释放被销毁了,那么我们该如何令他在退出程序后依然存储有关信息呢?这时我们就需要用到文件操作了。

首先我们需要实现一个保存函数在程序退出之前执行

void SaveContact(Contact* pc)
{
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return ;
	}
	for (int i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}
	fclose(pf);//关闭文件
	pf = NULL;//防止野指针
}

我们先看到fopen函数的参数

FILE *fopen(char *filename, *type);

首先关于他的返回值:

如果成功的打开一个文件, fopen()函数返回文件指针,   否则返回空指针 (NULL)。由此可判断文件打开是否成功,所以这里我们要先判断指针是否为空。

然后看到第一个参数:

fopen()函数中第一个形式参数表示文件名, 可以包含路径和文件名两部分。

第二个参数:

 第二个形式参数表示打开文件的类型。关于文件类型的规定参见下表。

  1. 表   文件操作类型

  2. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  3. 字符                含义

  4. ────────────────────────────

  5. "r"           打开文字文件只读

  6. "w"           创建文字文件只写

  7. "a"           增补, 如果文件不存在则创建一个

  8. "r+"          打开一个文字文件读/写

  9. "w+"          创建一个文字文件读/写

  10. "a+"          打开或创建一个文件增补

  11. “rb+”        为了读和写打开一个二进制文件
  12. “wb+”      为了读和写,新建一个新的二进制文件
  13. “ab+”       打开一个二进制文件,在文件尾进行读和写

接下来我们看到fwrite函数

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

他的四个参数含义如下:

第一个ptr是要写入的数据的头指针,无符号类型;

第二个参数size是大小,表示每个写入元素的大小,单位是字节;

第三个参数nmemb是个数,以上一个参数为单位的个数;

第四个参数stream就是文件指针,表示往哪里写。

至于返回值,如果成功执行,则返回写入元素的个数,如果不和nmemb相等,则表示出错。

 

之后我们又面临一个问题,在我们重新打开通讯录时,我们如何读取已保存的数据,这个过程我们通常称之为加载,其实我们只需要在原初始化函数稍加改造即可。

void IntiContact(Contact* pc)
{
	assert(pc);
	pc->sz = 0;
	pc->data = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo));
	if (pc->data == NULL) //检查分配是否成功
	{
		printf("%s\n", strerror(errno));
		return;
	}
	pc->capacity = DEFAULT_SZ;
//_____________________________________//

	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return;
	}
	PeoInfo buf = { 0 };
	while (fread(&buf, sizeof(PeoInfo), 1, pf))
	{
		CheckCapacity(pc);
		pc->data[pc->sz] = buf;
		pc->sz++;
	}
	fclose(pf);
	pf = NULL;
	
}

 同样的我们看到fread函数

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

fread函数的作用是从文件里读内容到程序中,它的参数意思是:

第一个参数ptr表示盛放内容的首地址

第二个参数size表示每个元素的大小,单位还是字节

第三个参数nmem表示要读取的元素个数

第四个参数stream表示的是文件指针,即从哪个文件中读取

返回值则是表示读取元素的个数,与nmemb一致表示读取成功,否则失败

由于该函数操作与fread类似这里就不过多赘述了。

3.具体代码 

由于大部分代码在之前已经展示过,大家只需稍加改动即可,所以我们只放出最后的contact.c的文件代码。

#define _CRT_SECURE_NO_WARNINGS
#include"Contact.h";

void CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity * 2) * (sizeof(PeoInfo)));
		if (ptr != NULL)
		{
			pc->data = ptr;
			pc->capacity *= 2;
			printf("增容成功\n");
		}
	}
}

void IntiContact(Contact* pc)
{
	assert(pc);
	pc->sz = 0;
	pc->data = (PeoInfo*)calloc(DEFAULT_SZ, sizeof(PeoInfo));
	if (pc->data == NULL) //检查分配是否成功
	{
		printf("%s\n", strerror(errno));
		return;
	}
	pc->capacity = DEFAULT_SZ;

	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return;
	}
	PeoInfo buf = { 0 };
	while (fread(&buf, sizeof(PeoInfo), 1, pf))
	{
		CheckCapacity(pc);
		pc->data[pc->sz] = buf;
		pc->sz++;
	}
	fclose(pf);
	pf = NULL;
	
}


void AddContact(Contact* pc)
{
	assert(pc);
	CheckCapacity(pc);
	printf("请输入姓名\n");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入年龄\n");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入性别\n");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话\n");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入地址\n");
	scanf("%s", pc->data[pc->sz].addr);

	pc->sz++;
	printf("添加成功\n");
}

void ShowContact(const Contact* pc)
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("当前通讯录为空\n");
		return;
	}
	int i = 0;
	printf("%-10s\t%-5s\t%-5s\t%-13s\t%-20s\t\n", "名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-5d\t%-5s\t%-13s\t%-20s\t\n", 
			pc->data[i].name,pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}

int FindByname(const Contact* pc,char name[])
{
	for(int i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}

void DeleteContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX] = { 0 };
	if (pc->sz == 0)
	{
		printf("通讯录已空,删除失败\n");
		return;
	}
	printf("请输入待删除联系人姓名\n");
	scanf("%s", name);
	int pos = FindByname(pc, name);
	if (pos == -1)
	{
		printf("该联系人不存在\n");
	}
	else
	{
		for (int j = 0; j < pc->sz - 1; j++)
		{
			pc->data[j] = pc->data[j + 1];
		}
		pc->sz--;
		printf("删除成功\n");
	}
}

void FindContact(Contact* pc)
{
	int i = 0;
	char name[NAME_MAX] = { 0 };
	int pos = 0;
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录已空,无法查找\n");
		return;
	}
		printf("请输入要查找人的名字:>");
	    scanf("%s", name);
		pos = FindByname(pc, name);
	if (pos == -1)
	{
		printf("要查找的条目不存在\n");
		return;
	}
	printf("%15s\t%5s\t%5s\t%12s\t%20s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%15s\t%5d\t%5s\t%12s\t%20s\n", pc->data[pos].name,
		pc->data[pos].age,
		pc->data[pos].sex,
		pc->data[pos].tele,
		pc->data[pos].addr);
}

void ModifyContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX] = { 0 };
	int pos = 0;
	if (pc->sz == 0)
	{
		printf("通讯录已空,无法修改\n");
		return;
	}
	printf("请输入要修改的联系人姓名\n");
	scanf("%s", name);
	pos = FindByname(pc, name);
	if (pos == -1)
	{
		printf("要修改的联系人不存在,修改失败\n");
		return;
	}
	printf("请输入要修改什么信息(1-名字,2-年龄,3-性别,4-电话,5-住址):>");
	int msg = 0;
	scanf("%d", &msg);
	switch (msg)
	{
	case 1:
		printf("请输入新的姓名:>");
		scanf("%s", pc->data[pos].name);
		break;
	case 2:
		printf("请输入新的年龄:>");
		scanf("%d", &pc->data[pos].age);
		break;
	case 3:
		printf("请输入新的性别:>");
		scanf("%s", pc->data[pos].sex);
		break;
	case 4:
		printf("请输入新的电话:>");
		scanf("%s", pc->data[pos].tele);
		break;
	case 5:
		printf("请输入新的地址:>");
		scanf("%s", pc->data[pos].addr);
		break;
	default:
		printf("输入有误,修改失败\n");
		return;
	}
	printf("修改成功\n");
}

void ClearContact(Contact* pc)
{
	assert(pc);
	IntiContact(pc);
	printf("初始化成功\n");
}

void SortContact(Contact* pc)
{
	PeoInfo tmp;
	for (int i = 0; i < pc->sz - 1; i++)
	{
		for (int j = 0; j < pc->sz - i - 1; j++)
		{
			if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
			{
				tmp = pc->data[j];
				pc->data[j] = pc->data[j + 1];
				pc->data[j + 1] = tmp;
			}
		}
	}
	printf("排序完成\n");
}

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

void SaveContact(Contact* pc)
{
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return ;
	}
	for (int i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}
	fclose(pf);
	pf = NULL;
}

那么本文到此结束,作者能力有限,文章如有纰漏请在评论区下方指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值