通讯录的实现【涉及动态内存管理和文件操作】【从易到难】【详解】
创始人
2024-02-02 11:48:33
0

在这里插入图片描述

本期介绍🍖
主要介绍:如何实现一个通讯录,从静态版通讯录,到动态内存版通讯录,再到文件存储版通讯录,详细讲述了每一个通讯录的实现步骤以及思维逻辑,以及通讯录的完整代码👀。


文章目录

  • 一、通讯录(静态版)🍖
    • 1.1 如何存放通讯录🍖
    • 1.2 通讯录菜单🍖
    • 1.3 初始化通讯录🍖
    • 1.4 实现添加新成员🍖
    • 1.5 打印全部成员🍖
    • 1.6 查找联系人🍖
    • 1.7 删除联系人🍖
    • 1.8 修改联系人信息🍖
    • 1.9 排序联系人🍖
  • 二、通讯录(动态内存版)🍖
    • 2.1 通讯录的结构体声明🍖
    • 2.2 通讯录的初始化🍖
    • 2.3 通讯录添加新成员🍖
    • 2.4 销毁通讯录🍖
  • 三、通讯录(文件操作版)🍖
    • 3.1 保存通讯录🍖
    • 3.2 加载通讯录🍖
  • 四、完整的代码🍖


前言

  现要求使用C语言实现一个能够存放1000个人信息的通讯录,每个人的信息包括:姓名、年龄、性别、电话、地址。且要求这个通讯录能够实现:

  1. 添加联系人
  2. 删除联系人
  3. 查找联系人
  4. 修改联系人
  5. 显示所有联系人
  6. 排序联系人

一、通讯录(静态版)🍖

1.1 如何存放通讯录🍖

  首先,我们需要来思考一下什么是通讯录,以及我们该使用什么变量来存放通讯录。通讯录就是一个由联系人组成的集合,且每一个联系人包含了5个不同类型的信息。故我们不难得出可以用一个结构体数组来存放整个通讯录。但值得注意的是,在之后应用中我们随时需要知道通讯录中已经存在几个联系人,所以可以将通讯录设置成如下的样式:

//个人信息
#define NAME_MAX 20//姓名所占字节大小
#define SEX_MAX 5//性别所占字节大小
#define TELE_MAX 12//电话所占字节大小
#define ADDR_MAX 30//地址所占字节大小
#define NUM_MAX 1000//通讯录存储人员个数大小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[NUM_MAX];//通讯录人员信息int num;//通讯录种已存放人员个数
}Contact;

  这样做大大简化了函数的构思。举个例子:当一个函数需要知道通讯录当下已经存放了几个成员时,就不需要再通过函数传参、以及函数返回值来实时的更新人员个数,仅仅只需要传一个通讯录变量的地址给函数,然后在函数内部就能完成全部的操作。

  注意:这里使用#define来定义那些常量的目的是,在统一的运用后,可以统一的进行修改(只需要在#define后进行修改就可以了)。


1.2 通讯录菜单🍖

//菜单
static void menu()
{printf("\n");printf("*****************************************************\n");printf("********  1.增加新成员        2.删除成员      *******\n");printf("********  3.查找成员          4.修改成员信息  *******\n");printf("********  5.显示所有成员信息  6.排序          *******\n");printf("********  0.退出                              *******\n");printf("*****************************************************\n");printf("\n");}enum Option
{Exit,Add,Del,Search,Modify,Show,Sort
};int main()
{//1.创建一个通讯录Contact con;//2.初始化通讯录InitContact(&con);//3.实现菜单int input = 0;do{menu();printf("请选择:");scanf("%d", &input);switch (input){case Add:Add_Contact(&con);//增加新成员break;case Del:Del_Contact(&con);//删除成员break;case Search:Search_Contact(&con);//查找成员break;case Modify:Modify_Contact(&con);//修改成员信息break;case Show:Show_Contact(&con);//显示所有成员信息break;case Sort:Sort_Contact(&con);//成员排序break;case Exit:printf("已退出通讯录\n");break;default:printf("输入错误请重新输入:\n");break;}} while (input);return 0;
}

  注意:这里设置一个枚举类型enum Option的目的是:在接下来的switch case语句中可以用枚举类型成员名来代替数字了,这样做跟容易联想,不需要在思考数字所对应的功能是什么了。


