基于C语言 --- 自己写一个通讯录

C语言程序设计笔记---039

  • C语言之实现通讯录
    • 1、介绍C/C++程序的内存开辟
    • 2、C语言实现通讯录
      • 2.1、ContactMain.c程序大纲
      • 2.2、Contact2.h
      • 2.3、Contact2.c
        • 2.3.1 InitContact( )初始化通讯录函数
        • 2.3.2 AddContact( )添加联系人和CheckCapaticy( )检查容量函数
        • 2.3.3、ShowContact( )显示联系人函数
        • 2.3.4、DelContact( )删除联系人和FindByName( )函数
        • 2.3.5、SearchContact( )查找联系人
        • 2.3.6、ModifyContact( )修改联系人
        • 2.3.7、CleanContact( )清空联系人
        • 2.3.8、SortContact( )排序联系人
    • 4、结语

C语言之实现通讯录

前言:
通过C语言自定义类型的知识,这篇将对动态内存的管理知识,接下来进行应用学习写一个简易的通讯录。

/知识点汇总/

1、介绍C/C++程序的内存开辟

大致整体空间的分配
内核空间:用户代码…(不能读写)
栈区:局部变量、形式参数…
内存映射段:文件映射、动态库、匿名映射…
堆区:动态内存、malloc/calloc/free…
数据段(数据段):全局数据、静态数据、static常变量…
代码段:可执行代码、只读常量
具体如下图所示
在这里插入图片描述

2、C语言实现通讯录

实现一个通讯录;
通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址
提供方法:
1.添加联系人信息
2.删除指定联系人信息
3.查找指定联系人信息
4.修改指定联系人信息
5.显示所有联系人信息
6.清空所有联系人
7.以名字排序所有联系人

2.1、ContactMain.c程序大纲

首先,从生活实际使用的角度出发,我们需要一个显示的菜单,显示我们需要的功能集。
这里可以前面篇章所学的menu( )自定义函数,设计一个简易的菜单。

//菜单
void menu()
{printf("*************************************\n");printf("******** 1.add          2.del   *****\n");printf("******** 3.sreach       4.modify*****\n");printf("******** 5.show         6.sort  *****\n");printf("******** 7.clean        0.exit  *****\n");printf("*************************************\n");
}

为了让选项贴合实际,数字0~7可以使用一个自定义类型所学的枚举类型来列举引用即可。

//枚举常量
enum Option
{EXIT,//0ADD,//1DEL,//2SEARCH,//3MODIFY,//4SHOW,//5SORT,//6CLEAN//7
};

其中可以看见,我把EXIT写在第一个,这样是为了利用枚举成员变量未初始化是默认从0开始的特点,从而与选项一致对应。
那么接下来,主函数中就是利用do while和 switch 搭建一个程序执行框架。

int main()
{int input = 0;//创建通讯录Contact con;//初始化通讯录InitContatc(&con);do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case ADD:printf("\n添加联系人\n\n");AddContact(&con);break;case DEL:printf("\n删除联系人\n\n");DelContact(&con);break;case SEARCH:printf("\n搜索联系人\n\n");SearchContact(&con);break;case MODIFY:printf("\n修改联系人\n\n");ModifyContact(&con);break;case SHOW:printf("\n显示联系人\n\n");ShowContact(&con);break;case SORT:printf("\n排序联系人\n\n");SortContact(&con);break;case CLEAN:printf("\n清空联系人\n\n");CleanContact(&con);break;case EXIT:printf("\n退出通讯录\n\n");break;default:printf("\n输入错误请重新输入\n\n");break;}} while (input);return 0;
}

主函数首先定义一个input由玩家输入的变量,然后同步switch语句,根据选项进入不同的入口,分别执行添加联系人、删除联系人、搜索联系人、修改联系人、显示联系人、排序联系人、清空联系人、退出通讯录以及输入数值不对做出一个反馈,输入错误的提示信息。

