【C语言项目实战】使用单链表实现通讯录

                                                 

                                               💓 博客主页:倔强的石头的CSDN主页 

                                               📝Gitee主页:倔强的石头的gitee主页

                                                                期待您的关注

目录

一、引言

二、单链表的基本概念

三、通讯录项目的需求分析

四、通讯录的数据结构

五、通讯录的接口

 1.通讯录初始化 / 导入外部数据

2.添加联系人信息

3.删除联系人信息

4.查找联系人信息

5.修改联系人信息

6.展示联系人信息

7.导出数据到文件

8.通讯录销毁

六、主函数中通讯录操作

1.通讯录菜单

2.通讯录人机交互操作

七、各文件的实现代码

SLinkList.h

SLinkList.c

 Contact.h

Contact.c

test.c

八、测试与验证

九、写在最后


一、引言

在数字化时代,通讯录作为我们日常生活中不可或缺的一部分,扮演着记录和管理联系人信息的重要角色。随着智能手机的普及,人们对于通讯录的功能和性能要求也在不断提高。为了更好地满足这些需求,我们有必要对通讯录的实现方式进行深入研究和探索。

在众多的数据结构中,单链表以其独特的优势成为了实现通讯录的一种理想选择。单链表是一种线性数据结构,它通过每个节点中的指针链接在一起,形成一个有序的链表。相比于数组等其他数据结构,单链表在插入、删除操作上具有更高的效率,因为它不需要像数组那样移动大量的元素。此外,单链表在内存使用上也更加灵活,可以根据需要动态地分配和释放内存空间。

因此,本文旨在探讨如何使用单链表来实现一个高效、灵活的通讯录项目。我们将首先介绍单链表的基本概念和基本操作,然后分析通讯录项目的需求,并设计相应的数据结构和接口。接下来,我们将详细实现通讯录类的各个功能,并进行测试和验证。最后,我们将对项目进行总结和反思,并提出改进方向。

通过本文的介绍和实践,读者将能够深入理解单链表在通讯录项目中的应用,掌握使用单链表实现通讯录的基本方法和技巧。同时,本文也为读者提供了一个实际的项目案例,有助于提升读者的编程能力和解决问题的能力。

二、单链表的基本概念

通讯录项目的实现直接借用了单链表实现的头文件SLinkList.h 和 源文件SLinkList.c

   关于单链表的问题请参照前置文章

【数据结构/C语言】单链表的实现-CSDN博客

 对单链表有了深入的理解之后才能更好的实现通讯录项目

三、通讯录项目的需求分析

  1. 能够存储较多的联系人信息,并且能够高效地管理内存,避免不必要的内存浪费
  2. 能够保存用户信息:名字、性别、年龄、电话、地址等
  3. 增加联系⼈信息
  4. 删除指定联系⼈
  5. 查找指定联系⼈
  6. 修改指定联系⼈
  7. 显示联系⼈信息
  8. 实现数据的导入导出

四、通讯录的数据结构

以下是用结构体记录通讯录单个联系人信息的信息,对应单链表单个节点的数据部分的数据类型

#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100typedef struct PersonInfo
{char name[NAME_MAX];//姓名char sex[SEX_MAX];  //性别int age;            //年龄char tel[TEL_MAX];  //电话char addr[ADDR_MAX];//地址
}PeoInfo;

 同时,单链表头文件中对单链表结构的数据部分定义有所修改

typedef PeoInfo SLTDataType;//类型重定义
typedef struct SListNode
{SLTDataType data;//数据struct SListNode* next;//指针
}SLTNode;

 注意:

因为单链表的头文件需要用到通讯录的头文件的联系人结构体定义,所以在单链表头文件中包含了通讯录头文件。

但通讯录头文件中又需要用到单链表中对节点的定义,头文件不能互相包含,所以应当在通讯录头文件中包含一条对单链表的结构体的前置声明

typedef struct SListNode contact;//声明并重命名

 接下来,就可以来实现通讯录项目的方法了

