通讯录的思路与实现(C语言)

news2024/11/16 22:40:17

目录

前言

程序的分装

程序的结构

函数实现

通讯录的初始化

通讯录的扩容

将数据保存到本地

增加联系人

显示通讯录所有联系人

目标联系人的检索(根据名称)

目标联系人的检索(根据号码)

检索发展来的函数

删除联系人

查询目标联系人

联系人信息的更改

按名称对通讯录进行排序

找到属于目标类别的联系人

通讯录的销毁

小结


前言

🎃濒临期末,大一的期末设计就安排写一个通讯录的程序来实现以下的内容,虽说没有要求使用动态内存跟文件操作,但完善一点自然是更好。类似的通讯录之前已经写过了,还是在这里记录一下,给不了解的同学讲一讲思路跟实现。

 

程序的分装

🎃为了便于代码的管理以及实现分作 test.c contact.c 和 contact.h 三个部分,使用自己的头文件,可以避免由于前后顺序不同使得函数复用时出现故障的情况。test.c 主要用于菜单的实现,contact.c 用于通讯录所需函数的实现,contact.h 则用于函数的声明以及结构的定义等前置工作。

程序的结构

🎃我们知道通讯录里存放的是一个个联系人,而联系人包括了各种各样的信息,如姓名、单位、联系方式、类别等。因此可以确定的是一个联系人应该定义一个结构体来存储这些对应的信息。至于通讯录我们则可以使用循序表来存储每一个联系人。所以顺序表内部的数组应该是结构体数组,同时为了保证内存的充分利用,这里还需要使用动态内存的知识及时地调整顺序表的大小。

//常量的定义   (定义常量是为了方便修改)
#define MAX_NAME 20
#define MAX_Company 30
#define MAX_TEL 12 
#define MAX_ADD 30
#define MAX_mail 30
#define MAX_category 10

//一个联系人的结构
struct PeoInfo
{
	char name[MAX_NAME];
	char company[MAX_Company];
	char mail[MAX_mail];
	char tel[MAX_TEL];
	char add[MAX_ADD];
	char category[MAX_category]; 
};

//整个通讯录的结构
struct contact
{
	struct PeoInfo *data;
	int sz;
	int capacity;
};

函数实现

🎃当函数在头文件里都定义完之后,我们就可以着手于通讯录相关函数的实现了。

通讯录的初始化

🎃这个通讯录是以循序表的结构实现的。因此对于一个在主函数里申请的循序表,我们需要在这里对其进行初始化操作。

🎃首先进行断言,保证传进来的指针不是空指针,以避免之后操作里出现对空指针的解引用操作。之后需要为顺序表开辟空间。这里初始化设置为 之后根据需要再进行调整。并将基础数值填充到循序表里,之后通过文件读取的方式打开外部文件,读取外部内容并将其拷贝到当前的顺序表里,若外部没有目标文件则新建一个文件用于储存数据。最后关闭文件进行收尾,函数结束。

void InitContact(struct contact* pc)
{
	assert(pc);                 //保证传进来的不是空指针
	pc->data = (struct PeoInfo*)malloc(3 * sizeof(struct PeoInfo));  //初始值为3地开辟空间
	if (pc->data == NULL)    //对malloc检查
	{
		perror("InitContact");
		return;
	}
	pc->sz = 0;           //赋初始值
	pc->capacity = 3;
	FILE* pf = fopen("data.txt", "rb");   //使用文件操作打开文件
	if (!pf)
	{
		pf = fopen("data.txt", "ab+");    //没有就新建一个
	}
	struct PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(struct PeoInfo), 1, pf))   //从文件里一次读取一个的人的信息
	{
		if (pc->sz == pc->capacity)                //检查顺序表空间
		{
			check_capacity(pc); 
		}
		pc->data[pc->sz] = tmp;          //赋值
		pc->sz++;
	}
	fclose(pf);   //关闭文件
	pf = NULL;    //指针置空
}

通讯录的扩容

🎃当通讯录内容已满就需要扩容,这里依附的是 realloc 函数,每次默认增加两个联系人的大小。根据 realloc 的特性,我们不知道新的空间是原地开辟还是异地开辟,因此需要将其再度赋值给 data ,使得顺序表存储的空间就是我们所开辟的新空间。