2.2、Contact2.h

主要用于存放所自定义的函数和头文件等声明的程序
由于涉及多种类型的数据,所以最好的方法就是定义一个结构体类型。
然后,这里同时提供通讯录初始化的静态数组的版本,也提供动态内存的写法,利用前篇动态内存空间管理的知识,申请动态开辟空间和管理。
其它的声明,通俗易懂,就不多赘述,详见代码注释的说明。

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 20
#define ADDR_MAX 20//#define MAX 100
#define DEFAULT_SZ 3   //默认容量
#define DEFAULT_INC 2  //增容量
//结构体类型申明
typedef struct PeoInfo
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}PeoInfo;//创建通讯录 --- 静态版本
//typedef struct Contact
//{
//	PeoInfo data[MAX];//通讯录容量
//	int sz;//当前通讯录的信息个数
//}Contact;//创建通讯录 --- 动态版本
typedef struct Contact
{PeoInfo* data;//指向通讯录的数据int sz;//当前通讯录的信息个数int capacity;//记录当前通讯录的容量
}Contact;//初始化通讯录
void InitContact(Contact* pc);//增加联系人
void AddContact(Contact* pc);//销毁通讯录
void DestoryContact(Contact* pc);//显示通讯录
void ShowContact(Contact* pc);//删除联系人
void DelContact(Contact* pc);//查找联系人
void SearchContact(Contact* pc);//修改联系人
void ModifyContact(Contact* pc);//清空通讯录
void CleanContact(Contact* pc);//排序通讯录
void SortContact(Contact* pc);

2.3、Contact2.c

主要用于存放对 ContactMain.c 程序大纲做提到的函数进行封装,实现具体的功能的程序
说明:基于ContactMain.c 程序大纲逻辑对代码进行讲解

2.3.1 InitContact( )初始化通讯录函数

首先在头文件中,定义了结构体成员和创建好后,根据主函数逻辑对通讯录进行初始化.
初始化可以理解为将通讯录的数据赋予一个初始值,方便我们后续对其进行操作。
sz清零就是使得当前存放的长度为0;capacity清零就是让容量为默认的3个容量;
接下来就是将数据初始化,这里为了直接方便的初始化所以选择使用calloc而不是malloc函数来初始化,因为它们的区别就在于calloc会直接将每个字节初始化为0,而malloc不会初始化。
然后结合参数的意思就是将申请开辟默认容量乘以结构体得的大小的空间。
为了代码的规范性、健壮性需要做开辟空间失败的反馈。

//初始化通讯录 -- 静态版本
//void InitContact(Contact* pc)
//{
//	assert(pc);
//	pc->sz = 0;
//	memset(pc->data, 0, sizeof(pc->data));//将数据以每个字节设置或初始化为0
//}//初始化通讯录 -- 动态版本
void InitContact(Contact* pc)
{assert(pc);pc->sz = 0;pc->capacity = DEFAULT_SZ;pc->data = calloc(pc->capacity , sizeof(PeoInfo));if (pc->data == NULL){perror("InitContact->calloc\n");return;}
}
2.3.2 AddContact( )添加联系人和CheckCapaticy( )检查容量函数

同样,提供了静态版本和动态版本,简单说明一下静态和动态的区别,当我们不清楚需要多大的空间时,比如需要存放1000个数据,静态给10太小,给10000太大,所以存在空间资源的浪费甚至是溢出等情况,那么就引用动态的方式存储,就可以实现灵活的开辟空间和释放空间,既满足需求,也能合理使用空间资源。那么接下来,都是以动态进行分享思路。
回到添加联系人的函数体,我们每次存入数据前,肯定需要检查是否能够存放得下,那么就需要对当前空间的剩余量与数据的大小,进行判断,如果放不下那么就执行扩容,反之就是放得下。
那么就可以继续执行添加联系人得信息,利用的是按照逻辑访问结构体成员依次添加,最后完成一个数据添加后,sz当前通讯录的信息个数随着加1。
对于,增容函数,主要是依靠realloc函数实现增容。参数是开辟空间的首地址和增容的大小。
另外,对于realloc还有一种特殊情况,就不展开了,详见前篇动态内存空间管理的知识。

