前言:小伙伴们又见面啦!这几天通过我们对自定义数据类型的学习,我们已经掌握了如何同时对多种数据类型进行管理,那么今天这篇文章,我们就来干一件大事——实现简易的通讯录。
一.思路分析
先来想想通讯录有哪些功能:
添加联系人信息
删除联系人信息
查找联系人信息
修改联系人信息
显示联系人信息
排序联系人信息
清空所有联系人
基本上都会有以上的7种功能,而且我们需要一个菜单来让用户进行选择。
制作一个大的项目,往往都要将代码写得规范整洁。
所以我们要跟之前写三子棋和扫雷游戏一样,将宏定义常量,函数声明与定义,以及主函数分开来写,方便日后对代码的维护。
Contact即为联系人,通讯录。
Contact.h 用来管理宏定义常量、函数的声明。
Contact.c 是管理函数的定义实现。
test.c 是用来进行测试代码的功能。
二.基本框架实现
下面我们就先来一步一步的实现通讯录的基本框架。
1.目录
#include "Contact.h"
void menu()
{
printf("*********************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.revize *****\n");
printf("***** 5.show 6.sort *****\n");
printf("***** 7.empty 0.exit *****\n");
printf("*********************************\n");
}
int main()
{
int input;
do {
menu();
printf("请选择->:");
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出通讯录\n");
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
目录的做法对于现在的我们来说应该算是手到擒来,但是现在我们想要进行一个小小的改进:
如果我们做这个代码要交给其他的程序员看的话,他可能看到Switch-case语句时会很难分辨出各个case语句的数字都代表的是哪一项功能,还得不停地回到菜单函数去查看。
所以我们希望用各项功能的名字取代数字,这样就会更加方便。
那么这时候,就要用到枚举啦:
#include "Contact.h"
void menu()
{
printf("*********************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.revize *****\n");
printf("***** 5.show 6.sort *****\n");
printf("***** 7.empty 0.exit *****\n");
printf("*********************************\n");
}
enum Function
{
EXIT,//默认从0开始
ADD,
DEL,
SEARCH,
REVIZE,
SHOW,
SORT,
EMPTY,
};
int main()
{
int input;
do {
menu();
printf("请选择->:");
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出通讯录\n");
break;
case ADD:
break;
case DEL:
break;
case SEARCH:
break;
case REVIZE:
break;
case SHOW:
break;
case SORT:
break;
case EMPTY:
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
枚举常量默认从0开始,所以将exit放在第一位,而后逐个递增。
最后再将数字进行替换就好啦。
2.创建联系人
通讯录里会保存联系人的哪些信息呢???
名字、电话、住址等等,这些显然不会是一种数据类型。
所以创建联系人变量,就要用到结构体啦。
struct Peomessage
{
char name[20];
int tele[12];
char addr[30];
};
简单创建一个联系人信息的结构体,这时候又会有问题:
一个人的电话的不会超过12个数字,但是名字不会超过20个字符吗???
某一天,我认识了一个外国朋友,它的名字特别长,就要修改name数组的大小,地址同样如此。
那么为了方便高效,我们将这两个数组的大小进行宏定义。
#define NAME_MAX 20
#define ADDR_MAX 30
typedef struct Peomessage
{
char name[NAME_MAX];
int tele[12];
char addr[ADDR_MAX];
}Peomessage;
同时我们使用typedef类型重定义关键字,将此结构体类型的名字定义为Peomessage,方便我们后续的使用。
创建完联系人类型之后呢,我们就要开始创建联系人列表,这显然需要一个结构体类型的数组。
那么数组的大小是多少呢???这个又是一个无法估量的问题,所以我们仍然使用宏定义常量,
先默认能够存放100个联系人。
Peomessage data[DATA_MAX];
int sz;
我们建立了这样一个数组。除此之外,我们还需要一个整型变量来帮助我统计通讯录里有多少个联系人了,所以我们定义sz。
不难看出,这两者我们也需要同时管理,所以干脆就将它们两个也用一个结构体来管理:
typedef struct Contact
{
Peomessage data[DATA_MAX];
int sz;
}Contect;
3.初始化数据
我们创建了Contect结构体之后,还需要将其内部数据初始化为0,以免出现异常情况。
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
memset是我们之前讲过的内存函数,可以将任意位置的任意数量的数据设置成我们想要的值。
这里要讲的一点是assert函数,这个函数的作用就是帮助我们检查代码是否有错,有错误则会立即终止程序并返回错误信息。
这里主要是帮助我们判断pc指针的可用性,如果pc指针不可用,那么就会造成巨大的问题。
通过上述的步骤,我们已经实现了通讯录的基本框架,下面我们开始实现各种功能。
三.功能实现
每一个功能的实现,必然少不了对于函数的运用。
1.添加联系人信息
再添加信息之前,有一点非常值得注意:那就是我们的通讯录有没有存满。
所以我们得先进行一个判断。
void AddContact(Contact* pc)
{
assert(pc);
//判断是否已存满
if (pc->sz == DATA_MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
//没有存满则进行存放
printf("请输入名字:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入住址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
添加过一个联系人之后,我们的sz就需要+1,用来记录我们已经存放了多少个联系人的信息。
来看实践:
添加完联系人之后,我们想马上看一下我们到底有没有存进去。
下面我们就实现显示联系人信息。
2.显示联系人信息
我们这里要解释一点,显示联系人信息是显示当前已经存放过的所有联系人的信息。
而之后要讲的查找联系人信息才是针对某个联系人来显示。
那么既然要打印所有人的信息,就必然少不了对于循环的运用。
同样的,在显示之前,我们还要判断一下通讯录是否为空。
void ShowContact(Contact* pc)
{
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%s %s %s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
}
}
这样我们就可以显示出我们的联系人信息啦。
但是发现我们的信息上边没有像名字,电话,住址这样的列表名,而且我们每一行的信息排列并不整齐。
所以我们进行几处修改和补充:
void ShowContact(Contact* pc)
{
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("%-20s%-12s%-30s\n","姓名","电话","住址");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
}
}
这种打印方式不知道小伙伴们了不了解:
%s是我们常规的方式,那么%20s就是指在打印的数据之前先打印20个空格。
%-20s则代表着先打印数据,在打印20个空格,这样我们就能实现对齐啦。
3.查找联系人信息
这里我们先讲查找联系人信息。
因为删除联系人信息、修改联系人信息实现的前期是:我们必须得先找到这个人才行。
那么我们该怎么查找呢???显然通过名字是最直接的方法。而且我们需要用到循环。
既然使用名字查找这种方式到处都要用到,所以我们干脆给它也写成一个函数:
int FindByName(Contact* pc, char* name)
{
assert(pc && name);
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;
}
}
return -1;
}
这里我们这个函数的返回值为什么要是整型呢???
而且要是找到的话还返回它在通讯录列表里的位置,往下看就知道啦。
void SearchContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要查找人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("找不到该联系人\n");
return;
}
printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
}
通过定义ret来接收FindByName函数的返回值,这样我们就能得到要查找的联系人的位置,能够轻松的打印出他的信息啦。
4.删除联系人信息
删除某个联系人信息,必然也是要先查找一下这个联系人存不存在。
同时想要删除一个联系人信息,必然是通过他的名字来删除。
void DelContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要查找人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("找不到该联系人\n");
return;
}
}
接下来就是要思考怎么来删除了。
我们使用的通讯录本质上是一个数组,想要删除某个位置的信息,首先想到的就是把它置为0。
但是这样一来我们就会浪费一个空间,因为你不知道这个位置的数组下标,也不可能把一个新的联系人信息补充到这个位置上来。
所以我们采取逐个覆盖的方法,用后边的信息逐一向前一位进行覆盖。
void DelContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要删除联系人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("该联系人信息不存在\n");
return;
}
//删除联系人
int i = 0;
for (i = ret;i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
用 i 接收 FindByName 的返回值 ret ,这样我们就得到了要删除的联系人的位置,从这里开始我们通过循环进行逐一覆盖。
删除一个联系人之后,不要忘记我们的sz要-1。
5.修改联系人信息
修改联系人信息之前,我们还是要先查找一下这个联系人存不存在,如果存在则进行修改。
修改联系人信息则需要让用户选择出要具体修改哪个信息。
所以我们还需要一个小目录来供用户选择,这里的操作就和刚开始的制作目录的方法一样啦。
void menu2()
{
printf("*******************************\n");
printf("***** 0.exit 1.name *****\n");
printf("***** 2.tele 3.addr *****\n");
printf("*******************************\n");
}
enum Type
{
QUIT,
NAME,
TELE,
ADDR
};
这里为了不发生类型多次重定义,我们将“退出”用它的另一个英文“quit”来定义。
void ReviseContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要修改联系人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("该联系人信息不存在\n");
return;
}
int input;
do
{
menu2();
printf("请选择要修改的信息类型:");
scanf("%d", &input);
switch (input)
{
case QUIT:
printf("退出修改\n");
break;
case NAME:
printf("请输入名字:");
scanf("%s", pc->data[ret].name);
printf("修改成功\n");
break;
case TELE:
printf("请输入电话:");
scanf("%s", pc->data[ret].tele);
printf("修改成功\n");
break;
case ADDR:
printf("请输入地址:");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
来看实战:
6.排序联系人信息
排序联系人信息我们这里需要用到一个函数——qsort
使用这个函数需要头文件:#include<stdlib.h>
qsort函数有4个参数,分别是:
base 排序的首地址
num 排序的数据数量
size 排序的数据字节大小
compar 排序的方式函数
通讯录的排序都是通过名字的首字母来排序的,所以这里我们要比较的是两个名字的字符串,
所以我们强制类型转换之后要调用name成员来比较。
int compare(const void* a, const void* b)
{
return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
}
这里的强制类型转换用法我们在之前的文章——内存函数中已经讲到,不熟悉的小伙伴建议再去补习补习哦。
void SortContact(Contact* pc)
{
assert(pc);
qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
printf("排序成功\n");
}
我们要排序的是data这个结构体类型的数组,所以要传入它的首地址、大小、以及每个结构体数据的字节大小,最后传入我们的比较函数compare。
来看实战:
7.清空所有联系人
那么最后,如何清空所有的联系人呢???
其实这个超级简单,所谓清空,不就是再初始化一次吗,我们一开始就已经搞定啦。
void EmptyContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
四.完整代码展示
1.Contact.h
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define NAME_MAX 20
#define ADDR_MAX 30
#define DATA_MAX 100
//选项
enum Function
{
EXIT,//默认从0开始
ADD,
DEL,
SEARCH,
REVISE,
SHOW,
SORT,
EMPTY
};
enum Type
{
QUIT,
NAME,
TELE,
ADDR
};
//联系人信息
typedef struct Peomessage
{
char name[NAME_MAX];
char tele[12];
char addr[ADDR_MAX];
}Peomessage;
typedef struct Contact
{
Peomessage data[DATA_MAX];
int sz;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人信息
void AddContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);
//查找联系人信息
void SearchContact(Contact* pc);
//删除联系人信息
void DelContact(Contact* pc);
//修改联系人信息
void ReviseContact(Contact* pc);
//排序联系人信息
void SortContact(Contact* pc);
//清空所有联系人
void EmptyContact(Contact* pc);
2.Contact.c
#include"Contact.h"
//初始化通讯录
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
//增加联系人信息
void AddContact(Contact* pc)
{
assert(pc);
//判断是否已存满
if (pc->sz == DATA_MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
//没有存满则进行存放
printf("请输入名字:");
scanf("%s", pc->data[pc->sz].name);
printf("请输入电话:");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入住址:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加成功\n");
}
//显示联系人信息
void ShowContact(Contact* pc)
{
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("%-20s%-12s%-30s\n","姓名","电话","住址");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
}
}
//通过名字查找
int FindByName(Contact* pc, char* name)
{
assert(pc && name);
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(name, pc->data[i].name) == 0)
{
return i;
}
}
return -1;
}
//查找联系人信息
void SearchContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要查找人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("找不到该联系人\n");
return;
}
printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
}
//删除联系人信息
void DelContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要删除联系人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("该联系人信息不存在\n");
return;
}
//删除联系人
int i = 0;
for (i = ret;i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void menu2()
{
printf("*******************************\n");
printf("***** 0.exit 1.name *****\n");
printf("***** 2.tele 3.addr *****\n");
printf("*******************************\n");
}
//修改联系人信息
void ReviseContact(Contact* pc)
{
assert(pc);
char name[NAME_MAX];
printf("请输入要修改联系人的名字:");
scanf("%s", name);
int ret = FindByName(pc, name);
if (ret == -1)
{
printf("该联系人信息不存在\n");
return;
}
int input;
do
{
menu2();
printf("请选择要修改的信息类型:");
scanf("%d", &input);
switch (input)
{
case QUIT:
printf("退出修改\n");
break;
case NAME:
printf("请输入名字:");
scanf("%s", pc->data[ret].name);
printf("修改成功\n");
break;
case TELE:
printf("请输入电话:");
scanf("%s", pc->data[ret].tele);
printf("修改成功\n");
break;
case ADDR:
printf("请输入地址:");
scanf("%s", pc->data[ret].addr);
printf("修改成功\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
//排序联系人信息
int compare(const void* a, const void* b)
{
return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
}
void SortContact(Contact* pc)
{
assert(pc);
qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
printf("排序成功\n");
}
//清空所有联系人
void EmptyContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
printf("已清空通讯录\n");
}
3.test.c
#include "Contact.h"
void menu1()//菜单
{
printf("*********************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.revise *****\n");
printf("***** 5.show 6.sort *****\n");
printf("***** 7.empty 0.exit *****\n");
printf("*********************************\n");
}
int main()
{
Contact con;
//初始化通讯录
InitContact(&con);
int input;
do {
menu1();
printf("请选择->:");
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出通讯录\n");
break;
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case REVISE:
ReviseContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EMPTY:
EmptyContact(&con);
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
五.总结
到这里,对于如何用C语言实现简易通讯录的讲解终于完结撒花啦!!!
喜欢博主文章的小伙伴一定要点个关注不迷路哦。
最后还是要记得一键三连呀!
祝大家中秋节快乐,我们下期再见啦!