五、通讯录的接口

通讯录的基本方法接口包括

  • 初始化与销毁通讯录
  • 数据的导入导出
  • 对联系人的增删改查
  • 以及展示通讯录中的联系人信息

需要对单链表数据进行修改的函数,应该传址调用,实参传递地址,形参使用二级指针接收 

//初始化通讯录
void InitContact(contact** con);
//添加通讯录数据
void AddContact(contact** con);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);

 1.通讯录初始化 / 导入外部数据

单链表实现的通讯录因为不带有额外的头节点,并且链表每个节点都是独立的,所以初始化不需要额外的操作,只需要从外部文件导入通讯录数据即可

这里将导入数据单独封装成一个函数,以便代码复用

由初始化函数来调用导入数据函数

导入外部数据的函数功能:

  •  以二进制读方式打开文件(注意:以读的方式打开文件,必须保证文件存在,否则会出错
  • 判断是否成功
  • 循环读取数据,每读取一条就尾插到单链表
void LoadContact(contact** con)//导入数据到通讯录
{FILE* pf = fopen("contact.txt", "rb");//以二进制读方式打开文件if (pf == NULL)//判空{perror("fopen");return;}PeoInfo po;while (fread(&po, sizeof(po), 1, pf))//循环读取数据{SLTPushBack(con, po);//尾插到单链表}printf("数据载入成功!\n");fclose(pf);pf = NULL;
}
/初始化通讯录
void InitContact(contact** con)
{LoadContact(con);//导入外部数据
}

2.添加联系人信息

添加联系人信息的函数功能:

  • 创建一个联系人的结构体变量
  • 逐个录入信息至该变量
  • 将该结构体变量和通讯录链表的首节点地址作为参数,一起传给底层的单链表尾插函数
//添加通讯录数据
void AddContact(contact** con)
{assert(con);//二级指针判空PeoInfo po;printf("请按提示输入要添加的联系人信息\n");printf("请输入姓名:\n");scanf("%s", po.name);printf("请输入性别:\n");scanf("%s", po.sex);printf("请输入年龄:\n");scanf("%d", &po.age);printf("请输入电话:\n");scanf("%s", po.tel);printf("请输入地址:\n");scanf("%s", po.addr);SLTPushBack(con, po);//调用单链表函数尾插printf("添加联系人成功\n");}

3.删除联系人信息

删除联系人记录需要封装一个查找联系人函数单独实现查找,无其他功能)

单独的查找函数的函数功能:

  • 这里以姓名作为关键值查找,接收一个链表首地址和关键值信息
  • 遍历链表,如果找到该联系人,返回节点地址
  • 否则返回空指针
//封装的单独查找函数
contact* FindByname(contact*con,char name[])
{assert(con);//二级指针判空contact* pcur = con;//遍历链表的指针while (pcur){if (strcmp(pcur->data.name, name) == 0)//字符串比对return pcur;pcur = pcur->next;}return NULL;
}

 删除联系人记录的函数功能:

  • 录入要删除的联系人姓名
  • 调用封装的查找函数
  • 如果找到,调用单链表实现的删除指定节点函数
  • 如果找不到,报错
//删除通讯录数据
void DelContact(contact** con)
{assert(con&&*con);//二级指针判空,链表判空printf("请输入要删除的联系人姓名:");char name[NAME_MAX];scanf("%s", name);contact* del = FindByname(*con, name);//调用单独的查找函数if (del == NULL){printf("要删除的联系人不存在!\n");return;}SLTErase(con, del);//调用单链表删除指定元素printf("删除联系人成功!\n");}

4.查找联系人信息

查找联系人信息的函数功能:

  • 这里不同于上面的功能单一的查找函数
  • 作用是根据录入的关键值查找并打印该联系人信息或报错
  • 这里依然以姓名作为关键值查找
//查找通讯录数据
void FindContact(contact* con)
{printf("请输入要查找的联系人姓名:");char name[NAME_MAX];scanf("%s", name);contact* pcur = FindByname(con, name);//调用已经实现的查找函数if (pcur == NULL){printf("要查找的联系人不存在!\n");return;}printf("%-10s%-10s%-10s%-10s%-10s\n","姓名", "性别", "年龄", "电话", "地址");//打印该联系人信息printf("%-10s%-10s%-10d%-10s%-10s\n",pcur->data.name,pcur->data.sex,pcur->data.age,pcur->data.tel,pcur->data.addr);
}

5.修改联系人信息

修改联系人信息的函数功能:

  • 依然以姓名作为关键值(因为可以重复利用封装的查找函数)
  • 录入姓名,调用单独的查找函数
  • 若找到指定联系人依次修改该联系人的各部分信息,赋值给该节点
  • 否则,该联系人不存在,报错
//修改通讯录数据
void ModifyContact(contact** con)
{assert(con);printf("请输入要修改的联系人姓名:");char name[NAME_MAX];scanf("%s", name);contact* pcur = FindByname(*con, name);//调用已经实现的查找函数if (pcur == NULL){printf("要修改的联系人不存在!\n");return;}printf("请输入修改后的联系人姓名: ");scanf("%s", pcur->data.name);printf("请输入修改后的联系人性别: ");scanf("%s", pcur->data.sex);printf("请输入修改后的联系人年龄: ");scanf("%d", &pcur->data.age);printf("请输入修改后的联系人电话: ");scanf("%s", pcur->data.tel);printf("请输入修改后的联系人地址: ");scanf("%s", pcur->data.addr);printf("修改联系人信息成功!\n");
}

6.展示联系人信息

展示联系人信息的函数功能:

  • 先打印表头信息
  • 创建一个遍历链表的指针
  • 逐个访问链表的每一个节点,每行打印该节点的数据部分
//展示通讯录数据
void ShowContact(contact* con)
{if (con == NULL)//对空链表的特殊处理{printf("NULL\n");return;}printf("%-10s%-10s%-10s%-10s%-10s\n","姓名", "性别", "年龄", "电话", "地址");//表头contact* pcur = con;while (pcur)//遍历链表,打印每个节点的联系人信息{printf("%-10s%-10s%-10d%-10s%-10s\n",pcur->data.name,pcur->data.sex,pcur->data.age,pcur->data.tel,pcur->data.addr);pcur = pcur->next;}
}

7.导出数据到文件

数据导出的函数功能:

  • 以二进制写方式打开文件
  • 循环遍历链表,将每个节点的数据(每个联系人信息)输出到外部文件
//导出数据
void SaveData(contact* con)
{FILE* pf = fopen("contact.txt", "wb");//以二进制写方式打开文件if (pf == NULL){perror("fopen\n");exit(1);}contact* pcur = con;while (pcur)//遍历链表,将通讯录数据输出到文件中{fwrite(pcur, sizeof(contact), 1, pf);pcur = pcur->next;}fclose(pf);free(pf);pf = NULL;
}

8.通讯录销毁

通讯录销毁的函数功能:

  • 销毁之前需要先调用导出数据的函数,将联系人信息保存下来
  • 调用单链表中已实现的链表销毁函数——循环遍历链表,释放每一个动态申请空间的节点
//销毁通讯录数据
void DestroyContact(contact** con)
{SaveData(*con);//调用函数导出数据到文件SListDesTroy(con);//调用单链表函数销毁通讯录
}

六、主函数中通讯录操作

1.通讯录菜单

菜单函数的功能:

  • 封装一个函数向用户展示通讯录项目的功能以及每项功能对应的选项
void menu()
{printf("\n######################################\n");printf("###########--——通讯录菜单——--#########\n");printf("#####1.添加联系人   2.删除联系人######\n");printf("#####3.修改联系人   4.查找联系人######\n");printf("#########  5.展示全部联系人 ##########\n");printf("#########  0.退出通讯录程序 ##########\n");printf("######################################\n\n");
}

2.通讯录人机交互操作

通讯录人机交互部分功能:

  • 首先主函数运行,初始化通讯录,从外部载入数据
  • 然后通过一个 do while循环(保证程序至少运行一次)和 swtich语句(用户选择指定选项对应指定的功能)配合完成对通讯录的操作
  • 最后,用户选择结束操作后,将数据保存至外部文件,销毁通讯录
contact* con = NULL;
InitContact(&con);//初始化
int op = 0;//选项
do {menu();printf("请选择您的操作:  ");scanf("%d", &op);switch (op){case 1:AddContact(&con);//添加break;case 2:DelContact(&con);//删除break;case 3:ModifyContact(&con);//修改break;case 4:FindContact(con);//查找break;case 5:ShowContact(con);//展示break;case 0:printf("退出通讯录!\n");break;default:printf("您选择的数字有误,请重新输入;\n");break;}} while (op);
DestroyContact(&con);//销毁

七、各文件的实现代码

单链表中已有文件——

SLinkList.h

单链表结构定义及函数声明头文件

//SLinkList.h
#define _CRT_SECURE_NO_WARNINGS 1
//单链表结构定义及函数声明头文件
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include"Contact.h"
#include<string.h>
typedef PeoInfo SLTDataType;//类型重定义
typedef struct SListNode
{SLTDataType data;//数据struct SListNode* next;//指针
}SLTNode;void SLTPrint(SLTNode* phead);//链表打印SLTNode* NewNode(SLTDataType x);//申请节点//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);

SLinkList.c

单链表方法实现源文件

//SLinkList.c
//单链表方法实现源文件
#include"SLinkList.h"
//void SLTPrint(SLTNode* phead)//链表打印
//{
//	SLTNode* pcur = phead;
//	while (pcur)//pcur!=NULL
//	{
//		printf("%d->", pcur->data);//打印该节点数据
//		pcur = pcur->next;//指针指向下一个节点
//	}
//	printf("NULL\n");
//}SLTNode* NewNode(SLTDataType x)//申请节点
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL)//注意这里不要写成一个={perror("newnode");exit(1);//如果申请节点失败,异常退出程序}newnode->data = x; //数据初始化newnode->next = NULL;//指针初始化return newnode;//返回新申请节点的地址
}//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{assert(pphead);//二级指针不能为空,否则解引用就会报错SLTNode* newnode = NewNode(x);if (*pphead == NULL){*pphead = newnode;//如果链表为空,新节点即为第一个节点}else{SLTNode* pcur = *pphead;while (pcur->next!=NULL)//找到链表的尾节点{pcur = pcur->next;}pcur->next = newnode;//如果不对空链表分别处理}						//此处就会对空指针解引用
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{assert(pphead);//二级指针不能为空,否则解引用就会报错SLTNode* newnode = NewNode(x);newnode->next = *pphead;//新节点next指针指向原来的首节点*pphead = newnode;//新节点成为首节点
}void SLTPopBack(SLTNode** pphead)//尾删
{assert(pphead && *pphead);//二级指针不能为空,链表不能为空if ((*pphead)->next == NULL)//处理链表只有一个节点的情况{free(*pphead);*pphead = NULL;}else//处理正常情况{SLTNode* pcur = *pphead;//找到指针的最后一个节点SLTNode* prev = *pphead;//找到指针的倒数第二个节点while (pcur->next != NULL){prev = pcur;pcur = pcur->next;}free(pcur);pcur = NULL;prev->next = NULL;//如果不做特殊处理,此处就会对空指针解引用}
}void SLTPopFront(SLTNode** pphead)//头删
{assert(pphead && *pphead);//二级指针不能为空,链表不能为空SLTNode* next = (*pphead)->next;//存储第二个节点free(*pphead);//删除第一个节点*pphead = next;//链表指向第二个节点
}//查找
//SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
//{
//	SLTNode* pcur = phead;
//	while (pcur)
//	{
//		if (pcur->data == x)
//			return pcur;//如果找到,返回节点地址
//	}
//	return NULL;//对未找到的情况和链表为空的情况都可以处理
//}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);//二级指针不能为空,链表不能为空assert(pos);//指定位置必须存在SLTNode* newnode = NewNode(x);if (*pphead == pos)//如果要插入到第一个节点之前{SLTPushFront(pphead, x);//可以直接调用头插}else{SLTNode* prev = *pphead;while (prev->next != pos)//需要先找到pos的前一个节点{						//如果不分别处理,最后就会对空指针解引用prev = prev->next;}newnode->next = pos;//新节点指向posprev->next = newnode;//原pos的前一个节点指向新节点}}//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);//pos节点必须存在SLTNode* newnode = NewNode(x);newnode->next = pos->next;//新节点的next指针指向原pos的下一个节点pos->next = newnode;//pos的next指针指向新节点
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && *pphead);//二级指针不能为空,链表不能为空assert(pos);//指定位置必须存在if (pos == *pphead)//如果删除的是首节点,或链表只有一个节点{SLTPopFront(pphead);}else//链表有多个节点,且删除的不是首节点{SLTNode* prev = *pphead;while (prev->next != pos)//如果不分开处理,这里就可能对空指针解引用{prev = prev->next;}prev->next = pos->next;//找到前一个指针,并将其next指针指向pos的next指针所指节点free(pos);pos = NULL;}
}//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);//pos节点必须存在且pos之后必须存在节点SLTNode* del = pos->next;//预先存储要删除的节点,防止修改指针之后找不到要删除的节点pos->next = del->next;free(del);del = NULL;
}//销毁链表
void SListDesTroy(SLTNode** pphead)
{assert(pphead);//二级指针不能为空,链表不能为空SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;//需要预先存储当前要删除节点的下一个节点free(pcur);					//否则就找不到下一个节点了pcur = next;}*pphead = NULL;//删除完所有节点之后,链表置空
}

 Contact.h