//增加联系人 --- 静态版本
//void AddContact(Contact* pc)
//{
//	assert(pc);
//	if (pc->sz == MAX)
//	{
//		printf("\n通讯录已满,无法增加\n\n");
//		return;
//	}
//	//增加信息
//	printf("请输入联系人姓名:>");
//	scanf("%s", pc->data[pc->sz].name);
//	printf("请输入联系人年龄:>");
//	scanf("%d", &pc->data[pc->sz].age);
//	printf("请输入联系人性别:>");
//	scanf("%s", pc->data[pc->sz].sex);
//	printf("请输入联系人电话:>");
//	scanf("%s", pc->data[pc->sz].tele);
//	printf("请输入联系人地址:>");
//	scanf("%s", pc->data[pc->sz].addr);
//	//信息个数增加
//	pc->sz++;
//	printf("\n增加成功\n\n");
//}//增容函数
static void CheckCapaticy(Contact* pc)
{if (pc->sz == pc->capacity){PeoInfo* ptr = realloc(pc->data, (pc->capacity + DEFAULT_INC) * sizeof(PeoInfo));if (ptr != NULL){pc->data = ptr;pc->capacity += DEFAULT_INC;printf("\n增容成功\n\n");}else{perror("AddContact->realloc\n");return;}}
}
//增加联系人 --- 动态版本
void AddContact(Contact* pc)
{assert(pc);//如果容量与信息相等,自动增加容量CheckCapaticy(pc);//增加信息printf("请输入联系人姓名:>");scanf("%s", pc->data[pc->sz].name);printf("请输入联系人年龄:>");scanf("%d", &pc->data[pc->sz].age);printf("请输入联系人性别:>");scanf("%s", pc->data[pc->sz].sex);printf("请输入联系人电话:>");scanf("%s", pc->data[pc->sz].tele);printf("请输入联系人地址:>");scanf("%s", pc->data[pc->sz].addr);//信息个数增加pc->sz++;printf("\n增加成功\n\n");
}

在这里插入图片描述

2.3.3、ShowContact( )显示联系人函数

这个函数的逻辑就比较简单了,就是遍历数据成员依次打印出来就行,重点在于对界面符合一定的审美,包括占位符、缩进。标题行的重要性等比较灵活,这里仅提供参考一种打印效果如下所示:
在这里插入图片描述

//显示通讯录
void ShowContact(Contact* pc)
{assert(pc);if (pc->sz == 0){printf("\n无法显示,通讯录为空\n\n");return;}//显示联系人//打印标题 -- 美化printf("%-10s%-5s%-5s%-20s%-20s\n", "姓名", "年龄", "性别", "电话", "地址");//打印联系人信息int i = 0;for (i = 0; i < pc->sz; i++){printf("%-10s%-5d%-5s%-20s%-20s\n",pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}

在这里插入图片描述

2.3.4、DelContact( )删除联系人和FindByName( )函数

对于删除函数,我们需要先判断是否为空,当数据为空,没有删除的对象了,顾名思义就不能删除了;其次,我们需要删除某个联系人对象,与后面的查找联系人和修改联系人都需要去遍历查找所以,为了方便调用就封装了一个匹配联系人的函数FindByName,并且是根据联系人姓名来进行匹配的。直接使用strcmp即可。在删除目标联系人后,后面的数据就得向前移动。使用for语句移动,最后sz也需要随着减1。

//匹配联系人函数
static int FindByName(Contact* pc, char* name)
{assert(pc);int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(pc->data[i].name, name) == 0){return i;}}return -1;
}
//删除联系人
void DelContact(Contact* pc)
{assert(pc);if (pc->sz == 0){printf("\n无法删除,通讯录为空\n\n");return;}//输入要删除的联系人姓名char name[NAME_MAX];printf("请输入要删除联系人的姓名:>");scanf("%s", name);//查找匹配联系人int ret = FindByName(pc, name);if (ret == -1){printf("\n无此联系人\n\n");return;}//删除联系人int i = 0;for (i = ret; i < pc->sz; i++){pc->data[i] = pc->data[ i + 1];}//处理最后一个联系人pc->sz--;printf("\n删除成功\n\n");
}