void check_capacity(struct contact* pc)
{     //每次加2的增大循序表的大小
	struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(struct PeoInfo));
	if (ptr == NULL)     //判断扩容是否成功
	{
		printf("增容失败\n");
		perror("PeoInfoadd()");
		return;
	}
	else
	{
		pc->data = ptr;    //将创建的新地址给data
		pc->capacity += 2;
	}
}

将数据保存到本地

🎃若不增加文件操作,那么我们输入进去的数据就是一次性的,只要关闭程序这些数据也会跟着消失。为了我们输入进去的数据可以再次使用,所以我们需要将当前的数据保存到文件里,下次通讯录初始化的时候就会读取文件里面的内容,就完成了数据的本地存储。

首先以 wb 的形式打开文件并对其进行检查。若无问题,则将当前通讯录内的联系人一个个写入文件里。由于 wb 每次写入都会清空文件内部的内容,所以不用担心会有数据重复的情况。最后关闭文件

void contactsave(struct contact* pc)
{
	FILE* pf = fopen("data.txt", "wb");   //以写入二进制的方式打开文件
	if (!pf)
	{
		perror(fopen);                    //没有找到文件就报错
		return 1;
	}
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data+i, sizeof(struct PeoInfo), 1, pf);  //一个一个拷贝进文件
	}
	fclose(pf);   //关闭文件
	pf = NULL;
	printf("保存成功\n");
}

这些前置工作都做完之后,才是实际用户使用的函数的实现。

增加联系人

🎃首先先检查顺序表的内存是否充足,之后根据不同的数据类型对不同的内容进收录。由于 sz 在数组里指向的都是当前数组最后一个联系人的下一位,因此 sz 就为当前我们要录入的联系人对应在数组里的下标(仔细想想),最后sz++标志通讯录存储人数加一,完成录入。

void PeoInfoadd(struct contact* pc)
{
	if (pc->sz == pc->capacity)     //检查内存是否已满
	{
		check_capacity(pc);         //满了就扩容
	}
	printf("请输入姓名\n");          //录入各个数据
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入单位\n");
	scanf("%s", pc->data[pc->sz].company);
	printf("请输入类别\n");
	scanf("%s", pc->data[pc->sz].category);
	printf("请输入电话\n");
	scanf("%s", pc->data[pc->sz].tel);
	printf("请输入地址\n");
	scanf("%s", pc->data[pc->sz].add);
	printf("请输入邮箱\n");
	scanf("%s", pc->data[pc->sz].mail);
	pc->sz++;                   //标志通讯录存储人数加一
	printf("联系人添加成功\n");
}

显示通讯录所有联系人

🎃为了形式美观也为了使用者在使用时可以观测到数据对应的意义,在打印信息前先打印各列数据对应的内容。再根据原先定义的不同数据的上限大小对打印出来的数据进行左对齐。

void showPeoInfodel(struct contact* pc)
{
	int i = 0;                     //首行打印的是各行数据所代表的意义
	printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n", "姓名", "单位", "类别", "电话", "地址","邮箱");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n",
			pc->data[i].name,
			pc->data[i].company,
			pc->data[i].category,
			pc->data[i].tel, 
			pc->data[i].add,
			pc->data[i].mail);
		printf("\n");
	}
}

🎃打印出来就是这个样子的。

 

目标联系人的检索(根据名称)

🎃用于以名字查找目标联系人,需要外部传入一个字符串供对比。通过遍历顺序表的方式查找目标联系人。找到了就返回目标联系人的下标,若找不到就返回 -1 ,供调用其的函数进行甄别。用于以名字查找目标联系人,需要外部传入一个字符串供对比。通过遍历顺序表的方式查找目标联系人。找到了就返回目标联系人的下标,若找不到就返回 -1 ,供调用其的函数进行甄别。

int findpeo(struct contact* pc, char name[])
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		int ret = strcmp(pc->data[i].name, name);  //用strcmp进行对目标人物的查找
		if (ret == 0)
		{
			return i;         //返回目标的下标
		}
	}
	return -1;   //找不到返回-1
}

目标联系人的检索(根据号码)

🎃其实与跟名字查找是类似的,基本上的程序相差不大,由检索名字转向检索号码。因为我们存储号码使用的也是字符数组,因此可以直接使用 strcmp 进行对比

int findphone(struct contact* pc, char* phone)
{
	for (int i = 0; i < pc->sz; i++)
	{
		int ret = strcmp(pc->data[i].tel, phone);   //检索号码
		if (ret == 0)
		{
			return i;
		}
		else
		{
			return -1;
		}
	}
}