1.3 初始化通讯录🍖

//初始化通讯录
void InitContact(Contact* con)
{//初始化datamemset(con, 0, sizeof(struct PeoInfo) * NUM_MAX);//初始化numcon->num = 0;
}

  初始化通讯录其实只要将,通讯录变量中的datanum部分分别初始化就可以了。对于data部分我们是通过内存设置函数memset() 来实现的,该函数的参数为:

  1. 将要被设置内存的起始位置
  2. 每个字节被设置的值
  3. 被设置的内存一共占多少个字节

  所以不难得出,data的起始地址为con,每个字节需要被设为0data一共占用sizeof(struct PeoInfo) * NUM_MAX个字节的大小(也就是1000个联系人的大小)。


1.4 实现添加新成员🍖

void Add_Contact(Contact* con)
{assert(con);printf("添加新成员\n");//输入一个人的信息printf("请输入姓名:");scanf("%s", con->data[con->num].name);printf("请输入年龄:");scanf("%d", &(con->data[con->num].age));printf("请输入性别:");scanf("%s", con->data[con->num].sex);printf("请输入电话:");scanf("%s", con->data[con->num].tele);printf("请输入地址:");scanf("%s", con->data[con->num].addr);//通讯录成员个数加一con->num++;
}

1.5 打印全部成员🍖

//显示所有成员信息
void Show_Contact(const Contact* con)
{assert(con);if (con->num == 0){printf("通信录为空\n");return;}int i = 0;for (i = 0; i < con->num; i++){printf("%20s %5d %5s %15s %30s\n", con->data[i].name,con->data[i].age,con->data[i].sex,con->data[i].tele,con->data[i].addr);}
}

在这里插入图片描述


1.6 查找联系人🍖

//查找函数
int Search(const Contact* con, char name[])
{assert(con);int i = 0;for (i = 0; i < con->num; i++){if (0 == strcmp(con->data[i].name, name))return i;}return -1;
}//查找联系人
void Search_Contact(const Contact* con)
{assert(con);char name[20] = { 0 };int flag = 0;printf("请输入需要查找的姓名:");scanf("%s", name);//Search函数为查找函数//若没有找到则返回-1,若找到了则返回该成员所在下标flag = Search(con, name);if (flag < 0){printf("未找到该成员\n");}else{printf("%20s %5d %5s %15s %30s\n",con->data[flag].name,con->data[flag].age,con->data[flag].sex,con->data[flag].tele,con->data[flag].addr);}
}

在这里插入图片描述

  注意:这里的查找函数int Search(const Contact* con, char name[])会在之后的删除功能修改功能中用到,因为要想删除和修改前提条件是能够找到呀。


1.7 删除联系人🍖

//删除成员
void Del_Contact(Contact* con)
{assert(con);char name[20] = { 0 };int flag = 0;printf("请输入要删除人的姓名:");scanf("%s", name);//1.查找确定这个人是否存在flag = Search(con, name);if (flag < 0){printf("未找到此人\n");return;}//2.实施删除操作con->data[flag] = con->data[con->num - 1];con->num--;printf("完成删除操作\n");
}

在这里插入图片描述

  注意:此处是通过现找到需要被删除的联系人,然后用最后一个联系人覆盖掉它,最后将现有联系人个数num--。那么下一次录入新成员时就会将之前的最后一个联系人覆盖掉,这样通过覆盖的方式不就间接的实现的删除操作了嘛!


1.8 修改联系人信息🍖

//修改成员信息
void Modify_Contact(Contact* con)
{assert(con);char name[20] = { 0 };int flag = 0;printf("请输入需要被修改的成员:");scanf("%s", name);//1.查找这个人是否存在flag = Search(con, name);if (flag < 0){printf("未找到该此人\n");return;}//2.对该成员进行修改printf("开始修改\n");//输入一个人的信息printf("请输入姓名:");scanf("%s", con->data[flag].name);printf("请输入年龄:");scanf("%d", &(con->data[flag].age));printf("请输入性别:");scanf("%s", con->data[flag].sex);printf("请输入电话:");scanf("%s", con->data[flag].tele);printf("请输入地址:");scanf("%s", con->data[flag].addr);
}

在这里插入图片描述