在这里插入图片描述
在这里插入图片描述

2.3.5、SearchContact( )查找联系人

查找联系人的套路都大相径庭了,结合匹配函数找到对应的联系人,然后单独打印输出即可。

//查找联系人
void SearchContact(Contact* pc)
{assert(pc);if (pc->sz == 0){printf("\n无法查找,通讯录为空\n\n");return;}//输入要查找的联系人姓名char name[NAME_MAX];printf("请输入要查找联系人的姓名:>");scanf("%s", name);//查找匹配联系人int ret = FindByName(pc, name);if (ret == -1){printf("\n无此联系人\n\n");return;}//显示对应联系人//打印标题 -- 美化printf("%-10s%-5s%-5s%-20s%-20s\n", "姓名", "年龄", "性别", "电话", "地址");//打印联系人信息printf("%-10s%-5d%-5s%-20s%-20s\n",pc->data[ret].name,pc->data[ret].age,pc->data[ret].sex,pc->data[ret].tele,pc->data[ret].addr);
}

在这里插入图片描述

2.3.6、ModifyContact( )修改联系人

修改联系人,在理解了前面的内容,也不难理解,结合匹配联系人的函数找到需要修改的联系人,然后,输入修改后的信息即可。但是,这里比较冗余,不难发现会修改联系人的所有属性。可以增加一个switch选择只需要更改的属性即可。值得注意的是,在以上结合匹配函数的条件下,使用的是返回值ret才能正确符合逻辑运行哦,不能盲目复制粘贴前面的添加联系人的输入信息部分的代码。

//修改联系人
void ModifyContact(Contact* pc)
{assert(pc);if (pc->sz == 0){printf("\n无法修改,通讯录为空\n\n");return;}//输入要修改的联系人姓名char name[NAME_MAX];printf("请输入要修改联系人的姓名:>");scanf("%s", name);//查找匹配联系人int ret = FindByName(pc, name);if (ret == -1){printf("\n无此联系人\n\n");return;}//找到了,则修改printf("请输入新的姓名:>");scanf("%s", pc->data[ret].name);printf("请输入年龄:>");scanf("%d", &pc->data[ret].age);printf("请输入性别:>");scanf("%s", pc->data[ret].sex);printf("请输入电话:>");scanf("%s", pc->data[ret].tele);printf("请输入地址:>");scanf("%s", pc->data[ret].addr);printf("\n修改成功\n\n");
}

在这里插入图片描述
在这里插入图片描述

2.3.7、CleanContact( )清空联系人

清空通讯录,跟初始化通讯录类似,信息重新清零或者复位即可。

//清空通讯录
void CleanContact(Contact* pc)
{assert(pc);pc->sz = 0;pc->capacity = 0;free(pc->data);pc->data = NULL;//pc->data = 0;printf("\n已清空通讯录\n\n");
}

在这里插入图片描述
在这里插入图片描述

2.3.8、SortContact( )排序联系人

排序联系人主要结合了前面篇章的qsort函数的功能,这里完成的是以姓名排序。