通讯录结构定义及函数声明头文件

//Contact.h
#pragma once
#define NAME_MAX 100
#define SEX_MAX 4
#define TEL_MAX 11
#define ADDR_MAX 100//前置声明
typedef struct SListNode contact;//声明并重命名//用户数据
typedef struct PersonInfo
{char name[NAME_MAX];char sex[SEX_MAX];int age;char tel[TEL_MAX];char addr[ADDR_MAX];
}PeoInfo;//初始化通讯录
void InitContact(contact** con);
//添加通讯录数据
void AddContact(contact** con);
//删除通讯录数据
void DelContact(contact** con);
//展示通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact** con);
//销毁通讯录数据
void DestroyContact(contact** con);

Contact.c

通讯录方法实现源文件

//Contact.c
#include"Contact.h"
#include"SLinkList.h"
//导入数据到通讯录
void LoadContact(contact** con)
{FILE* pf = fopen("contact.txt", "rb");//以二进制读方式打开文件if (pf == NULL)//判空{perror("fopen");return;}PeoInfo po;while (fread(&po, sizeof(po), 1, pf))//循环读取数据{SLTPushBack(con, po);//尾插到单链表}printf("数据载入成功!\n");fclose(pf);pf = NULL;
}
//初始化通讯录
void InitContact(contact** con)
{LoadContact(con);//导入外部数据
}
//添加通讯录数据
void AddContact(contact** con)
{assert(con);//二级指针判空PeoInfo po;printf("请按提示输入要添加的联系人信息\n");printf("请输入姓名:\n");scanf("%s", po.name);printf("请输入性别:\n");scanf("%s", po.sex);printf("请输入年龄:\n");scanf("%d", &po.age);printf("请输入电话:\n");scanf("%s", po.tel);printf("请输入地址:\n");scanf("%s", po.addr);SLTPushBack(con, po);//调用单链表函数尾插printf("添加联系人成功\n");}//封装的单独查找函数
contact* FindByname(contact*con,char name[])
{assert(con);//二级指针判空contact* pcur = con;//遍历链表的指针while (pcur){if (strcmp(pcur->data.name, name) == 0)//字符串比对return pcur;pcur = pcur->next;}return NULL;
}//删除通讯录数据
void DelContact(contact** con)
{assert(con&&*con);//二级指针判空,链表判空printf("请输入要删除的联系人姓名:");char name[NAME_MAX];scanf("%s", name);contact* del = FindByname(*con, name);//调用单独的查找函数if (del == NULL){printf("要删除的联系人不存在!\n");return;}SLTErase(con, del);//调用单链表删除指定元素printf("删除联系人成功!\n");}//展示通讯录数据
void ShowContact(contact* con)
{if (con == NULL)//对空链表的特殊处理{printf("NULL\n");return;}printf("%-10s%-10s%-10s%-10s%-10s\n","姓名", "性别", "年龄", "电话", "地址");//表头contact* pcur = con;while (pcur)//遍历链表,打印每个节点的联系人信息{printf("%-10s%-10s%-10d%-10s%-10s\n",pcur->data.name,pcur->data.sex,pcur->data.age,pcur->data.tel,pcur->data.addr);pcur = pcur->next;}
}//查找通讯录数据
void FindContact(contact* con)
{printf("请输入要查找的联系人姓名:");char name[NAME_MAX];scanf("%s", name);contact* pcur = FindByname(con, name);//调用已经实现的查找函数if (pcur == NULL){printf("要查找的联系人不存在!\n");return;}printf("%-10s%-10s%-10s%-10s%-10s\n","姓名", "性别", "年龄", "电话", "地址");//打印该联系人信息printf("%-10s%-10s%-10d%-10s%-10s\n",pcur->data.name,pcur->data.sex,pcur->data.age,pcur->data.tel,pcur->data.addr);
}//修改通讯录数据
void ModifyContact(contact** con)
{assert(con);printf("请输入要修改的联系人姓名:");char name[NAME_MAX];scanf("%s", name);contact* pcur = FindByname(*con, name);//调用已经实现的查找函数if (pcur == NULL){printf("要修改的联系人不存在!\n");return;}printf("请输入修改后的联系人姓名: ");scanf("%s", pcur->data.name);printf("请输入修改后的联系人性别: ");scanf("%s", pcur->data.sex);printf("请输入修改后的联系人年龄: ");scanf("%d", &pcur->data.age);printf("请输入修改后的联系人电话: ");scanf("%s", pcur->data.tel);printf("请输入修改后的联系人地址: ");scanf("%s", pcur->data.addr);printf("修改联系人信息成功!\n");
}//导出数据
void SaveData(contact* con)
{FILE* pf = fopen("contact.txt", "wb");//以二进制写方式打开文件if (pf == NULL){perror("fopen\n");exit(1);}contact* pcur = con;while (pcur)//遍历链表,将通讯录数据输出到文件中{fwrite(pcur, sizeof(contact), 1, pf);pcur = pcur->next;}fclose(pf);free(pf);pf = NULL;
}//销毁通讯录数据
void DestroyContact(contact** con)
{SaveData(*con);//调用函数导出数据到文件SListDesTroy(con);//调用单链表函数销毁通讯录
}

test.c

主函数测试文件

#include"Contact.h"
#include"SLinkList.h"
void menu()
{printf("\n######################################\n");printf("###########--——通讯录菜单——--#########\n");printf("#####1.添加联系人   2.删除联系人######\n");printf("#####3.修改联系人   4.查找联系人######\n");printf("#########  5.展示全部联系人 ##########\n");printf("#########  0.退出通讯录程序 ##########\n");printf("######################################\n\n");
}
int main()
{//test1();contact* con = NULL;InitContact(&con);//初始化int op = 0;//选项do {menu();printf("请选择您的操作:  ");scanf("%d", &op);switch (op){case 1:AddContact(&con);//添加break;case 2:DelContact(&con);//删除break;case 3:ModifyContact(&con);//修改break;case 4:FindContact(con);//查找break;case 5:ShowContact(con);//展示break;case 0:printf("退出通讯录!\n");break;default:printf("您选择的数字有误,请重新输入;\n");break;}} while (op);DestroyContact(&con);//销毁return 0;
}

八、测试与验证

九、写在最后

本文所有代码已经过多轮测试,可以直接复制使用。

如果您发现某处代码存在问题以及对程序的改进意见,欢迎私信或评论指点。

 💓 博客主页:倔强的石头的CSDN主页

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

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

相关文章

2010-2024年别克维修手册和电路图线路接线图资料更新

经过整理&#xff0c;2010-2024年别克汽车全系列已经更新至汽修帮手资料库内&#xff0c;覆盖市面上99%车型&#xff0c;包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对照表…

Qt 界面上控件自适应窗体大小 - 随窗体缩放

Qt 界面上控件自适应窗体大小 - 随窗体缩放 引言一、在Qt Designer上设置二、参数详解三、参考链接 引言 添加布局&#xff0c;设置控件的minimumSize、maximumSize和sizePolicy可以使其跟随窗体进行自适应缩放 - 如上图所示。 一、在Qt Designer上设置 在代码中设置效果一致…

HTML.

HTML:超文本标记语言&#xff08;Hyper Text Markup Language&#xff09; 超文本&#xff1a;不同于普通文本&#xff0c;可以定义图片&#xff0c;音频&#xff0c;视频等内容 标记语言&#xff1a;由标签构成的语言 HTML标签都是预定义好的HTML代码直接在浏览器中运行&#…

JVM之【运行时数据区】

JVM简图 运行时数据区简图 一、程序计数器&#xff08;Program Counter Register&#xff09; 1.程序计数器是什么&#xff1f; 程序计数器是JVM内存模型中的一部分&#xff0c;它可以看作是一个指针&#xff0c;指向当前线程所执行的字节码指令的地址。每个线程在执行过程中…

深度神经网络——什么是生成式人工智能?

1.引言 生成式人工智能最近引起了很大的关注。 该术语用于指依赖无监督或半监督学习算法来创建新的数字图像、视频、音频和文本的任何类型的人工智能系统。 麻省理工学院表示&#xff0c;生成式人工智能是过去十年人工智能领域最有前途的进展之一。 通过生成式人工智能&#…

AI智能体|手把手教你使用扣子Coze图像流的文生图功能

大家好&#xff0c;我是无界生长。 AI智能体&#xff5c;手把手教你使用扣子Coze图像流的文生图功能本文详细介绍了Coze平台的\x26quot;图像流\x26quot;功能中的\x26quot;文生图\x26quot;节点&#xff0c;包括创建图像流、编排文生图节点、节点参数配置&#xff0c;并通过案例…

Layui设置table表格中时间的显示格式

1、问题概述? 【数据库中的时间格式】 【Layui中table表格默认的显示格式】 默认的格式中会显示时间的毫秒单位,但是这个毫秒有时候是不需要的。 总结:这个时候我们就需要定义table表格中的时间显示格式。 2、解决办法? 【解决后时间的显示格式】 【解决办法1:通过字符…

Linux基础 (九):Linux 进程复制与替换

各位看官&#xff0c;本篇博客干货满满&#xff0c;请耐下心来&#xff0c;慢慢吸收&#xff01;哈哈哈&#xff0c;内功一定会大增&#xff01; 目录 一、printf 函数输出问题 1.1 第1个示例代码 1.2 第2个示例代码 1.3 分析与结论 二、主函数参数介绍 三、复制进程 fo…

Android 处理音频焦点,解决音乐播放冲突的问题

1. 音频焦点是什么 在Android中&#xff0c;两个或多个 Android 应用可以同时将音频播放到同一输出流&#xff0c;系统会将所有音频混合在一起。 但是多数情况下&#xff0c;这对于用户来说是感到困惑的。为了避免多个应用的多个音频一起播放&#xff0c;Android 引入了“音频…

【百度智能体】零代码创建你的 AI 宠物助手

前言 今天给大家介绍一下百度的 AI 产品 – 百度智能体&#xff0c;在文心智能体平台你可以0代码就可以创建出属于自己的 AI 机器人&#xff0c;几乎可以选择任何你想要的领域或者行业机器人&#xff0c;进行无代码打造自己的对话助手&#xff0c;本文将介绍文心智能体&#x…

jmeter之MD5加密接口请求教程

前言&#xff1a; 有时候在项目中&#xff0c;需要使用MD5加密的方法才可以登录&#xff0c;或者在某一个接口中遇到 登录获取token后才可以进行关联&#xff0c;下面介绍下遇到的常见使用 一、第一种方法&#xff1a;使用jmeter自带的函数助手digest 选择工具&#xff0c;选择…

高效的大型语言模型适应方法:提升基础性的解决方案

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

表查询基础【mysql】【表内容 增,删,改,查询】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;Linux_花果山~程序猿的博客-CSDN博客MySQL之旅_花果山~程序猿的博客-CSDN博客Linux_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我…

Redis(十三) 事务

文章目录 前言事务的特性Redis事务的执行原理Redis中使用事务WATCH UNWATCH实现乐观锁 前言 前面我们学习 MySQL 的时候&#xff0c;肯定也学习了事务。事务是什么&#xff1f;给大家举个例子&#xff1a;假如我给朋友微信转账&#xff0c;我给他转了 100 块钱&#xff0c;当我…

Golang | Leetcode Golang题解之第114题二叉树展开为链表

题目&#xff1a; 题解&#xff1a; func flatten(root *TreeNode) {curr : rootfor curr ! nil {if curr.Left ! nil {next : curr.Leftpredecessor : nextfor predecessor.Right ! nil {predecessor predecessor.Right}predecessor.Right curr.Rightcurr.Left, curr.Righ…

Vue3实战笔记(46)—Vue 3高效开发定制化Dashboard的权威手册

文章目录 前言Dashboard开发总结 前言 后台管理系统中的Dashboard是一种图形化的信息显示工具&#xff0c;通常用于提供一个特定领域或系统的概况。它可以帮助用户监控和分析数据&#xff0c;快速获取重要信息。可以帮助用户监控业务状况、分析数据、获取关键信息和管理资源。…

list的模拟实现(一)

嗨喽大家好&#xff0c;时隔许久阿鑫又给大家带来了新的博客&#xff0c;list的模拟实现&#xff08;一&#xff09;&#xff0c;下面让我们开始今天的学习吧&#xff01; list的模拟实现&#xff08;一&#xff09; 1.list splice接口的使用 2.list尾插的实现 3.list的迭代…

Python编程的黑暗魔法:模块与包的神秘力量!

哈喽&#xff0c;我是阿佑&#xff0c;今天给大家讲讲模块与包~ 文章目录 1. 引言1.1 模块化编程的意义1.2 Python中模块与包的概念概述 2. 背景介绍2.1 Python模块系统模块的定义与作用Python标准库简介 2.2 包的结构与目的包的定义与目录结构包在项目组织中的重要性 3. 创建与…

【C语言】strstr函数的使用和模拟

前言 今天给大家带来一个字符串函数&#xff0c;strstr()的使用介绍和模拟实现。 模拟实现这个函数&#xff0c;可以帮助我们更深刻地理解这个函数的功能和提高解决字符串相关问题的能力&#xff0c;有兴趣的话就请往下看吧。 strstr函数介绍 函数功能&#xff1a; strstr函…

Three.js 研究:3、创建一个高科技圆环

打开Alpha混合 修改环形颜色&#xff0c;更改发光的颜色&#xff0c;更改发光的强度为2 更改世界环境灯光