1.9 排序联系人🍖

  要想排序联系人我们可以通过5种不同的方式来进行排序:姓名、年龄、性别、电话、地址。所以这里需要让使用者来进行选择,然后再通过不同的选择实现不同的排序。但问题来了,该怎么编写代码才不会出现冗余现象呢?我们发现排序函数的框架是不需要改变的(这里的框架表示:冒泡排序),只需要对元素比较部分进行不同样式的修改,就可以实现同一个函数不同样式的排序了,因为排列的先后顺序就是由比较函数所决定的举个例子:比较的姓名,那么排列的顺序必然是按照姓名顺序排列的。给出两个建议:

  1. 使用回调函数(函数指针)
  2. 使用转移表(函数指针数组)

  这里我是使用转移表来实现的,转移表就是通过将函数指针数组的下标排序功能的序号联系起来,从而做到选择什么排序就能过通过函数指针数组找到所选择的排序函数,最终实现输入什么就按照该功能进行排序。就譬如这里,我选择按照姓名排序,输入:1,就可以找到下标为1的函数指针数组所对应的比较函数(比较两个PeoInfo结构体中姓名的大小),最后将该比较函数的地址传递给排序函数,完成通讯录的排序。

//成员排序//菜单
static void menu()
{putchar('\n');printf("*****************************\n");printf("******  1. 按姓名排序  ******\n");printf("******  2. 按年龄排序  ******\n");printf("******  3. 按性别排序  ******\n");printf("******  4. 按电话排序  ******\n");printf("******  5. 按地址排序  ******\n");printf("*****************************\n");putchar('\n');
}//比较函数
int CmpName(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->name, s2->name);
}int CmpAge(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return s1->age - s2->age;
}int CmpSex(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->sex, s2->sex);
}int CmpTele(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->tele, s2->tele);
}int CmpAddr(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->addr, s2->addr);
}//排序函数
void peo_sort(Contact* con, int cmp(PeoInfo* s1, PeoInfo* s2))
{int i = 0;int flag = 0;//记录是否以有序//趟数for (i = 0; i < con->num - 1; i++){int j = 0;//每趟的交换的次数for (j = 0; j < con->num - 1 - i; j++){//升序排放if (cmp(&(con->data[j]), &(con->data[j + 1])) > 0){//交换两个元素PeoInfo tmp = con->data[j];con->data[j] = con->data[j + 1];con->data[j + 1] = tmp;//此时数组任然是乱序的flag = 1;}}//若flag在一趟排序中始终没有置1,则说明此时数组已然有序if (flag == 0)break;}
}void Sort_Contact(Contact* con)
{//创建函数指针数组(用于实现转移表)int (*cmp_arr[6])(PeoInfo*, PeoInfo*) = { 0, CmpName, CmpAge, CmpSex, CmpTele, CmpAddr };int input = 0;do{menu();printf("请选择要以什么方式进行排序:");scanf("%d", &input);if (input >= 1 && input <= 5){//冒泡排序peo_sort(con, cmp_arr[input]);printf("完成排序\n");break;}else{printf("输入错误请重新输入\n");}} while (input);
}

姓名排序
在这里插入图片描述
年龄排序
在这里插入图片描述
性别排序
在这里插入图片描述
电话排序
在这里插入图片描述
地址排序
在这里插入图片描述


二、通讯录(动态内存版)🍖

  上面实现的(静态版)通讯录对于内存的使用太死板不够灵活为什么这么说呢?举个例子:由于上面实现的通讯录一开始就开辟了能够存放1000人信息的空间,当仅需使用该通讯录来存放3个成员信息时,剩余的997个空间不就浪费掉了,而且及其奢侈,而我们又无法消除这种浪费;同样如果需要存放的成员信息多于1000个时,该通讯录就无法适用了,因为初始化时向内存申请的空间是固定死的,只有1000个。

  那怎样才能解决上述难题呢? 答案是使用动态内存来实现这个通讯录,如此就可以做到,内存不够用时对内存进行扩容,内存过大时对内存进行削减,实现对内存的利用最大化。总结,动态内存管理就是通过对内存的动态变化,使得内存的利用率变高的方法


2.1 通讯录的结构体声明🍖

  静态版通讯录结构体由两部份组成,其一是1000个成员信息data,其二是已经存放人数num,如下所示。

