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.rhkb.cn/news/136460.html

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

相关文章

day3_QT

day3_QT 1、文件保存2、始终事件 -闹钟 1、文件保存 2、始终事件 -闹钟 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> #include <QTime> #include <QTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { clas…

Qt --- Day03

<?xml version"1.0" encoding"UTF-8"?> <ui version"4.0"><class>Widget</class><widget class"QWidget" name"Widget"><property name"geometry"><rect><x>0…

fatal error: linux/compiler-gcc9.h: No such file or directory

linux 找到README文件 cd /mnt/e/CLionProjects/linux-3.10.99 sudo useradd linux3x sudo passwd linux3x sudo mkdir /home/linux3x sudo chown linux3x:linu3x /home/linux3x sudo chmod 755 /home/linux3x su - linux3x mkdir ~/build mkdir ~/build/kernel exit make O/…

目标检测Neck:FPN(Feature Pyramid Network)与PAN(附torch代码)

文章目录 0. 前言1. FPN1.1 FPN核心思想与步骤1.2 FPN的融合过程2. PAN2.1 PANet2.2 原版2.3 mmdetection中yolo_neck版本2.4 nanodet版本ReferenceFPN和PAN都是用于解决在目标检测中特征金字塔网络(FPN)在多尺度检测任务上的不足的方法。下面分别详细介绍一下它们的原理和区别…

Docker 容器设置为自动重启

Docker自动重启原因 Docker自动重启通常是由以下几个原因导致的&#xff1a; 程序崩溃系统内存不足系统进程使用过多CPU和RAM导致的阻塞docker容器被杀死或重新启动&#xff0c;导致应用程序中断网络中断 当这些问题出现时&#xff0c;Docker会自动重启运行中的服务来尝试解…

malloc与free

目录 前提须知&#xff1a; malloc&#xff1a; 大意&#xff1a; 头文件&#xff1a; 申请空间&#xff1a; 判断是否申请成功&#xff1a; 使用空间&#xff1a; 结果&#xff1a; 整体代码&#xff1a; malloc申请的空间怎么回收呢? 注意事项&#xff1a; free:…

【入门篇】ClickHouse最优秀的开源列式存储数据库

文章目录 一、什么是ClickHouse&#xff1f;OLAP场景的关键特征列式数据库更适合OLAP场景的原因输入/输出CPU 1.1 ClickHouse的定义与发展历程1.2 ClickHouse的版本介绍 二、ClickHouse的主要特性2.1 高性能的列式存储2.2 实时的分析查询2.3 高度可扩展性2.4 数据压缩2.5 SQL支…

PHP自己的框架2.0结合容器技术(重构篇二)

目录 1、使用容器实现框架加载类运行 2、 创建框架容器类core/fm/Di.php 3、框架使用容器类来执行public/index.php 4、运行效果还是一样 1、使用容器实现框架加载类运行 2、 创建框架容器类core/fm/Di.php 什么是容器&#xff1f;容器就相当于盒子&#xff0c;把很多类放里…

Postman应用——控制台调试

当你在测试脚本中遇到错误或意外行为时&#xff0c;Postman控制台可以帮助你识别&#xff0c;通过将console.log调试语句与你的测试断言相结合&#xff0c;你可以检查http请求和响应的内容&#xff0c;以及变量之类的。 通常可以使用控制台日志来标记代码执行&#xff0c;有时…

【分布式】分布式ID

目录 前言一、雪花算法snowflake1. 组成2. 优缺点3. 时钟回拨怎么解决a. 时钟回拨b. 解决方案 4. 项目中如何使用 二、基于Redis三、基于Zookeeper四、号段模式五、指定步长的自增ID六、UUID参考 六、扩展总结 前言 分布式场景下&#xff0c;一张表可能分散到多个数据结点上。因…

【JavaEE】多线程案例-单例模式

文章目录 1. 前言2. 什么是单例模式3. 如何实现单例模式3.1 饿汉模式3.2 懒汉模式4. 解决单例模式中遇到的线程安全问题4.1 加锁4.2 加上一个判断解决频繁加锁问题4.2 解决因指令重排序造成的线程不安全问题 1. 前言 单例模式是我们面试中最常考到的设计模式。什么是设计模式呢…

【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.…

《DevOps实践指南》- 读书笔记(六)

DevOps实践指南 Part 4 第二步 &#xff1a;反馈的技术实践17. 将假设驱动的开发和A/B测试融入日常工作17.1 A/B 测试简史17.2 在功能测试中集成 A/B 测试17.3 在发布中集成 A/B 测试17.4 在功能规划中集成 A/B 测试17.5 小结 18. 建立评审和协作流程以提升当前工作的质量18.1 …

04条件构造器和常用接口

条件构造器和常用接口 wapper介绍 条件构造器的两个条件之间默认就是AND并列关系,如果需要或者的关系则需要调用构造器的or()方法 条件构造器类型作用Wrapper条件构造抽象类,最顶端父类AbstractWrapper生成SQL的where条件QueryWrapper封装查询或删除的条件UpdateWrapper封装修…

小程序自定义tabbar

前言 使用小程序默认的tabbar可以满足常规开发&#xff0c;但是满足不了个性化需求&#xff0c;如果想个性化开发就需要用到自定义tabbar,以下图为例子 一、在app.json配置 先按照以往默认的形式配置&#xff0c;如果中间的样式特殊则不需要配置 "tabBar": {&qu…

社区分享|MeterSphere变身“啄木鸟”,助力云帐房落地接口自动化测试

云帐房网络科技有限公司&#xff08;以下简称为“云帐房”&#xff09;成立于2015年3月&#xff0c;以“成为最值得信赖的税务智能公司”为愿景&#xff0c;运用人工智能、大数据等互联网技术&#xff0c;结合深厚的财税行业服务经验&#xff0c;为代账公司和中大型企业提供智能…

避雷器雷击计数器检验

试验目的 由于密封不良&#xff0c; 放电计数器在运行中可能进入潮气或水分&#xff0c; 使内部元件锈蚀&#xff0c;导致计数器不能正确动作&#xff0c; 因此需定期试验以判断计数器是否状态良好、 能否正常动作&#xff0c; 以便总结运行经验并有助于事故分析。 带有泄漏电…

小程序隐私弹窗的实现

小程序的开发者对于微信官方来说是有爱有恨&#xff0c;三天二头整事是鹅厂的一贯风格。 隐私弹窗的几个要点 回归正题&#xff0c;小程序隐私弹窗的几个要点&#xff1a; 1、何时弹出用户隐私协议的弹窗&#xff1f; 2、是每次进小程序都弹出来吗&#xff1f; 这两个想明…

什么是HTTP状态码?常见的HTTP状态码有哪些?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是HTTP状态码&#xff1f;⭐ 1xx - 信息性状态码⭐ 2xx - 成功状态码⭐ 3xx - 重定向状态码⭐ 4xx - 客户端错误状态码⭐ 5xx - 服务器错误状态码⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前…

1979-2021年地级市空气流通系数数据

1979-2021年地级市空气流通系数数据 1、时间&#xff1a;1979-2021年 2、来源&#xff1a;整理自era-interim 3、范围&#xff1a;367个地级市 4、指标&#xff1a;10米风速、边界层高度、空气流通系数 5、指标解释&#xff1a; 空气流动系数是空气污染的常用工具变量&am…