问题描述
用C语言实现简易通讯录
工具
vs2019
基本框架
通讯录可以用来存储100个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址
提供方法:
- 添加联系人信息
- 删除指定联系人信息
- 查找指定联系人信息
- 修改指定联系人信息
- 显示所有联系人信息
- 清空所有联系人
- 以名字排序所有联系人
静态通讯录
依靠数组和结构体来实现(较为基础)
具体实现思路与流程
注:参考代码部分有些地方可以根据自己的需要进行优化
准备阶段
需要建立两个源文件和一个头文件:
- contact.h:宏定义修饰特定数据(方便维护),包含各种头文件,以及结构体与函数的声明
- test.c:写通讯录的整体流程和逻辑
- contact.c:实现通讯录各种功能函数
创建联系人信息与通讯录结构体
对于联系人信息:
需要包含包括姓名、性别、年龄、电话、住址
对于静态通讯录:
除了联系人,还需要记录使用的人数(方便后面功能的实现)
由此我们需要创建结构体变量
参考代码:
//个人信息结构 struct Peoinfo { char name[NAME_MAX]; int age; char sex[SEX_MAX]; int number[NUMBER_MAX]; char addr[ADDR_MAX]; }; //通讯录结构 struct Contact { //总联系人容量 struct Peoinfo data[PEOINFO_MAX]; //使用个数记录 int sz; };
为了方便以后维护修改,我们可以把一些数据用预处理指令来修饰
- 参考代码:
//信息接收最大数目(预处理指令便于维护) #define NAME_MAX 20 #define NUMBER_MAX 11 #define SEX_MAX 10 #define ADDR_MAX 30 #define PEOINFO_MAX 100
主体
do while循环:
首先进入选择功能或者退出,选择功能后还可以继续选择,由此采用do while循环来实现整体逻辑
回调函数:
对于各种功能的实现在这里一般只需要传入结构体地址来进行操作通讯录,所以参数形式都一致,返回类型也一致,所以采用回调函数便于调用函数,并且简易便捷
罗盘:
一个数组,元素为函数指针(便于进行传入函数地址)
参考代码:
//回调函数,需要传入函数地址(调用函数)和通讯录地址(操作通讯录) void Calo(void(*pc)(struct Peoinfo*i), struct Contact* p) { pc(p);//pc()为函数调用,参数传入p即通讯录地址 } //主体逻辑 int main() { int intput; struct Contact con;//创建一个通讯录 InitContact(&con);//对通讯录初始化 //写个罗盘数组,元素为函数指针(便于进行调用函数,进行传参) void(*table[8])(struct Contract* pc) = { 0,AddContact,DelContact,SearchContact,ModifyContact,ShowContact,SortContact,InitContact }; //基本逻辑循环 do { menu(); printf("请输入接下来想要进行的操作:->\n"); scanf("%d", &intput); if (intput > 0 && intput <=7 ) { printf("成功进入所选择项操作!\n"); Calo(table[intput],&con);//回调操作 } else if (intput == 0) { printf("退出操作成功!!\n"); break; } else { printf("输入项错误!请重新选择:->\n"); } } while (intput);//intput为0则退出 }
菜单界面
这个很简单,直接上代码
- 参考代码:
void menu() { printf("**************************\n"); printf("****1.add 2.del****\n"); printf("****3.search 4.modify****\n"); printf("****5.show 6.sort****\n"); printf("****7.clear 0.exit****\n"); printf("**************************\n"); }
通讯录各功能实现
初始化通讯录(清空通讯录联系人)
- 注意点:
- 创建后我们还需要进行初始化,否则为随机值
- 同样的清除通讯录也相当于初始化通讯录(实现代码一致,部分细节可以自己修改)
- 参考代码:
//初始化通讯录 void InitContact(struct Contact* p) { p->sz = 0; //使用内存函数进行初始化数组 memset(p->data, 0, sizeof(p->data)); }
添加通讯录联系人
- 注意点:
- 首先要对通讯录判断是否已满,再进行操作
- 判断完后添加信息,添加成功还要记得让已使用人数+1
- 参考代码:
//添加通讯录联系人 void AddContact(struct Contact*p) { if (p->sz == PEOINFO_MAX) { printf("通讯录联系人已满,无法添加!\n"); } else { printf("请输入新联系人的名字:"); scanf("%s", p->data[p->sz].name); printf("请输入新联系人的年龄:"); scanf("%d", &p->data[p->sz].age); printf("请输入新联系人的性别:"); scanf("%s", p->data[p->sz].sex); printf("请输入新联系人的电话号码:"); scanf("%s", p->data[p->sz].number); printf("请输入新联系人的地址:"); scanf("%s", p->data[p->sz].addr); p->sz++; printf("添加通讯录联系人%s成功!\n", p->data[p->sz].name); } return; }
删除通讯录联系人
- 注意点:
- 要删除联系人,首先得在通讯录里看是否存在该联系人(采用遍历),再进行操作
- 删除联系人我们可以选择用覆盖的方法,将后面一个联系人的信息赋值给前面一个
- 删除后已就得记得将已使用人数-1
- 参考代码:
//删除通讯录联系人 void DelContact(struct Contact* p) { char name[NAME_MAX]; printf("请输入要删除的联系人名字:\n"); scanf("%s", &name); for (int i = 0; i < p->sz; i++) { //字符串比较采用strcmp if (strcmp(name, p->data[i].name) == 0) { for (int j = i; j < p->sz - 1; j++) { //字符串赋值采用strcpy strcpy(p->data[j].name , p->data[j + 1].name); p->data[j].age = p->data[j + 1].age; strcpy(p->data[j].sex , p->data[j + 1].sex); strcpy(p->data[j].number , p->data[j + 1].number); strcpy(p->data[j].addr , p->data[j + 1].addr); } p->sz--; printf("删除通讯录联系人%s成功!\n", name); return; } } printf("在通讯录中无法查找到联系人%s!\n",name); return; }
查找通讯录联系人
- 注意点:
- 同样的我们要判断查找的联系人是否存在
- 如果存在我们得显示该联系人的相关信息
- 参考代码:
//查找通讯录联系人 void SearchContact(struct Contact* p) { char name[NAME_MAX]; printf("请输入要进行操作的联系人名字:\n"); scanf("%s", &name); for (int i = 0; i < p->sz; i++) { if (strcmp(name, p->data[i].name) == 0) { printf("联系人的名字:%s\n", p->data[i].name); printf("联系人的年龄:%d\n", p->data[i].age); printf("联系人的性别:%s\n",p->data[i].sex); printf("联系人的电话号码:%s\n", p->data[i].number); printf("联系人的地址:%s\n", p->data[i].addr); return;//展示后就可以退出了 } } //前面条件不符合即没有该联系人 printf("在通讯录中无法查找到该联系人!\n"); return; }
修改联系人信息
注意点:
- 同样的我们依旧要看是否存在该联系人,存在则先将信息展示
- 对于修改有两个方案:1.重新再输入 2.选择需要的信息修改
- 这里我们为了更人性化选择方案2,当然粗暴的方案1也行
- 对于方案2的主体逻辑我们可以用do while循环
- 里面再使用switch进行选择的实现
- 对于选择项我们可以写个选择界面出来
参考代码
//操作选项 void option() { printf("**************************\n"); printf("****1.name 2.age****\n"); printf("****3.sex 4.number****\n"); printf("****5.addr 0.exit****\n"); printf("**************************\n"); } //修改通讯录联系人信息 void ModifyContact(struct Contact* p) { int pos=-1; int intput = -1; ShowContact(p); char name[NAME_MAX]; printf("请输入要进行操作的联系人名字:\n"); scanf("%s", &name); for (int i = 0; i < p->sz; i++) { if (strcmp(name, p->data[i].name) == 0) { //找到则记录位置并进行展示 printf("联系人的名字:%s\n", p->data[i].name); printf("联系人的年龄:%d\n", p->data[i].age); printf("联系人的性别:%s\n", p->data[i].sex); printf("联系人的电话号码:%s\n", p->data[i].number); printf("联系人的地址:%s\n", p->data[i].addr); pos = i; break; } } //找不到则退出 if(pos==-1) { printf("在通讯录中无法查找到该联系人!\n"); return; } do { option(); printf("请输入要修改的选项:->\n"); scanf("%d", &intput); switch (intput) { case 0: printf("退出修改成功!\n"); break; case 1: printf("请输入新的名字:\n"); scanf("%s", &p->data[pos].name); break; case 2: printf("请输入新的年龄:\n"); scanf("%d", &p->data[pos].age); break; case 3: printf("请输入新的性别:\n"); scanf("%d", &p->data[pos].sex); break; case 4: printf("请输入新的号码:\n"); scanf("%d", &p->data[pos].number); break; case 5: printf("请输入新的地址:\n"); scanf("%d", &p->data[pos].addr); break; default: printf("输入错误,请重新输入:\n"); } } while (intput); return; }
展示通讯录联系人
注意点:
- 为了美观,我们可以弄一个表格出来
- 展示联系人信息直接遍历就好了
- 参考代码:
//展示通讯录联系人 void ShowContact(struct Contact* p) { //制表 printf("————————————————————————————————————————————————\n"); printf("||%20s|\t%5s|\t%11s|\t%10s|\t%30s||\n", "name", "age", "sex", "tele", "addr"); printf("————————————————————————————————————————————————\n"); for (int i = 0; i < p->sz; i++) { printf("||%20s|\t%5d|\t%11s|\t%10s|\t%30s||\n", p->data[i].name, p->data[i].age, p->data[i].sex, p->data[i].number, p->data[i].addr); printf("————————————————————————————————————————————————\n"); } return; }
排序通讯录联系人
- 注意点:
- 对于排序我们可以使用qsort函数
- 对于qsort函数我们要实现对应的比较函数
- 选择采用的排序方式的主体逻辑于选择修改方式一致
- 对于选择方式界面我们可以沿用修改方式中所提到的选择界面
- 参考代码:
//qsort函数参数比较函数的实现 int cmp_con_name(const void* e1,const void* e2) { return strcmp(((struct Peoinfo*)e1)->name, ((struct Peoinfo*)e2)->name); } int cmp_con_age(const void* e1, const void* e2) { return ((struct Peoinfo*)e1)->age - ((struct Peoinfo*)e2)->age; } int cmp_con_sex(const void* e1, const void* e2) { return strcmp(((struct Peoinfo*)e1)->sex, ((struct Peoinfo*)e2)->sex); } int cmp_con_number(const void* e1, const void* e2) { return strcmp(((struct Peoinfo*)e1)->number, ((struct Peoinfo*)e2)->number); } int cmp_con_addr(const void* e1, const void* e2) { return strcmp(((struct Peoinfo*)e1)->addr, ((struct Peoinfo*)e2)->addr); } //通讯录排序 void SortContact(struct Contact* p) { int intput=-1; do { option(); printf("请输入排序方式的选项:\n"); scanf("%d", &intput); switch (intput) { case 0: printf("退出修改成功!\n"); break; case 1: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_name); ShowContact(p); break; case 2: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_age); ShowContact(p); break; case 3: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_sex); ShowContact(p); break; case 4: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_number); ShowContact(p); break; case 5: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_addr); ShowContact(p); break; default: printf("输入错误,请重新输入:\n"); } } while (intput); return; }
整体效果图
附上源码
- contact.h:
#define _CRT_SECURE_NO_WARNINGS #pragma once //特定数据宏定义方式,包含各种头文件,以及结构体与函数的声明 #include<stdio.h> #include<string.h> //信息接收最大数目(预处理指令便于维护) #define NAME_MAX 20 #define NUMBER_MAX 11 #define SEX_MAX 10 #define ADDR_MAX 30 #define PEOINFO_MAX 100 //个人信息结构 struct Peoinfo { char name[NAME_MAX]; int age; char sex[SEX_MAX]; int number[NUMBER_MAX]; char addr[ADDR_MAX]; }; //通讯录结构 struct Contact { //总联系人容量 struct Peoinfo data[PEOINFO_MAX]; //使用个数记录 int sz; }; //初始化通讯录(清空通讯录) void InitContact(struct Contact* p); //添加通讯录联系人 void AddContact(struct Contact* p); //删除通讯录联系人 void DelContact(struct Contact* p); //查找通讯录联系人 void SearchContact(struct Contact* p); //修改通讯录联系人信息 void ModifyContact(struct Contact* p); //展示通讯录联系人 void ShowContact(struct Contact* p); //通讯录排序 void SortContact(struct Contact* p); //操作选项 void option(); int cmp_con_name(void* e1, void* e2); int cmp_con_age(void* e1, const void* e2); int cmp_con_sex(const void* e1, const void* e2); int cmp_con_number(const void* e1, const void* e2); int cmp_con_addr(const void* e1, const void* e2);
- tast.c:
//写通讯录的整体流程和逻辑 #include "contact.h" void menu() { printf("**************************\n"); printf("****1.add 2.del****\n"); printf("****3.search 4.modify****\n"); printf("****5.show 6.sort****\n"); printf("****7.clear 0.exit****\n"); printf("**************************\n"); } //回调函数,需要传入函数地址(调用函数)和通讯录地址(操作通讯录) void Calo(void(*pc)(struct Peoinfo*i), struct Contact* p) { pc(p);//pc()为函数调用,参数传入p即通讯录地址 } //主体逻辑 int main() { int intput; struct Contact con; InitContact(&con); //写个罗盘数组,元素为函数指针(便于进行调用函数,进行传参) void(*table[8])(struct Contract* pc) = { 0,AddContact,DelContact,SearchContact,ModifyContact,ShowContact,SortContact,InitContact }; //基本逻辑循环 do { menu(); printf("请输入接下来想要进行的操作:->\n"); scanf("%d", &intput); if (intput > 0 && intput <=7 ) { printf("成功进入所选择项操作!\n"); Calo(table[intput],&con);//回调操作 } else if (intput == 0) { printf("退出操作成功!!\n"); break; } else { printf("输入项错误!请重新选择:->\n"); } } while (intput);//intput为0则退出 }
- contact.c:
//实现通讯录功能函数 #include "contact.h" //初始化通讯录 void InitContact(struct Contact* p) { p->sz = 0; //使用内存函数进行初始化数组 memset(p->data, 0, sizeof(p->data)); } //添加通讯录联系人 void AddContact(struct Contact*p) { if (p->sz == PEOINFO_MAX) { printf("通讯录联系人已满,无法添加!\n"); } else { printf("请输入新联系人的名字:"); scanf("%s", p->data[p->sz].name); printf("请输入新联系人的年龄:"); scanf("%d", &p->data[p->sz].age); printf("请输入新联系人的性别:"); scanf("%s", p->data[p->sz].sex); printf("请输入新联系人的电话号码:"); scanf("%s", p->data[p->sz].number); printf("请输入新联系人的地址:"); scanf("%s", p->data[p->sz].addr); p->sz++; printf("添加通讯录联系人%s成功!\n", p->data[p->sz].name); } return; } //删除通讯录联系人 void DelContact(struct Contact* p) { char name[NAME_MAX]; printf("请输入要删除的联系人名字:\n"); scanf("%s", &name); for (int i = 0; i < p->sz; i++) { //字符串比较采用strcmp if (strcmp(name, p->data[i].name) == 0) { for (int j = i; j < p->sz - 1; j++) { //字符串赋值采用strcpy strcpy(p->data[j].name , p->data[j + 1].name); p->data[j].age = p->data[j + 1].age; strcpy(p->data[j].sex , p->data[j + 1].sex); strcpy(p->data[j].number , p->data[j + 1].number); strcpy(p->data[j].addr , p->data[j + 1].addr); } p->sz--; printf("删除通讯录联系人%s成功!\n", name); return; } } printf("在通讯录中无法查找到联系人%s!\n",name); return; } //查找通讯录联系人 void SearchContact(struct Contact* p) { char name[NAME_MAX]; printf("请输入要进行操作的联系人名字:\n"); scanf("%s", &name); for (int i = 0; i < p->sz; i++) { if (strcmp(name, p->data[i].name) == 0) { printf("联系人的名字:%s\n", p->data[i].name); printf("联系人的年龄:%d\n", p->data[i].age); printf("联系人的性别:%s\n",p->data[i].sex); printf("联系人的电话号码:%s\n", p->data[i].number); printf("联系人的地址:%s\n", p->data[i].addr); return; } } printf("在通讯录中无法查找到该联系人!\n"); return; } //操作选项 void option() { printf("**************************\n"); printf("****1.name 2.age****\n"); printf("****3.sex 4.number****\n"); printf("****5.addr 0.exit****\n"); printf("**************************\n"); } //修改通讯录联系人信息 void ModifyContact(struct Contact* p) { int pos=-1; int intput = -1; ShowContact(p); char name[NAME_MAX]; printf("请输入要进行操作的联系人名字:\n"); scanf("%s", &name); for (int i = 0; i < p->sz; i++) { if (strcmp(name, p->data[i].name) == 0) { //找到则记录位置并进行展示 printf("联系人的名字:%s\n", p->data[i].name); printf("联系人的年龄:%d\n", p->data[i].age); printf("联系人的性别:%s\n", p->data[i].sex); printf("联系人的电话号码:%s\n", p->data[i].number); printf("联系人的地址:%s\n", p->data[i].addr); pos = i; break; } } //找不到则退出 if(pos==-1) { printf("在通讯录中无法查找到该联系人!\n"); return; } do { option(); printf("请输入要修改的选项:->\n"); scanf("%d", &intput); switch (intput) { case 0: printf("退出修改成功!\n"); break; case 1: printf("请输入新的名字:\n"); scanf("%s", &p->data[pos].name); break; case 2: printf("请输入新的年龄:\n"); scanf("%d", &p->data[pos].age); break; case 3: printf("请输入新的性别:\n"); scanf("%d", &p->data[pos].sex); break; case 4: printf("请输入新的号码:\n"); scanf("%d", &p->data[pos].number); break; case 5: printf("请输入新的地址:\n"); scanf("%d", &p->data[pos].addr); break; default: printf("输入错误,请重新输入:\n"); } } while (intput); return; } //展示通讯录联系人 void ShowContact(struct Contact* p) { //制表 printf("————————————————————————————————————————————————\n"); printf("||%20s|\t%5s|\t%11s|\t%10s|\t%30s||\n", "name", "age", "sex", "tele", "addr"); printf("————————————————————————————————————————————————\n"); for (int i = 0; i < p->sz; i++) { printf("||%20s|\t%5d|\t%11s|\t%10s|\t%30s||\n", p->data[i].name, p->data[i].age, p->data[i].sex, p->data[i].number, p->data[i].addr); printf("————————————————————————————————————————————————\n"); } return; } //qsort函数参数比较函数的实现 int cmp_con_name(const void* e1,const void* e2) { return strcmp(((struct Peoinfo*)e1)->name, ((struct Peoinfo*)e2)->name); } int cmp_con_age(const void* e1, const void* e2) { return ((struct Peoinfo*)e1)->age - ((struct Peoinfo*)e2)->age; } int cmp_con_sex(const void* e1, const void* e2) { return strcmp(((struct Peoinfo*)e1)->sex, ((struct Peoinfo*)e2)->sex); } int cmp_con_number(const void* e1, const void* e2) { return strcmp(((struct Peoinfo*)e1)->number, ((struct Peoinfo*)e2)->number); } int cmp_con_addr(const void* e1, const void* e2) { return strcmp(((struct Peoinfo*)e1)->addr, ((struct Peoinfo*)e2)->addr); } //通讯录排序 void SortContact(struct Contact* p) { int intput=-1; do { option(); printf("请输入排序方式的选项:\n"); scanf("%d", &intput); switch (intput) { case 0: printf("退出修改成功!\n"); break; case 1: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_name); ShowContact(p); break; case 2: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_age); ShowContact(p); break; case 3: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_sex); ShowContact(p); break; case 4: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_number); ShowContact(p); break; case 5: qsort(p->data, p->sz, sizeof(p->data[0]), cmp_con_addr); ShowContact(p); break; default: printf("输入错误,请重新输入:\n"); } } while (intput); return; }
动态通讯录
对于静态通讯录的实现我们用的是数组来存储数据
但是这样的通讯录存储的联系人容量是有限的(可能会少了,也可能大了)
而实现一个动态的通讯录(使用动态内存管理实现)就能避免这样的问题
对于动态通讯录的实现其实只要在静态通讯录上修改几个地方就可以了
注:动态内存管理知识后面我们会进行详细讲解