//通讯录
typedef struct Contact
{PeoInfo data[NUM_MAX];//通讯录人员信息int num;//通讯录中已存放人员个数
}Contact;

  而这种声明方式必然会导致上述难题的出现,而为了能够灵活使用内存,我们应将通讯录声明设计成如下形式:

//通讯录
typedef struct Contact
{PeoInfo* data;//通讯录人员信息int num;//通讯录中已存放人员个数int Capacity;//通讯录容量
}Contact;

  其中data为一个指针,该指针是用来维护之后动态申请的那块空间的。而多出来的这个成员变量Capacity用以表示当前通讯录的容量。对应的内存布局如下图所示:

在这里插入图片描述


2.2 通讯录的初始化🍖

  首先需要用calloc函数向内存申请一块能够存放3个成员信息的空间,并使得data指针来维护这块空间。然后将通讯录中已存放人员个数num和通讯录容量capacity初始化一下。

void InitContact(Contact* con)
{//向内存申请一块能够存放3个成员的空间,并使得data指针来维护这块空间con->data = (PeoInfo*)calloc(CON_NUM, sizeof(PeoInfo));if (con->data == NULL){printf("InitContact:%s\n", strerror(errno));return;}//初始化num和Capacitycon->num = 0;con->Capacity = CON_NUM;//宏定义CON_NUM = 3;
}

2.3 通讯录添加新成员🍖

  初始化时设定的通讯录容量为3个,当存放第4个成员信息前,就必须对通讯录的容量进行增容。而增容分为两部分:其一,对动态开辟的空间进行调整;其二,对结构体成员Capacity增加2。接着才能继续录入人员信息,直至下一次容量不够用时再对其扩容,如此往复。代码如下:

void CheckCapacity(Contact* con)
{assert(con);//判断已存放人数是否已超出容量if (con->num == con->Capacity){//通讯录扩容(扩容2个成员大小)PeoInfo* ptr = (PeoInfo*)realloc(con->data, (con->Capacity + INC_SZ) * sizeof(PeoInfo));if (ptr == NULL){printf("CheckCapacity::%s\n", strerror(errno));return;}else{con->data = ptr;con->Capacity += INC_SZ;//宏定义INC_SZ = 2;printf("增容成功\n");}}
}//添加新成员
void Add_Contact(Contact* con)
{assert(con);//检查通讯录CheckCapacity(con);printf("添加新成员\n");//输入一个人的信息printf("请输入姓名:");scanf("%s", con->data[con->num].name);printf("请输入年龄:");scanf("%d", &(con->data[con->num].age));printf("请输入性别:");scanf("%s", con->data[con->num].sex);printf("请输入电话:");scanf("%s", con->data[con->num].tele);printf("请输入地址:");scanf("%s", con->data[con->num].addr);//通讯录成员个数加一con->num++;
}

在这里插入图片描述


2.4 销毁通讯录🍖

  当我们结束对通讯录的操作后,是需要把之前动态内存开辟的空间还给操作系统,也就是把通讯录结构体中成员变量data指向的那块空间释放掉。如图所示,在test.c文件末尾加上销毁通讯录的操作。

在这里插入图片描述

//销毁通讯录
void DestoryContact(Contact* con)
{assert(con);free(con->data);con->data = NULL;
}

三、通讯录(文件操作版)🍖

  之前实现的(动态内存版)通讯录确实提高了内存的利用率,但是不知道大家在有没有发现一个问题:当程序运行起来后,我们向通讯录中增加了一些成员变量,而且确确实实的保存到通讯录中了,接着把通讯录关掉然后再次打开,就会发现其中一个成员的信息都没有了。为什么呢

  因为当程序运行起来后,如果我们没有将这些数据存储起来的话,它们只是在内存中暂时的存放。一旦程序结束,程序运行时所占用的空间就会被回收,那些数据自然相当于销毁了。 当下一次再运行时里面当然没有数据了,故我们又得重新录入数据,但这是不是有点太不人性化了呀!所以为了能够持久化的使用数据,不会因为程序运行的结束而使得数据丢失,故我们需要将它们保存下来,保存到文件中去。下面我就来带着大家实现一下(文件版)通讯录,当然是在动态内存版基础上实现的。


3.1 保存通讯录🍖

  首先思考一个问题:我们因该把保存通讯录的代码写在哪里? 是不是放在程序的最后面,且在销毁通讯录之前。如下图所示:

在这里插入图片描述

  至于该怎么实现,我们应先以二进制写的形式打开文件,然后使用fwrite()函数将现在通讯录中存在的所用成员信息输出到文件当中去,最后再关闭文件。注意:为什么我们要以“”的形式打开文件?因为如此做后,每一次向文件中写数据都会覆盖掉原先存在的数据,起到了数据的更新迭代。而不会因为每次都是在上一次的基础上追加,而导致数据的冗余。代码如下:

//保存通讯录
void Save_Contact(const Contact* con)
{assert(con);//1.打开文件FILE* pfWrite = fopen("contact.txt", "wb");if (pfWrite == NULL){perror("Save_Contact");return;}//2.保存所有人员信息fwrite(con->data, sizeof(PeoInfo), con->num, pfWrite);//3.关闭文件fclose(pfWrite);pfWrite = NULL;
}

  当该程序运行结束后,到通讯录项目下去找我们会发现那个我们创建的文件contact.txt

在这里插入图片描述


3.2 加载通讯录🍖

  上面我们已经实现了对通讯录数据的存储,那么再思考一下:该怎么使用这些被保存下来的数据呢? 我们是不是希望在关闭通讯录后下一次再打开时,能够看到之前的数据啊。所以我们应该在通讯录初始化时将保存的数据载入通讯录中,这样在使用通讯录之前就使得数据存在于通讯录中了。

  那该怎么实现呢? 首先我们并不知道内存中方有几个人员的信息,所以不能一次性的读取,只能一个一个的往里读,直到遇见文件的结尾或者没信息可读时才算结束。但值得注意的是,我们还得考虑通讯录容量不够的情况,所以必须在将数据放入通讯录前加一个容量的判断,若不够则扩容。下面是代码的实现:

//检查通讯录容量
void CheckCapacity(Contact* con)
{assert(con);//判断已存放人数是否已超出容量if (con->num == con->Capacity){//通讯录扩容(扩容2个成员大小)PeoInfo* ptr = (PeoInfo*)realloc(con->data, (con->Capacity + INC_SZ) * sizeof(PeoInfo));if (ptr == NULL){printf("CheckCapacity::%s\n", strerror(errno));return;}else{con->data = ptr;con->Capacity += INC_SZ;printf("增容成功\n");}}
}//加载通讯录
void Load_Contact(Contact* con)
{assert(con);//1.以二进制读的形式打开文件FILE* pfRead = fopen("contact.txt", "rb");if (pfRead == NULL){perror("Load_Contact");return;}//2.读取数据PeoInfo tmp = { 0 };while (fread(&tmp, sizeof(PeoInfo), 1, pfRead) == 1){//判断是否需要扩容CheckCapacity(con);//将临时存放在tmp中的数据放入通讯录中con->data[con->num] = tmp;con->num += 1;}//3.关闭文件fclose(pfRead);pfRead = NULL;
}//初始化通讯录
void InitContact(Contact* con)
{assert(con);//向内存申请一块能够存放3个成员的空间,并使得data指针来维护这块空间con->data = (PeoInfo*)calloc(CON_NUM, sizeof(PeoInfo));if (con->data == NULL){perror("InitContact");return;}//初始化num和Capacitycon->num = 0;con->Capacity = CON_NUM;//加载通讯录Load_Contact(con);
}

在这里插入图片描述

  在重新打开通讯录后,我们发现程序成功载入了上一次保存在文件中的数据。

  注意:代码中while循环的判断条件是fread()函数的返回值,该函数的返回值为成功读取到数据的个数,而这里我们是一个一个读的,所以只有两种情况要么为1要么为0。如此就可以是实现训话读数据,直到再也读不到数据时停下来。


四、完整的代码🍖