检索发展来的函数

删除联系人

🎃删除指定的联系人,首先要先用一个数组接收输入进来的字符串。之后通过调用 findpeo 来查找目标联系人,之后通过移动数据的方式,覆盖掉目标位置的联系人,由于 sz 会进行调整,即便后面的数据保留下来,但访问的界限取决于 sz 因此不会对程序造成影响。同时下次录入数据时就会覆盖掉尾部的数据。

void PeoInfodel(struct contact* pc)
{
	char name[MAX_NAME] = { 0 };      //先将目标名称保存,以便之后对比
	printf("输入要删除的联系人的名字: ");
	scanf("%s", name);
	int ret = findpeo(pc, name);      //用子函数以名字查找人
	if (ret == -1)
	{
		printf("找不到此联系人\n");
	}
	else
	{
		int j = 0;
		for (j = ret; j < pc->sz - 1; j++)  //通过移位的方式去除数据
		{
			pc->data[j] = pc->data[j + 1];
		}
		pc->sz--;
		printf("该联系人已删除\n");
	}
}

查询目标联系人

🎃这个函数增加的一点在于还要打印出联系人的信息,因此无论是查询姓名还是号码也就只有调用函数不同这个区别而已。

void search(struct contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("输入要查找人的姓名\n");
	scanf("%s", name);
	int ret = findpeo(pc, name);   //调用找名字的函数
	if (ret == -1)
	{
		printf("找不到此联系人\n");
	}
	else
	{
		//找到了就打印出来
		printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n", "姓名", "单位", "类别", "电话", "地址","邮箱");
		printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n",
			pc->data[ret].name,
			pc->data[ret].company,
			pc->data[ret].category,
			pc->data[ret].tel,
			pc->data[ret].add,
			pc->data[ret].mail);  
	}
}
void seachbyphone(struct contact* pc)
{
	char phone[MAX_TEL] = { 0 };
	printf("请输入要查找的号码: ");
	scanf("%s", phone);
	int ret = findphone(pc, phone);
	if (ret == -1)
	{
		printf("找不到此联系人\n");
	}
	else
	{
		printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n", "姓名", "单位", "类别", "电话", "地址", "邮箱");
		printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n",
			pc->data[ret].name,
			pc->data[ret].company,
			pc->data[ret].category,
			pc->data[ret].tel,
			pc->data[ret].add,
			pc->data[ret].mail);
	}
}

联系人信息的更改

🎃这几个函数都是异曲同工,都是查找目标联系人,这里通过直接覆盖的方式对目标联系人的信息进行修改。

