实现一个通讯录
功能:存放100个人的信息、并可以显示出来、允许增加、删除、查找、修改、排序
每个人的信息包含:名字、性别、年龄、电话、地址
步骤
打印菜单和操作选择
打印菜单和我们之前写过的两个小游戏一样,因此细节上就不再多说了,但是这一次我们要做一点小小的改动,我们要用到枚举常量来帮助增加我们代码的可读性
如果原本我们打印菜单的代码是这样的
void menu() {
printf("********************************\n");
printf("***** 1.ADD 2.DEL *****\n");
printf("***** 3.SEARCH 4.MODIFY *****\n");
printf("***** 5.SHOW 6.SORT *****\n");
printf("***** 0.EXIT *****\n");
printf("********************************\n");
}
int main() {
int input = 0;
do {
printf("请选择操作\n");
menu();
scanf("%d",&input);
switch (input)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
那么别人阅读你的代码的时候,还要去menu
函数里面看哪个数字对应哪个
但是我们倘若运用枚举常量的特性,就可以用一些有实际意义的字母替代些数字,大大增加了代码的可读性,同时也不影响代码的运行
enum Option
{ //由于我们判断循环是否继续进行要根据输入值判断,因此建议将EXIT的值设为0
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main() {
int input = 0;
do {
printf("请选择操作\n");
menu();
scanf("%d",&input);
switch (input)
{
case ADD:
break;
case DEL:
break;
case SEARCH:
break;
case MODIFY:
break;
case SHOW:
break;
case SORT:
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
数据存储
我们的通讯录,要能存储100个人的信息,那我们先把问题化小,如何存一个人的信息?
每一个人的信息包括:名字、性别、年龄、电话、地址,因此用一个类型肯定是达不到要求的 ,那么此时我们就可以想到用结构体来存储这些信息
typedef struct Peopleinfo
{
char name[20];
char gender[5];
int age;
char tele[12];
char addr[30];
}Peopleinfo;
//为了省事用了 typedef
和我们写扫雷的三子棋一样,如果将大小固定,我们后续要修改的时候就很麻烦,因此我们可以用define
常量来代表这些一般常量
#define MAX_NAME 20
#define MAX_GENDER 5
#define MAX_TELE 12
#define MAX_ADDR 30
typedef struct Peopleinfo
{
char name[MAX_NAME];
char gender[MAX_GENDER];
int age;
char tele[MAX_TELE];
char addr[MAX_ADDR];
}Peopleinfo;
解决了存储单个人数据的问题,接下来的问题就是如何存储100个人的信息,我们可以选择直接创建一个大小为100类型为Peopleinfo
的数组,但是这样我们在后续执行类似添加、删除这样的操作的时候就比较难受了。因为我们不知道你的数组内存了多少个人的信息,我们当然也可以选择写一个函数来计算,但是也不如我在开始的时候就和数组同时创建一个用于记录有效数据个数的变量
因此我们选择再创建一个结构体,用来存放大小为100的Peopleinfo
数组和有效信息个数
#define MAX 100
typedef struct Contact
{
//存储有效数据个数
int dataNum;
//存贮数据用的数组
Peopleinfo data[MAX];
}Contact;
然后在我们主函数开始时创建一个Contact
结构体即可
通讯录初始化
我们这个时候只创建了结构体,但是内部没有任何数据,为了防止出错,我们最好帮它内部的数据全部初始化为0
实际上最重要的就是把dataNum
设成0,因为后面存储要用这个数据,结构体数组是否初始化其实不影响使用
//只有接受的是地址,才能成功初始化,不要只传一个结构体过来
void InitContact(Contact* pc) {
memset(pc, 0, sizeof(*pc));
}
写好这个函数将其放在创建结构体的语句后面即可,记得一定是传址调用
增加数据
首先我们肯定要判断这个通讯录还存不存的下,那么写出来就是
void AddContact(Contact* p) {
if ((p->dataNum) < MAX) {
//添加操作
}
else {
printf("存储空间不足,请删除一些数据后再添加\n\n");
}
}
然后就是开始写添加操作,实际上也没什么技术含量,就是一个printf
和scanf
的简单结合
printf("接下来请按照指示输入各项数据\n");
printf("请输入姓名\n");
scanf("%s", (p->data)[p->dataNum].name);
printf("请输入性别\n");
scanf("%s", (p->data)[p->dataNum].gender);
//注意输入年龄这里,由于别的成员都是字符串,我们直接给它数组名就是首元素地址
//但是年龄是int类型,所以要加一个取地址符
printf("请输入年龄\n");
scanf("%d", &((p->data)[p->dataNum].age));
printf("请输入电话\n");
scanf("%s", (p->data)[p->dataNum].tele);
printf("请输入地址\n");
scanf("%s", (p->data)[p->dataNum].addr);
p->dataNum++;
printf("添加成功\n\n\n");
显示数据
首先我们可以判断一下有没有数据,没有就不让打印
然后由于我们只是打印数据,不会改变数据,所以可以在接收的参数前面可以加上const
防止误操作
同时使用一些格式操作符来进行对齐,不加负号就是右对齐,加负号就是左对齐
void ShowContact(const Contact* p) {
if (p->dataNum) {
printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
for (int i = 0; i < p->dataNum; i++) {
printf("%-10s %-5s %-4d %-12s %-30s\n",
(p->data)[i].name
, (p->data)[i].gender
, (p->data)[i].age
, (p->data)[i].tele
, (p->data)[i].addr);
}
}else{
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
搜索数据
这里我们实现的是用名字来进行搜索的功能,其他功能也可以以这个为基础修改写出来
首先,依旧是判断是否有数据,同时这个功能也不会改变源数据所以我们也加上const
void SearchContact(const Contact* p) {
if (p->dataNum) {
//查找
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
那么我们为了用名字搜索,首先肯定要创建一个临时字符串,然后去用循环依次比较,比较字符串可以用strcmp
写出来就是
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字\n");
scanf("%s", name);
int i = FindByName(p, name);
if (i != -1) {
printf("查找成功\n");
}
else {
printf("未查询到目标\n");
}
那我们既然搜索出来了,肯定也要把它打印出来让使用者看看,因此这里也需要打印
那么我们不妨想想,我们是不是能把上面的打印的代码独立出一个函数,这样代码就不会冗余了
提取出来就是下面这个函数,接收的参数是Contact*
和要打印的数据的位置,用static切断外部链接属性,因为这个函数只会在这个源文件内使用
static void printContact(const Contact* p,int num) {
printf("%-10s %-5s %-4d %-12s %-30s\n",
(p->data)[num].name
, (p->data)[num].gender
, (p->data)[num].age
, (p->data)[num].tele
, (p->data)[num].addr);
}
然后代码就变成了
void SearchContact(const Contact* p) {
if (p->dataNum) {
char tmp[MAX_NAME] = { 0 };
printf("请输入要查找人的名字\n");
scanf("%s", tmp);
for (int i = 0; i < p->dataNum; i++) {
if (!strcmp(tmp, (p->data)[i].name)) {
printf("查找成功\n");
printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
//调用打印函数
printContact(p, i);
printf("\n\n\n");
return;
}
}
printf("未查询到目标\n\n");
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
那我们刚刚分离了一个函数,我们不妨想想这个函数有没有什么可以分离出来的功能。实际上,在后面我们要写的删除和修改功能里,也需要做到先查找再删除或者修改,那么我们不妨就把这个查找功能分离出来
为了能够让原来的搜索功能函数能够正常识别是否查找到了数据,我们将返回值设置为int
,返回的是数据在Contact
数组中的位置,接收的参数为Contact*
和要查找数据的名字
注意:这个返回值即使是0
也应该是正常的,因为第一个数据的数组下标就是0
,因此我们将查找失败的返回值设置为-1
static FindByName(const Contact* p, char name[MAX_NAME]) {
for (int i = 0; i < p->dataNum; i++) {
if (!strcmp(name, (p->data)[i].name)) {
return i;
}
}
return -1;
}
那我们原来的搜索功能函数就变成了
void SearchContact(const Contact* p) {
if (p->dataNum) {
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字\n");
scanf("%s", name);
//调用查找函数
int i = FindByName(p, name);
if (i != -1) {
printf("查找成功\n");
printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
//调用打印函数
printContact(p, i);
}
else {
printf("未查询到目标\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
删除数据
首先,依旧是判断是否有数据,并且这个是会改变源数据的,不需要加上const
修饰
void DelContact(Contact* p) {
if (p->dataNum != 0) {
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
然后就是通过上面我们分离出来的查找函数去定位我们要删除的数据,代码如下
void DelContact(Contact* p) {
if (p->dataNum != 0) {
//查找
char name[MAX_NAME] = { 0 };
printf("请输入要删除人的名字\n");
scanf("%s", name);
//利用返回值判断是否存在
int pos = FindByName(p, name);
if (pos != -1) {
//删除
}
else {
printf("要找的人不存在\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
然后就是删除,我们并不能简单的将其归零,因为这样我们在打印的时候就会打印出一堆0
,这并不是我们想要的
那么有没有一种方式是可以不产生这种空缺就能删除数据的呢?
其实,我们只需要让后面的数据全部往前移动一位就好,即替换了我们想要删除的数据(也就等于删了),也让后面的数据填充了上来
这是一个常用的删除方法,请务必积累下来
但是这种删除方法会产生一个问题,就是遇到最后一位的时候我们不能再将后面的数据往前移动了,因为会越界访问。因此我们的替换操作只能执行到第MAX-1
的位置(MAX
为我们定义的最大存储量),那么最后一个位置的数据该如何处理呢?
实际上,当我们进行删除操作的时候,我们的数据数量dataNum
是会变化的,所以无论最后一个是什么都不会打印出来,并且在执行其他操作的时候也会直接替换数据,因此理论上我们可以不用管最后一个数据。
但就假如要改,我们又要怎么改呢?其实,只要当我们执行了删除操作,最后一个数据就一定会是空的,因此我们直接在删除操作执行完直接让最后一个数据归0
就行
void DelContact(Contact* p) {
if (p->dataNum != 0) {
char name[MAX_NAME] = { 0 };
printf("请输入要删除人的名字\n");
scanf("%s", name);
int pos = FindByName(p, name);
if (pos != -1) {
//移动前面的数据
while (pos < MAX - 1) {
p->data[pos] = p->data[pos + 1];
pos++;
}
//清空最后一个数据
memset(&(p->data[pos]), 0, sizeof(p->data[pos]));
//删除一个数据后dataNum--
p->dataNum--;
printf("删除成功\n");
}
else {
printf("要找的人不存在\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
修改数据
修改数据,实际上就是查找数据+添加数据的结合,没什么可多说的,直接上代码
void ModifyContact(Contact* p) {
//判断有没有数据
if (p->dataNum) {
char name[MAX_NAME] = { 0 };
printf("请输入要修改数据的人名\n");
scanf("%s", name);
//借用查找函数
int i = FindByName(p, name);
if (i != -1) {
//执行修改操作
printf("接下来请按照指示输入各项数据\n");
printf("请输入姓名\n");
scanf("%s", (p->data)[i].name);
printf("请输入性别\n");
scanf("%s", (p->data)[i].gender);
printf("请输入年龄\n");
scanf("%d", &((p->data)[i].age));
printf("请输入电话\n");
scanf("%s", (p->data)[i].tele);
printf("请输入地址\n");
scanf("%s", (p->data)[i].addr);
printf("修改成功\n\n\n");
}
else {
printf("未查询到目标\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
排序功能
首先,依旧是判断是否有数据,并且这个是会改变源数据的,不需要加上const
修饰
void SortContact(Contact* p) {
if (p->dataNum) {
//排序
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
排序我们就直接借助现有的qsort
函数,顺便回忆一下qsort
函数的使用
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
base
,指向我们要排序的数据的初始位置
num
,要排序的数据的个数
size
,要排序的数据中单个数据的大小
*compar
,指向一个如何比较两个数据的函数的指针
我们这里是按照名字排序,因此直接借用strcmp
就行
static int SortByName(void* p1,void* p2) {
return strcmp((char*)p1, (char*)p2);
}
然后把对应的数据传给qsort
,并且展示一下排序后的通讯录(直接调用前面的显示数据的函数就行)
void SortContact(Contact* p) {
if (p->dataNum) {
qsort(p->data, p->dataNum, sizeof(p->data[0]), SortByName);
printf("排序成功\n");
ShowContact(p);
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
总结
这个通讯录虽然已经完成了很多基本功能,但是依旧有一些大的缺点:
- 它的存储大小有限,不会根据需求自动开辟空间
- 它无法在程序退出后保存数据,每一次进入程序都要重新记录数据
那么之后我们又将如何解决这两个问题,就在后面的文章再做说明了
代码总览
main.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("***** 0.EXIT *****\n");
printf("********************************\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main() {
Contact con;
InitContact(&con);
int input = 0;
do {
printf("请选择操作\n");
menu();
scanf("%d",&input);
switch (input)
{
case ADD:
AddContact(&con);
break;
case DEL:
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX_NAME 20
#define MAX_GENDER 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define MAX 100
typedef struct Peopleinfo
{
char name[MAX_NAME];
char gender[MAX_GENDER];
int age;
char tele[MAX_TELE];
char addr[MAX_ADDR];
}Peopleinfo;
typedef struct Contact
{
//存储有效数据个数
int dataNum;
//存贮数据用的数组
Peopleinfo data[MAX];
}Contact;
//初始化通讯录
void InitContact(Contact* p);
//添加功能
void AddContact(Contact* p);
//展示通讯录
void ShowContact(const Contact* p);
//查找功能
void SearchContact(const Contact* p);
//删除功能
void DelContact(Contact* p);
//编辑通讯录
void ModifyContact(Contact* p);
//排序通讯录
void SortContact(Contact* p);
contact.c
#include"contact.h"
void InitContact(Contact* pc) {
memset(pc, 0, sizeof(*pc));
}
void AddContact(Contact* p) {
if ((p->dataNum) < MAX) {
printf("接下来请按照指示输入各项数据\n");
printf("请输入姓名\n");
scanf("%s", (p->data)[p->dataNum].name);
printf("请输入性别\n");
scanf("%s", (p->data)[p->dataNum].gender);
printf("请输入年龄\n");
scanf("%d", &((p->data)[p->dataNum].age));
printf("请输入电话\n");
scanf("%s", (p->data)[p->dataNum].tele);
printf("请输入地址\n");
scanf("%s", (p->data)[p->dataNum].addr);
p->dataNum++;
printf("添加成功\n\n\n");
}
else {
printf("存储空间不足,请删除一些数据后再添加\n\n");
}
}
static void printContact(const Contact* p,int num) {
printf("%-10s %-5s %-4d %-12s %-30s\n",
(p->data)[num].name
, (p->data)[num].gender
, (p->data)[num].age
, (p->data)[num].tele
, (p->data)[num].addr);
}
void ShowContact(const Contact* p) {
if (p->dataNum) {
printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
for (int i = 0; i < p->dataNum; i++) {
printContact(p, i);
}
}else{
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
static FindByName(const Contact* p, char name[MAX_NAME]) {
for (int i = 0; i < p->dataNum; i++) {
if (!strcmp(name, (p->data)[i].name)) {
return i;
}
}
return -1;
}
void SearchContact(const Contact* p) {
if (p->dataNum) {
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字\n");
scanf("%s", name);
int i = FindByName(p, name);
if (i != -1) {
printf("查找成功\n");
printf("%-10s %-5s %-4s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
printContact(p, i);
}
else {
printf("未查询到目标\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
void DelContact(Contact* p) {
if (p->dataNum != 0) {
char name[MAX_NAME] = { 0 };
printf("请输入要删除人的名字\n");
scanf("%s", name);
int pos = FindByName(p, name);
if (pos != -1) {
while (pos < MAX - 1) {
p->data[pos] = p->data[pos + 1];
pos++;
}
memset(&(p->data[pos]), 0, sizeof(p->data[pos]));
p->dataNum--;
printf("删除成功\n");
}
else {
printf("要找的人不存在\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
void ModifyContact(Contact* p) {
if (p->dataNum) {
char name[MAX_NAME] = { 0 };
printf("请输入要修改数据的人名\n");
scanf("%s", name);
int i = FindByName(p, name);
if (i != -1) {
printf("接下来请按照指示输入各项数据\n");
printf("请输入姓名\n");
scanf("%s", (p->data)[i].name);
printf("请输入性别\n");
scanf("%s", (p->data)[i].gender);
printf("请输入年龄\n");
scanf("%d", &((p->data)[i].age));
printf("请输入电话\n");
scanf("%s", (p->data)[i].tele);
printf("请输入地址\n");
scanf("%s", (p->data)[i].addr);
printf("修改成功\n\n\n");
}
else {
printf("未查询到目标\n");
}
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
}
printf("\n\n\n");
}
static int SortByName(void* p1,void* p2) {
return strcmp((char*)p1, (char*)p2);
}
void SortContact(Contact* p) {
if (p->dataNum) {
qsort(p->data, p->dataNum, sizeof(p->data[0]), SortByName);
printf("排序成功\n\n");
ShowContact(p);
}
else {
printf("数据为空,请先存放一些数据在进行该操作\n");
printf("\n\n\n");
}
}