//排序通讯录
//void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
static cmp_by_name(const void* e1,const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}void SortContact(Contact* pc)
{assert(pc);if (pc->sz == 0){printf("\n无法排序,通讯录为空\n\n");return;}//排序联系人qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);printf("\n排序成功\n\n");
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、结语

相信通过这样一个简易通讯录的实现,更具掌握了对数组、动态内存管理的操作以及对自定义函数的深刻认识;
如果觉着文章对您有所帮助,请不要吝啬的一赞三连哦,谢谢阅读,不足之处还请多多指教。

半亩方糖一鉴开,天光云影共徘徊。
问渠哪得清如许?为有源头活水来。–朱熹(观书有感)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模式识别——高斯分类器

模式识别——高斯分类器 需知定义特殊情况&#xff08;方差一致&#xff09;Sigmoid 需知 所有问题定义在分类问题下&#xff0c;基于贝叶斯决策 定义 条件概率为多元高斯分布&#xff0c;此时观测为向量 X X 1 , X 2 , . . . , X n X{X_1,X_2,...,X_n} XX1​,X2​,...,Xn​…

Docker Service 创建

Docker Swarm Mode Docker Swarm 集群搭建 Docker Swarm 节点维护 Docker Service 创建 service 只能依附于 docker swarm 集群&#xff0c;所以 service 的创建前提是&#xff0c;swarm 集群搭建完毕。 1. 创建 service docker service create 命令用于创建 service&#xff…

【C++项目】高并发内存池第二讲中心缓存CentralCache框架+核心实现

CentralCache 1.框架介绍2.核心功能3.核心函数实现介绍3.1SpanSpanList介绍3.2CentralCache.h3.3CentralCache.cpp3.4TreadCache申请内存函数介绍3.5慢反馈算法 1.框架介绍 回顾一下ThreadCache的设计&#xff1a; 如图所示&#xff0c;ThreadCache设计是一个哈希桶结构&…

前端领域的插件式设计

插件&#xff0c;是一个常见的概念。 例如&#xff0c;当我们需要把我们前端代码中的 css 样式提取打包&#xff0c;我们可以用 webpack 的 mini-css-extract-plugin&#xff0c;或者你如果用 rollup 的话&#xff0c;可以选择 rollup-plugin-postcss。 再比如我们可以给 bab…

RDB.js:适用于 Node.js 和 Typescript 的终极对象关系映射器

RDB.js 是适用于 Node.js 和 Typescript 的终极对象关系映射器&#xff0c;可与 Postgres、MS SQL、MySQL、Sybase SAP 和 SQLite 等流行数据库无缝集成。无论您是使用 TypeScript 还是 JavaScript&#xff08;包括 CommonJS 和 ECMAScript&#xff09;构建应用程序&#xff0c…

X32位汇编和X64位区别无参函数分析(一)

前言 一、X32汇编函数无参无返回分析 二、X64汇编函数无参无返回分析 总结 前言 提示&#xff1a;以下是个人学习总结&#xff1a;如有错误请大神指出来&#xff0c;只供学习参考&#xff0c;本内容使用使用VS2017开发工具&#xff1a;语言是C&#xff0c;需要一些常见的汇编指…

MySQL1——喵喵期末不挂科

宝宝&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重要…

阶段六-Day04-MyBatis2

一、别名 Alias 1. 为什么使用别名 一般映射文件中会包含大量<select>标签, 每个<select>中都需要配置resultType"com.bjsxt.pojo.People"&#xff0c;MyBatis提供了别名机制可以对某个类起别名或给某个包下所有类起别名&#xff0c;简化resultType取值…

sklearn-6算法链与管道

思想类似于pipeline&#xff0c;将多个处理步骤连接起来。 看个例子&#xff0c;如果用MinMaxScaler和训练模型&#xff0c;需要反复执行fit和tranform方法&#xff0c;很繁琐&#xff0c;然后还要网格搜索&#xff0c;交叉验证 1 预处理进行参数选择 对于放缩的数据&#x…

性能测试 —— Jmeter 命令行详细

我们在启动Jmeter时 会看见&#xff1a;Don’t use GUI mode for load testing !, only for Test creation and Test debugging.For load testing, use CLI Mode (was NON GUI) 这句话的意思就是说&#xff0c;不要使用gui模式进行负载测试&#xff0c;gui模式仅仅是创建脚本…

Unity 通过jar包形式接入讯飞星火SDK

最近工作上遇到了要接入gpt相关内容的需求&#xff0c;简单实现了一个安卓端接入讯飞星火的UnitySDK。 或者也可以接入WebSocket接口的。本文只讲安卓实现 我使用的Unity版本为2021.3.27f1c2 Android版本为4.2.2 1.下载SDK 登陆讯飞开放平台下载如图所示SDK 2.新建安卓工程…

【广州华锐互动】关于物理力学的3D实验实操平台

在科学的广阔领域中&#xff0c;物理力学是一个至关重要的分支&#xff0c;它探索了物体在力作用下的运动规律。然而&#xff0c;传统的物理实验往往需要复杂的设备和大量的操作&#xff0c;这对于学生来说是一项巨大的挑战。为了解决这个问题&#xff0c;广州华锐互动开发了物…

django 商品及购物车逻辑实现

基于类视图模式实现商品分类菜单接口开发 创建菜单子应用 python manage.py startapp menu测试 apps/menu/views from django.http import HttpResponse from django.views import Viewclass GoodsMainMenu(View):def get(self,request):print("get请求")return …

二叉搜索树进阶--AVL树详细实现过程

目录 AVL树概念AVL树实现AVL树基础结构插入插入&#xff1a;左旋实现插入&#xff1a;右旋实现 AVL树完整实现代码&#xff1a; 之前学习到的二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中…

实现Traefik工具Dashboard远程访问:搭建便捷的远程管理平台

文章目录 前言1. Docker 部署 Trfɪk2. 本地访问traefik测试3. Linux 安装cpolar4. 配置Traefik公网访问地址5. 公网远程访问Traefik6. 固定Traefik公网地址 前言 Trfɪk 是一个云原生的新型的 HTTP 反向代理、负载均衡软件&#xff0c;能轻易的部署微服务。它支持多种后端 (D…

HTML笔记-狂神

1. 初识HTML 什么是HTML&#xff1f; Hyper Text Markup Language : 超文本标记语言 超文本包括&#xff1a;文字、图片、音频、视频、动画等 目前使用的是HTML5&#xff0c;使用 W3C标准 W3C标准包括&#xff1a; 结构化标准语言&#xff08;HTML、XML&#xff09; 表现标…

大二第三周总结(算法+生活)

算法&#xff1a; 题目&#xff1a;有效的括号 这个题目也是做过很多回了。主要就是数据结构中”栈“的应用&#xff0c;先进后出。 解题思路&#xff1a; 1.创建 Map 哈希表形成键值对映射 2.进行遍历字符串 在遍历过程中 如果 遍历到的字符c 是左括号&#xff0c;则入栈 pu…

大小端字节序存储

大小端字节序存储&#xff1a;是以字节为单位讨论它在内存中的存储顺序&#xff0c;而不是更小的二进制位 例如&#xff1a; int main() {int a 0x11223344;return 0; }a在内存中的存储16进制为44 33 22 11&#xff0c;两个16进制为一个单位进行存储&#xff0c;而两个十六进…

Leetcode—260.只出现一次的数字III【中等】

2023每日刷题&#xff08;三&#xff09; Leetcode—260.只出现一次的数字III 借助lowbit的解题思想 参考的灵茶山艾府大神的题解 实现代码 /*** Note: The returned array must be malloced, assume caller calls free().*/ int* singleNumber(int* nums, int numsSize, in…

git rebase 和 git merge的区别?

一、是什么 在使用 git 进行版本管理的项目中&#xff0c;当完成一个特性的开发并将其合并到 master 分支时&#xff0c;会有两种方式&#xff1a; git mergegit rebase git rebase 与 git merge都有相同的作用&#xff0c;都是将一个分支的提交合并到另一分支上&#xff0c…