C语言——通讯录管理系统

通讯录管理系统项目简介

功能说明

  1. 控制台黑窗口实现
  2. 程序需要满足以下几个功能
    在这里插入图片描述
  3. 程序开始运行时首先显示选择菜单界面,根据用户输入确定实现何种功能

程序界面

在这里插入图片描述

代码实现

多文件实现

和之前写的实战项目类似,这里同样采用多文件实现的方式
多文件写代码的方式可以让我们的写的代码的逻辑结构更加清晰,一个项目多个文件实现的形式同时也符合实际工作中一个项目的实现过程,有利于我们养成良好的编程习惯。
在这里插入图片描述
Address_Book.h:内包含项目用到的所有头文件和函数声明,以及一些宏定义和结构体声明等
Address_Book.c:这个.c文件是用来实现项目中大部分基本函数的(不包含main函数的实现)
test.c:项目主函数文件,项目主要逻辑实现(包含main函数)

项目逻辑

在这里插入图片描述

头文件部分

包含项目所要引用到的所有头文件,和一些宏定义

//↓↓↓↓↓引入要用的头文件↓↓↓↓↓
#include <stdio.h>
#include<stdlib.h>//清屏函数的头文件
#include <string.h>//↓↓↓↓↓使用到的宏定义↓↓↓↓↓
#define MAX_NUM 100//通讯录最多存储100个联系人
#define FORMAT "%-10s %-10s %-10d %-25s %-30s\n"
#define DATA ptxl->peoples[i].name, ptxl->peoples[i].sex, ptxl->peoples[i].age, ptxl->peoples[i].phoneNumber, ptxl->peoples[i].address
//这里定义FORMAT和DATA是为了后面打印显示通讯录方便简洁,避免出现同一段代码重复出现多次的情况
//避免代码冗余

通讯录管理系统的实现是基于结构体和结构体数组的。描述一个联系人需要使用到多种类型的数据,这就要定义一个描述单个联系人的结构体,代码如下:

//创建联系人结构体
struct People
{char name[20];char sex[4];int age;char phoneNumber[12];//电话号码一般是11位数,后面加一位'\0'char address[30];
};

但是,描述一个通讯录的多个联系人,需要一个结构体数组,同时为了更好地统计通讯录中记录的联系人个数,也需要一个整型变量count,添加一个联系人,count加一,删除一个联系人,count减一;为了实现count和通讯录(结构体数组)之间的绑定关系,这里有定义了一个通讯录的结构体,结构体成员一个是存储联系人的信息的结构体数组,一个是统计联系人个数的整型元素count。

//创建通讯录,也就是联系人数组,最大容量为MAX_NUM,宏定义为100
struct Txl
{struct People peoples[MAX_NUM];int count ;
};

同时头文件也包含项目中函数的声明

//↓↓↓↓↓函数声明↓↓↓↓↓
void menu();//菜单函数
void initiate(struct Txl *ptxl); //初始化通讯录总数为0
void Add(struct Txl* ptxl);//添加联系人的函数
void Show(struct Txl* ptxl);//显示联系人的函数
void Find(struct Txl* ptxl);//查找联系人的函数
void Change(struct Txl* ptxl);//修改联系人的函数
void Delete(struct Txl* ptxl);//删除联系人的函数
void Clear(struct Txl* ptxl);//清空通讯录

这里说明一点,这些函数的参数都是结构体指针类型的,而不是结构体。
是因为结构体传参的时候,建议传结构体的地址。
函数在传参的时候,参数是需要压栈的,会有时间和空间上的系统开销;如果传递的是一个结构体对象的时候,结构体对象过大,参数压栈的系统开销就会比较大,会程序导致性能的下降。

内部函数实现

menu()函数的实现

menu函数的实现比较简单,主要是printf函数,代码如下:

//打印选择菜单
void menu()
{printf("********************************\n");printf("********  1.添加联系人  ********\n");printf("********  2.显示联系人  ********\n");printf("********  3.删除联系人  ********\n");printf("********  4.查找联系人  ********\n");printf("********  5.修改联系人  ********\n");printf("********  6.清空联系人  ********\n");printf("********  0.退出通讯录  ********\n");printf("********************************\n");
}

Add(struct Txl *ptxl)函数的实现

Add函数主要就是结构体数组的访问操作了,但是在这之前要先判断一下通讯录有没有满,也就是判断通讯录中的count成员的数值是不是等于MAX_NUM(定义的通讯录的最大容量),如果是,输出提示语,如果不是,则再进行结构体数组中单个结构体成员的访问,代码如下:

//添加联系人
void Add(struct Txl* ptxl)
{if (ptxl->count == MAX_NUM){printf("通讯录已满!不能再添加联系人了~\n");}else{//添加姓名printf("姓名:");scanf("%s", ptxl->peoples[ptxl->count].name);//添加性别printf("性别(男 或 女):");scanf("%s", ptxl->peoples[ptxl->count].sex);//添加年龄printf("年龄:");scanf("%d", &ptxl->peoples[ptxl->count].age);//这里要取地址操作符!!!//添加联系电话printf("联系电话:");scanf("%s", ptxl->peoples[ptxl->count].phoneNumber);//添加地址printf("地址:");scanf("%s", ptxl->peoples[ptxl->count].address);(ptxl->count)++;printf("添加联系人成功!\n");}
}

Show(struct Txl *ptxl)函数的实现

Show函数的实现也比较简单,循环访问并打印结构体数组中的成员就好,循环的条件是小于通讯录结构体中的count变量的值,代码如下:

//显示联系人
void Show(struct Txl* ptxl)
{int i = 0;printf("%-10s %-10s %-10s %-25s %-30s\n", "姓名", "性别", "年龄", "联系电话", "地址");for (i = 0; i < (ptxl->count); i++){printf(FORMAT,DATA);}
}

Delete(struct Txl *ptxl)函数的实现

根据用户输入的姓名信息删除结构体数组中的指定联系人

  1. 首先,定义一个字符类型的数组,接收用户的输入的姓名信息
  2. 然后,遍历结构体数组的每一个元素的name成员
  3. 用strcmp字符串比较函数,对用户输入和结构体数组的每一个元素的name成员进行比较,返回值用ret接收
  4. 返回值为0,则进行删除操作(就是把结构体数组成员从当前位置开始,把后一个元素赋值给前一个元素,直到循环遍历完整个结构体数组)简单来说就是用后面的元素覆盖前面的元素
  5. 接着把描述通讯录联系人总数的count进行减一操作

但是这里结构体数组中的最后一个元素并没有被覆盖但是也没有被删除,但是因为count的值进行了减一操作,所以后面打印结构体数组的时候,虽然最后一个元素没有被覆盖没有被删除,但是也不会打印出来。

代码如下:

//删除联系人
void Delete(struct Txl* ptxl)
{char input[20] = {0};int i = 0;int flag = 0;printf("请输入你要删除的联系人姓名:");scanf("%s",input);for (i = 0; i < ptxl->count; i++){int ret = strcmp(input,ptxl->peoples[i].name);if (ret == 0){flag = 1;int j = 0;int k = i;for (j = 0; j <(ptxl->count) - i-1; j++){ptxl->peoples[k] = ptxl->peoples[k+1];k++; }printf("删除联系人成功~\n");ptxl->count--;break;}}if (flag != 1){printf("没有找到此联系人!\n");}
}

之中还使用了flag来标记字符串是否匹配成功,如果成功就进行删除操作,并跳出循环,否则输出提示。

Find(struct Txl *ptxl)函数的实现

Find函数的实现和Delete函数类似,也是遍历结构体数组,用strcmp函数进行匹配,匹配到了就进行打印输出,没匹配到就输出提示

//查找联系人
void Find(struct Txl* ptxl)
{char input[20] = { 0 };printf("请输入你要查找的联系人的姓名:");scanf("%s", &input);int i = 0;int flag = 0;//定义一个标志,找到了置为1;for (i = 0; i < ptxl->count; i++){int ret = strcmp(ptxl->peoples[i].name, input);if (ret == 0){printf("查找成功,该联系人相关信息如下↓↓↓\n");printf("%-10s %-10s %-10s %-25s %-30s\n", "姓名", "性别", "年龄", "联系电话", "地址");printf(FORMAT, DATA);flag = 1;break;}}if (flag == 0){printf("查找失败!通讯录中没有此联系人信息!\n");}
}

后面函数的实现都大同小异,框架结构都类似,就不再赘述


Change(struct Txl *ptxl)函数的实现

//修改联系人
void Change(struct Txl* ptxl)
{char input[20] = { 0 };printf("请输入你要修改的联系人的姓名:");scanf("%s", &input);int i = 0;int flag = 0;for (i = 0; i < ptxl->count; i++){int ret = strcmp(ptxl->peoples[i].name, input);if (ret == 0){//姓名printf("姓名:");scanf("%s", ptxl->peoples[i].name);//添加性别printf("性别(男 或 女):");scanf("%s", ptxl->peoples[i].sex);//添加年龄printf("年龄:");scanf("%d", &ptxl->peoples[i].age);//这里要取地址操作符!!!//添加联系电话printf("联系电话:");scanf("%s", ptxl->peoples[i].phoneNumber);//添加地址printf("地址:");scanf("%s", ptxl->peoples[i].address);printf("联系人信息修改成功!\n");flag = 1;break;}}if (flag == 0){printf("此联系人不在通讯录中!无法修改!\n");}
}

Clear(struct Txl *ptxl)函数的实现

void Clear(struct Txl* ptxl)
{ptxl->count = 0;printf("通讯录清空成功~~~\n");//这里只是简单的把结构体txl中的count值设置为0,//这样打印的时候就什么都不会打印,看起来像是清空了通讯录//实际上内存中还是存在数据的,程序结束前并没有把数组中的数据清除//这里具体后面在想办法改善//动态内存管理相关内容
}

主程序代码

#include "Address_Book.h"//包含自己写的头文件int main()
{int input = 0;struct Txl txl;initiate(&txl);do {menu();printf("请选择你要进行的操作->");scanf("%d",&input);switch (input){case 1://AddAdd(&txl);break;case 2://ShowShow(&txl);break;case 3:Delete(&txl);break;case 4:Find(&txl);break;case 5:Change(&txl);break;case 6:Clear(&txl);//这里只是简单的把结构体txl中的count值设置为0,这样打印的时候就什么都不会打印,看起来像是清空了通讯录,实际上数据还是存在数组中的!!break;case 0://Exitprintf("退出系统~~~\n");break;default:printf("选择错误,请输入0~6 的数字!\n");break;}} while (input);return 0;
}

思考和总结

这一部分的代码还是很荣誉

  1. 以上这一些函数可以用转移表(函数指针进行优化),他们的参数和返回值类型都一致
  2. 后面写代码也发现了,遍历结构体数组,然后用strcmp库函数进行字符串匹配的这些代码多次出现,很冗余,可以封装成一个函数
  3. 删除联系人和清空联系人的操作并不是真正意义上的清除了数据和所占用的空间,后续可以使用动态内存相关知识进行优化
  4. 可以给通讯录增加一个排序功能,按名字,按年龄等
  5. 修改联系人方面可以优化,具体修改什么属性的功能
  6. 链表实现?

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

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

相关文章

各种电机驱动原理

步进电机 步进电机参考资料 野火官方文档 步进电机驱动原理 上面参考文档中有的内容就不写了&#xff0c;写一下我自己的总结吧。 说明&#xff1a; 电机驱动器输入信号有电机转动方向信号DIR&#xff0c;电机转速信号PWM&#xff0c;电机使能信号EN&#xff1b;电机驱动器…

S7-1200PLC和LED电子看板通信(TCP/IP)

S7-200SMART PLC和LED电子看板通信应用,请查看下面文章链接: SMART 200 PLC UDP通讯应用LED看板_RXXW_Dor的博客-CSDN博客开放式用户通信 (OUC) 库:数据解析:https://rxxw-control.blog.csdn.net/article/details/121424897这篇博客我们主要介绍S7-1200PLC和LED电子看板通…

servlet 引用src目录下子目录的class文件方法

1、MyServlet class文件所处的目录结构如下&#xff1a; 2、如果在url里直接引用是不行的&#xff0c;http://localhost:9092/GetRequest_Web_exploded/MyServlet 3、需要在web.xml映射后才行&#xff1a; MyServlet com.example.MyServlet <servlet-mapping><ser…

HI_NAS linux 记录

dev/root 100% 占用解决记录 通过下面的命令查看各文件夹 大小 sudo du --max-depth1 -h # 统计当前文件夹下各个文件夹的大小显示为M 最终发现Var/log 占用很大空间 发现下面两个 log 占用空间很大&#xff0c;直接 rm-rf 即可 HI NAS python3 记录 # 安装pip3 sudo apt u…

Linux下C语言使用 netlink sockets与内核模块通信

netlink简介 Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。在Linux标准内核中&#xff0c;系统默认集成了很多netlink实例&#xff0c;比如日志上报、路由系统等&#xff0c;netlink消息是双向的&a…

Knife4j系列--使用方法

原文网址&#xff1a;Knife4j系列--使用/教程/实例/配置_IT利刃出鞘的博客-CSDN博客

python LeetCode 刷题记录 58

题目 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 输入&#xff1a;s "Hello World" 输出&#xff1a;5 解释&am…

Linux中如何获取输入设备(如触摸屏、按键等)的事件信息

Linux中如何获取输入设备&#xff08;如触摸屏、按键等&#xff09;的事件信息 在Linux中&#xff0c;可以使用getevent命令来获取输入设备&#xff08;如触摸屏、按键等&#xff09;的事件信息。如果你想在C程序中获取输入设备事件&#xff0c;可以使用以下步骤&#xff1a; …

【golang】调度系列之P

调度系列 调度系列之goroutine 调度系列之m 在前面两篇中&#xff0c;分别介绍了G和M&#xff0c;当然介绍的不够全面&#xff08;在写后面的文章时我也在不断地完善前面的文章&#xff0c;后面可能也会有更加汇总的文章来统筹介绍GMP&#xff09;。但是&#xff0c;抛开技术细…

代码随想录算法训练营19期第56天

583. 两个字符串的删除操作 代码随想录 初步思路&#xff1a;动态规划。 总结&#xff1a; 【1】第一步先求出两个字符串的最长公共子序列长度。 【2】删除的最少步数 两个字符串的总长度减去两个最长公共子序列的长度。 用时&#xff1a;45分钟 72. 编辑距离 代码随…

移动端APP测试-如何指定测试策略、测试标准?

制定项目的测试策略是一个重要的步骤&#xff0c;可以帮助测试团队明确测试目标、测试范围、测试方法、测试资源、测试风险等&#xff0c;从而提高测试效率和质量。本篇是一些经验总结&#xff0c;理论分享。并不是绝对正确的&#xff0c;也欢迎大家一起讨论。 文章目录 一、测…

使用Linkerd实现流量管理:学习如何使用Linkerd的路由规则来实现流量的动态控制

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Mac 错误zsh: command not found: brew解决方法

打开iterm或其他shell终端&#xff0c;执行命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 选择下载brew的源&#xff0c;输入1~6任意都行 根据提示输入Y及开机密码 最后执行&#xff1a;source ~/.z…

封装使用Axios进行前后端交互

Axios是一个强大的HTTP客户端&#xff0c;用于在Vue.js应用中进行前后端数据交互。本文将介绍如何在Vue中使用Axios&#xff0c;并通过一个企业应用场景来演示其实际应用。 Axios简介 公众号&#xff1a;Code程序人生&#xff0c;个人网站&#xff1a;https://creatorblog.cn A…

什么是鉴权?一篇文章带你了解postman的多种方式

一、什么是鉴权&#xff1f; 鉴权也就是身份认证&#xff0c;就是验证您是否有权限从服务器访问或操作相关数据。发送请求时&#xff0c;通常必须包含相应的检验参数以确保请求具有访问权限并返回所需数据。通俗的讲就是一个门禁&#xff0c;您想要进入室内&#xff0c;必须通过…

2023-09-19力扣每日一题

链接&#xff1a; 2560. 打家劫舍 IV 题意 n个数字&#xff0c;相邻不能选&#xff0c;选择的结果为 选中的数字中的最大数字&#xff0c;要求最少选k个数字 求这个结果最小能是多少 解&#xff1a; 怎么就从DP变成二分了呢&#xff1f; 关键字&#xff1a;最大的最小 …

一个Qt鼠标透传场景与事件过滤器的用法

一个Qt鼠标透传场景与事件过滤器的用法 最近工作中遇到一个开发场景&#xff0c;将一个QWidget控件&#xff08;称为控件A&#xff09;放入QScrollArea&#xff0c;该控件A重写了QWidget::wheelEvent&#xff0c;根据鼠标滚轮事件缩放内部的绘制视图。当控件过大时&#xff0c…

【Redis】深入探索 Redis 主从结构的创建、配置及其底层原理

文章目录 前言一、对 Redis 主从结构的认识1.1 什么是主从结构1.2 主从结构解决的问题 二、主从结构创建2.1 配置并建立从节点2.2.1 从节点配置文件2.2.2 启动并连接 Redis 主从节点2.2.3 SLAVEOF 命令2.2.4 断开主从关系 2.2 查看主从节点的信息2.2.1 INFO REPLICATION 命令2.…

(leetcode)单值二叉树

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 题目&#xff1a; 思路&#xff1a; 代码&#xff1a; 画图与分析&#xff1a; 题目&#xff1a; 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&…

树形DP杂题

引 对老师布置的题目稍微记录一下吧 也算对树形 D P DP DP 的巩固 T1 Ostap and Tree 题目传送门 由于有 距离 k 距离k 距离k 的限制&#xff0c;设计二维 d p dp dp 设计状态&#xff1a; f i , j : i 的子树内&#xff0c;离 i 最近的染色点与 i 距离为 j 且若 j <…