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


    通讯录管理系统项目简介

    功能说明

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

    程序界面

    在这里插入图片描述

    代码实现

    多文件实现

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

    项目逻辑

    在这里插入图片描述

    头文件部分

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

    //↓↓↓↓↓引入要用的头文件↓↓↓↓↓
    #include 
    #include//清屏函数的头文件
    #include 
    
    
    //↓↓↓↓↓使用到的宏定义↓↓↓↓↓
    #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是为了后面打印显示通讯录方便简洁,避免出现同一段代码重复出现多次的情况
    //避免代码冗余
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

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

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

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

    //↓↓↓↓↓函数声明↓↓↓↓↓
    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);//清空通讯录
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    内部函数实现

    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    之中还使用了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");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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


    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");
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

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

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

    主程序代码

    #include "Address_Book.h"//包含自己写的头文件
    
    int main()
    {
    	int input = 0;
    	struct Txl txl;
    	initiate(&txl);
    	do 
    	{
    		menu();
    		printf("请选择你要进行的操作->");
    		scanf("%d",&input);
    		switch (input)
    		{
    		case 1:
    			//Add
    			Add(&txl);
    			break;
    		case 2:
    			//Show
    			Show(&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:
    			//Exit
    			printf("退出系统~~~\n");
    			break;
    		default:
    			printf("选择错误,请输入0~6 的数字!\n");
    			break;
    		}
    	} while (input);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    思考和总结

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

    1. 以上这一些函数可以用转移表(函数指针进行优化),他们的参数和返回值类型都一致
    2. 后面写代码也发现了,遍历结构体数组,然后用strcmp库函数进行字符串匹配的这些代码多次出现,很冗余,可以封装成一个函数
    3. 删除联系人和清空联系人的操作并不是真正意义上的清除了数据和所占用的空间,后续可以使用动态内存相关知识进行优化
    4. 可以给通讯录增加一个排序功能,按名字,按年龄等
    5. 修改联系人方面可以优化,具体修改什么属性的功能
    6. 链表实现?
  • 相关阅读:
    下载JDK8 JVM源码
    【c++】weak_ptr和观察者模式
    BIOS主板(非UEFI)安装fedora40的方法
    强化学习之Dueling DQN对DQN的改进——以倒立摆环境(Inverted Pendulum)为例
    Android修行手册 - 模板匹配函数matchTemplate详解,从N张图片中找到是否包含五星
    图之最小生成树Prim算法详解(C语言版)
    djangorestframework-simplejwt
    【Linux】centos创建用户以及赋予sudo权限
    .NET 6应用程序适配国产银河麒麟V10系统随记
    mysql 在eclipse在配置
  • 原文地址:https://blog.csdn.net/yjagks/article/details/133035443