void peochange(struct contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要修改人的姓名\n");
	scanf("%s", name);
	int ret = findpeo(pc, name);
	if (ret == -1)
	{
		printf("找不到此联系人\n");
	}
	else
	{
		printf("请输入要修改成的姓名\n");
		scanf("%s", pc->data[ret].name);
		printf("请输入要修改成的单位\n");
		scanf("%s", pc->data[ret].company);
		printf("请输入要修改成的类别\n");
		scanf("%s", pc->data[ret].category);
		printf("请输入要修改成的电话\n"); 
		scanf("%s", pc->data[ret].tel);
		printf("请输入要修改成的地址\n");
		scanf("%s", pc->data[ret].add);
		printf("请输入要修改成的邮箱\n");
		scanf("%s", pc->data[ret].mail);
		printf("修改成功\n");
	}

按名称对通讯录进行排序

🎃以名字的为对比的标准,从而对当前联系人进行排序,为了方便这里直接使用 qsort 进行排序就足够满足性能要求,而没有必要特意写个快排或者堆排。说到排序,过几天会写一个博客专门介绍排序,敬请期待。

int cmp(void* e1, void* e2)
{          //直接对名称进行比较
	return strcmp(((struct PeoInfo*)e1)->name, ((struct PeoInfo*)e2)->name);
}

void sortContact(struct contact* pc)
{
	qsort(pc->data, pc->sz, sizeof(struct PeoInfo), cmp);
	printf("排序已完成\n");
}

找到属于目标类别的联系人

🎃其实并没有想象的那么难,只要根据输入的类别遍历数组并对目标类别进行检索,并打印出该类别的联系人就可以了。

void showbycatefory(struct contact* pc)
{
	char category[MAX_category] = { 0 };
	printf("请输入要查找的类别: ");
	scanf("%s", category);          
	printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n", "姓名", "单位", "类别", "电话", "地址", "邮箱");
	for (int i = 0; i < pc->sz; i++)   //遍历顺序表
	{
		int ret = strcmp(pc->data[i].category, category);  //属于目标类别则打印
		if (ret == 0)
		{
			printf("%-20s\t%-30s\t%-10s\t%-12s\t%-30s\t%-30s\n",
				pc->data[i].name,
				pc->data[i].company,
				pc->data[i].category,
				pc->data[i].tel,
				pc->data[i].add,
				pc->data[i].mail);
		}
	}
}

通讯录的销毁

🎃通讯录结束使用或是需要清除通讯录里的所有内容则需要使用到这个函数,先将顺序表申请内存释放,之后再对指向申请空间的指针置空,其他的值都回归初始状态,就完成了通讯录的销毁。

void detorycontact(struct contact* pc)
{
	free(pc->data);       //释放内存
	pc->data = NULL;      //指向的内容置空
	pc->sz = 0;           //回归初始化
	pc->capacity = 0;
}

小结

🎃这个通讯录的实现并没有那么的困难,主要的难点在于结构的定义以及该结构的具体使用上。由于这个结构层层嵌套,因此在使用的时候要十分注意对通讯录结构指针解引用时的优先级。只有知道了当前数据的数据类型才能保证我们可以对其进行正确的操作。其次就是对于动态内存和文件操作需要十分的熟悉,才能避免出现对空指针的解引用。做到文件有开就有关,内存有申请就有释放。

🎃都看到这么下面了,要个三连不过分吧,关注博主一起学习!!!🧸🧸🧸

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/96792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python写个“点球大战”小游戏

大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 看过我Python入门教程的朋友应该会看到其中有提到一个点球小游戏的作业。 在世界杯决赛即将到来之际&#xff0c;我们再来回顾一下这个小游戏。对于刚刚学习编程不久的同学&#xff0c;这是个不错的练手习题&…

(二)RT-Thread入门——线程管理

目录 线程管理 线程管理特点 线程工作机制 线程控制块 线程属性 线程栈 线程状态 线程优先级 时间片 线程入口函数 无限循环模式 顺序执行或有限次循环模式 线程错误码 线程状态切换 线程操作 创建动态线程 删除 初始化静态线程 脱离 获得当前线程 让出…

数据结构基础篇》》用c语言实现复数的八个基本运算

数据结构开讲啦&#xff01;&#xff01;&#xff01;&#x1f388;&#x1f388;&#x1f388; 本专栏包括&#xff1a; 抽象数据类型线性表及其应用栈和队列及其应用串及其应用数组和广义表树、图及其应用存储管理、查找和排序将从简单的抽象数据类型出发&#xff0c;深入浅出…

B-013 缓启动电路设计

缓启动电路设计1 简介2 案例分析2.1 电路说明2.2 原理分析3 电路参数设定说明1 简介 缓启电路的供电是由一个PMOS控制通断的&#xff0c;软启动的设计是让PMOS的导通时间变缓&#xff0c;电路上的做法是在PMOS的栅极和源极之间接一个合适的电容&#xff0c;PMOS的导通时间就会…

Arcgis中创建Python脚本工具

文章目录创建工具步骤第一步&#xff1a;第二步&#xff1a;第三步&#xff1a;定义工具工具箱Toolbox工具类1、__init__2、getParameterInfo3、isLicensed4、updateParameters5、updateMessage6、execute进度条的使用代码相比于自定义工具箱的源脚本和参数定义难以集中管理的缺…

中国专利电子申请网站系统环境配置方法

一、在线平台使用环境要求 支持的操作系统、浏览器、office的版本如下&#xff0c;必须匹配对应的版本&#xff1a; 操作系统&#xff1a;WINDOWS XP、WINDOWS 7、WINDOWS 8 浏览器&#xff1a;IE8、IE9、IE10 文档编辑软件&#xff1a;OFFICE2003、OFFICE2007 强烈推荐使用中…

1. Maven基础

1. Maven简介 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构 提供了一套标准化的构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布……&#xff09; 提供了一套依赖管理机制 1.1…

Allegro快速编辑丝印文字操作指导

Allegro快速编辑丝印文字操作指导 Allegro支持丝印文字的编辑,下面介绍快速编辑丝印文字的两种方法如下 以编辑下方丝印文字为例 方法一: 选择Text edit 命令 点击丝印文字,丝印会被高亮起来 输入需要更改后的文字,如下 右击选择done 文字被更改好了 方法二 选择se…

Function composition

In mathematics, function composition is an operation  ∘  that takes two functions f and g, and produces a function h g  ∘  f such that h(x) g(f(x)). In this operation, the function g is applied to the result of applying the function f to x. That is…

van-uplaoder保存文件到后端,回显后端接口返回的数据

实现功能&#xff1a;在移动端使用van-uploader组件上传图片&#xff0c;然后调用接口保存到后端数据库&#xff0c;提交保存信息成功后&#xff0c;调用另外的接口返回数据用来回显uploaded的文件&#xff0c;&#xff08;一般正常的返回数据的接口是个图片地址&#xff0c;可…

15 CPP函数重载

函数重载的细节&#xff1a; 1 使用重载函数时&#xff0c;如果数据类型不匹配&#xff0c;C尝试使用类型转换与形参进行匹配&#xff0c;如果转换后有多个函数能匹配上&#xff0c;编译将报错。 2 引用可以作为函数重载的条件&#xff0c;但是调用重载函数的 时候&#xff0…

javaSE - 认识字符串(String class),String类里面方法的使用,下半部分

一、字符, 字节与字符串 1.1、字符与字符串之间进行转换 字符串内部包含一个字符数组&#xff0c;String 可以和 char[] 相互转换 将整个字符数组转换成字符串 public static void main(String[] args) {char[] chars {a,b,c,d,e,f,g};String str new String(chars);Sys…

Sulfo-NHS-SS-biotin,CAS:325143-98-4介绍,生物素双硫键琥珀酰亚胺

英文名称&#xff1a;Sulfo-NHS-SS-biotin 化学式&#xff1a;C19H27N4NaO9S4 分子量&#xff1a;606.7 CAS&#xff1a;325143-98-4 纯度&#xff1a;95% 储存条件&#xff1a;-20C 结构式&#xff1a; 简介&#xff1a;磺基NHS SS生物素是一种可切割试剂&#xff0c;用…

【剧前爆米花--爪哇岛寻宝】抽象类和接口(上)——理论及逻辑理解

作者&#xff1a;困了电视剧 专栏&#xff1a;《JavaSE语法与底层详解》 文章分布&#xff1a;这是一篇关于抽象类和接口的文章&#xff0c;在本篇文章中我会介绍其相关的定义和语法&#xff0c;并且揭示接口和抽象类的运行逻辑&#xff0c;提高对面对象编程的理解。 目录 抽象…

吴恩达week6 ~批量梯度下降 指数加权平均 动量梯度下降 学习率衰减 Adam

文章目录前言一、小批量梯度下降 mini-batch1、batch gradient descent2、stochastic gradient descent3、mini-batch gradient descent二、指数加权平均1.什么是指数加权平均2、理解指数加权平均3、与普通求平均值的区别4、指数加权平均的偏差修正三、gradient descent with m…

Allegro批量替换过孔类型操作指导

Allegro批量替换过孔类型操作指导 Allegro支持批量替换过孔类型,具体操作如下 例如需要把这些VIA10的过孔全部替换成VIA8的过孔 选择菜单上面的Tool-padstack-Group edit 右击选择temp Group 选中需要替换的过孔 选完之后右击选择complete 弹出Padstack Map窗口,modify…

java中多线程、并发、并行、线程与进程、线程调度、创建线程的方式

多线程&#xff1a; 多线程比单线程快&#xff0c;前面简单介绍过&#xff1a;集合如果是不安全的&#xff0c;那么它就是多线程的&#xff0c;了解多线程之前&#xff0c;先了解什么是并发和并行。 并发&#xff1a;指两个或多个事件在同一个时间段内发生。 并行&#xff1…

高通平台开发系列讲解(AtCoP篇)AtCoP架构简介

文章目录 一、ATCoP简介二、ATCoP架构三、流程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇讲介绍高通ATCoP的架构。 一、ATCoP简介 ATCoP(AT Command Processor)是高通平台对于AT命令处理的模块,通过它,我们可以实现对AT命令的修改和新增。ATCoP接收从串口…

[附源码]Python计算机毕业设计公司办公自动化系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…