#include
#include
#include
#include
#include#define NAME_MAX 20//姓名所占字节大小
#define SEX_MAX 5//性别所占字节大小
#define TELE_MAX 12//电话所占字节大小
#define ADDR_MAX 30//地址所占字节大小
#define CON_NUM 3//初始容量
#define INC_SZ 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;//通讯录人员信息int num;//已存放人员个数int Capacity;//通讯录容量
}Contact;//初始化通讯录
void InitContact(Contact* con);//销毁通讯录
void DestoryContact(Contact* con);//增加新成员
void Add_Contact(Contact* con);//显示所有成员信息
void Show_Contact(const Contact* con);//查找成员
void Search_Contact(const Contact* con);//删除成员
void Del_Contact(Contact* con);//修改成员信息
void Modify_Contact(Contact* con);//成员排序
void Sort_Contact(Contact* con);//保存通讯录
void Save_Contact(const Contact* con);//加载通讯录
void Load_Contact(Contact* con);//检查通讯录容量
void CheckCapacity(Contact* con)
{assert(con);//判断已存放人数是否已超出容量if (con->num == con->Capacity){//通讯录扩容(扩容2个成员大小)PeoInfo* ptr = (PeoInfo*)realloc(con->data, (con->Capacity + INC_SZ) * sizeof(PeoInfo));if (ptr == NULL){printf("CheckCapacity::%s\n", strerror(errno));return;}else{con->data = ptr;con->Capacity += INC_SZ;printf("增容成功\n");}}
}//加载通讯录
void Load_Contact(Contact* con)
{assert(con);//1.以二进制读的形式打开文件FILE* pfRead = fopen("contact.txt", "rb");if (pfRead == NULL){perror("Load_Contact");return;}//2.读取数据PeoInfo tmp = { 0 };while (fread(&tmp, sizeof(PeoInfo), 1, pfRead) == 1){//判断是否需要扩容CheckCapacity(con);//将临时存放在tmp中的数据放入通讯录中con->data[con->num] = tmp;con->num += 1;}//3.关闭文件fclose(pfRead);pfRead = NULL;
}//初始化通讯录
void InitContact(Contact* con)
{assert(con);//向内存申请一块能够存放3个成员的空间,并使得data指针来维护这块空间con->data = (PeoInfo*)calloc(CON_NUM, sizeof(PeoInfo));if (con->data == NULL){perror("InitContact");return;}//初始化num和Capacitycon->num = 0;con->Capacity = CON_NUM;//加载通讯录Load_Contact(con);
}//保存通讯录
void Save_Contact(const Contact* con)
{assert(con);//1.打开文件FILE* pfWrite = fopen("contact.txt", "wb");if (pfWrite == NULL){perror("Save_Contact");return;}//2.保存所有人员信息fwrite(con->data, sizeof(PeoInfo), con->num, pfWrite);//3.关闭文件fclose(pfWrite);pfWrite = NULL;
}//销毁通讯录
void DestoryContact(Contact* con)
{assert(con);//释放data所指向的动态开辟空间free(con->data);con->data = NULL;
}//查找成员
int Search(const Contact* con, char name[])
{assert(con);int i = 0;for (i = 0; i < con->num; i++){if (0 == strcmp(con->data[i].name, name))return i;}return -1;
}//添加新成员
void Add_Contact(Contact* con)
{assert(con);//检查通讯录容量CheckCapacity(con);printf("添加新成员\n");//输入一个人的信息printf("请输入姓名:");scanf("%s", con->data[con->num].name);printf("请输入年龄:");scanf("%d", &(con->data[con->num].age));printf("请输入性别:");scanf("%s", con->data[con->num].sex);printf("请输入电话:");scanf("%s", con->data[con->num].tele);printf("请输入地址:");scanf("%s", con->data[con->num].addr);//通讯录成员个数加一con->num++;
}//显示所有成员信息
void Show_Contact(const Contact* con)
{assert(con);if (con->num == 0){printf("通信录为空\n");return;}int i = 0;for (i = 0; i < con->num; i++){printf("%20s %5d %5s %15s %30s\n", con->data[i].name,con->data[i].age,con->data[i].sex,con->data[i].tele,con->data[i].addr);}
}//查找成员
void Search_Contact(const Contact* con)
{assert(con);char name[20] = { 0 };int flag = 0;printf("请输入需要查找的姓名:");scanf("%s", name);//Search函数为查找函数//若没有找到则返回-1,若找到了则返回该成员所在下标flag = Search(con, name);if (flag < 0){printf("未找到该成员\n");}else{printf("%20s %5d %5s %15s %30s\n",con->data[flag].name,con->data[flag].age,con->data[flag].sex,con->data[flag].tele,con->data[flag].addr);}
}//删除成员
void Del_Contact(Contact* con)
{assert(con);char name[20] = { 0 };int flag = 0;printf("请输入要删除人的姓名:");scanf("%s", name);//1.查找确定这个人是否存在flag = Search(con, name);if (flag < 0){printf("未找到此人\n");return;}//2.实施删除操作con->data[flag] = con->data[con->num - 1];con->num--;printf("完成删除操作\n");
}//修改成员信息
void Modify_Contact(Contact* con)
{assert(con);char name[20] = { 0 };int flag = 0;printf("请输入需要被修改的成员:");scanf("%s", name);//1.查找这个人是否存在flag = Search(con, name);if (flag < 0){printf("未找到该此人\n");return;}//2.对该成员进行修改printf("开始修改\n");//输入一个人的信息printf("请输入姓名:");scanf("%s", con->data[flag].name);printf("请输入年龄:");scanf("%d", &(con->data[flag].age));printf("请输入性别:");scanf("%s", con->data[flag].sex);printf("请输入电话:");scanf("%s", con->data[flag].tele);printf("请输入地址:");scanf("%s", con->data[flag].addr);
}//成员排序//菜单
static void menu()
{putchar('\n');printf("*****************************\n");printf("******  1. 按姓名排序  ******\n");printf("******  2. 按年龄排序  ******\n");printf("******  3. 按性别排序  ******\n");printf("******  4. 按电话排序  ******\n");printf("******  5. 按地址排序  ******\n");printf("*****************************\n");putchar('\n');
}//比较函数
int CmpName(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->name, s2->name);
}int CmpAge(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return s1->age - s2->age;
}int CmpSex(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->sex, s2->sex);
}int CmpTele(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->tele, s2->tele);
}int CmpAddr(PeoInfo* s1, PeoInfo* s2)
{assert(s1 && s2);return strcmp(s1->addr, s2->addr);
}//排序函数
void peo_sort(Contact* con, int cmp(PeoInfo* s1, PeoInfo* s2))
{int i = 0;int flag = 0;//记录是否以有序//趟数for (i = 0; i < con->num - 1; i++){int j = 0;//每趟的交换的次数for (j = 0; j < con->num - 1 - i; j++){//升序排放if (cmp(&(con->data[j]), &(con->data[j + 1])) > 0){//交换两个元素PeoInfo tmp = con->data[j];con->data[j] = con->data[j + 1];con->data[j + 1] = tmp;//此时数组任然是乱序的flag = 1;}}//若flag在一趟排序中始终没有置1,则说明此时数组已然有序if (flag == 0)break;}
}void Sort_Contact(Contact* con)
{//创建函数指针数组(用于实现转移表)int (*cmp_arr[6])(PeoInfo*, PeoInfo*) = { 0, CmpName, CmpAge, CmpSex, CmpTele, CmpAddr };int input = 0;do{menu();printf("请选择要以什么方式进行排序:");scanf("%d", &input);if (input >= 1 && input <= 5){peo_sort(con, cmp_arr[input]);printf("完成排序\n");break;}else{printf("输入错误请重新输入\n");}} while (input);
}//菜单
static void menu()
{printf("\n");printf("*****************************************************\n");printf("********  1.增加新成员        2.删除成员      *******\n");printf("********  3.查找成员          4.修改成员信息  *******\n");printf("********  5.显示所有成员信息  6.排序          *******\n");printf("********  0.退出                              *******\n");printf("*****************************************************\n");printf("\n");}enum Option
{Exit,Add,Del,Search,Modify,Show,Sort
};int main()
{//1.创建一个通讯录Contact con;//2.初始化通讯录InitContact(&con);//4.实现菜单int input = 0;do{menu();printf("请选择:");scanf("%d", &input);switch (input){case Add:Add_Contact(&con);//增加新成员break;case Del:Del_Contact(&con);//删除成员break;case Search:Search_Contact(&con);//查找成员break;case Modify:Modify_Contact(&con);//修改成员信息break;case Show:Show_Contact(&con);//显示所有成员信息break;case Sort:Sort_Contact(&con);//成员排序break;case Exit://保存通讯录Save_Contact(&con);//销毁通讯录DestoryContact(&con);printf("已退出通讯录\n");break;default:printf("输入错误请重新输入:\n");break;}} while (input);return 0;
}

在这里插入图片描述

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...