这是一篇介绍通讯录实现的博客,采用c语言。包含有实现的简单思路、思考方向、代码以及笔者的一些小经验,希望可以帮助到大家,如有错误,还望大佬不吝赐教。 文章稍长,若仅需要某一部分功能,目录可轻易到达。
一、实现思路
1、联系人采用结构体
- 定义结构体,组成一个人的各类信息;
- 定义sz,确定已收录的人员数目;
- 人员之间采用顺序表,便于查找;
2、主要功能
-
增加 用于在通讯录中,增加联系人; 因采用顺序表实现,只需访问到结构体中合适的位置,故只需一个下标即可,而sz的数目正好指向的顺序表添加元素的下一个位置;
-
删除 删除条目,需后续位置的信息往前挪,随后修改sz便可;
-
查询 逐一与结构体中的元素比对,利用strcmp便可实现;
-
修改 若存在元素,则利用自定义的查询函数找到下标,进行修改;
-
排序 运用qsort排序函数与自定义的比较函数,可快速实现;
-
打印 for循环打印便可轻易满足;
二、函数实现及思路
1、结构体的定义
- 采用define确定结构体的成员大小,便于后续调整;
- 双层结构体,sz定义在data外部,节省内存空间;
//类型声明,结构体各成员的大小
#define NAME_MAX 20
#define SEX_MAX 5
#define TEL_MAX 15
#define ADDR_MAX 30
//联系表大小
#define MAX 1000
//联系人结构体
typedef struct Peo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}Peo;
//联系表结构体
typedef struct Contact
{
Peo data[MAX];
int sz;
}Contact;
2、主要功能的实现
a、增加
- 由于顺序表空间有限,需进行判断;
- 表中存在 sz 个元素时,data 中 sz 下标的位置正好是 sz+1 个元素的位置,因此以sz作为下标,便可轻易实现添加;
//增加联系人
void Add_Contact(Contact* con)
{
assert(con);
if (con->sz == MAX)
{
printf("通讯录已满!不可添加");
return;
}
printf("请输入名字:");
scanf("%s", con->data[con->sz].name);
printf("请输入性别:");
scanf("%s", con->data[con->sz].sex);
printf("请输入年龄:");
scanf("%d", &(con->data[con->sz].age));
printf("请输入电话:");
scanf("%s", con->data[con->sz].tel);
printf("请输入地址:");
scanf("%s", con->data[con->sz].addr);
con->sz++;
printf("添加成功!\n");
}
b、删除
- 由于删除需要确定删除的具体位置,先定义一个函数,以名字确定位置,若存在则返回下标,不存在返回-1;
- 删除时也需考虑联系表为空的情况,故需要判断;
- 存在要删除的人不存在的情况,因此以if语句进行判断;
//以名字搜索
int Search_By_Name(Contact* con, char* cmp)
{
int i = 0;
for (i = 0; i < con->sz; i++)
{
if (strcmp(cmp, con->data[i].name) == 0)
{
return i;
}
}
return -1;
}
//按名字删除
void Del_By_Name(Contact* con)
{
assert(con);
if (con->sz == 0)
{
printf("通讯录为空!不可删除\n");
return;
}
char cmp[20] = { 0 };
printf("请输入要删除的人:");
scanf("%s", cmp);
int x = Search_By_Name(con, cmp);
if (x != -1)
{
for (; x < con->sz; x++)
{
con->data[x] = con->data[x + 1];
}
con->sz--;
printf("删除成功!\n");
}
else
{
printf("要删除的人不存在!\n");
}
}
c、查询
- 自定义的以名字查询的函数在实现删除功能时已实现,因此此处便较为方便;
- 查询成功后,需要打印对应联系人的信息,考虑到后续打印一个人的信息的功能会常用,因此,单独封装成函数;
- 联系表为空时不可查询,故增加判断;
- 查抄的人可能不存在,同样进行判断;
//打印指定下标联系人
void PrintP(const Contact* con, int i)
{
printf("%20s %5s %5d %15s %30s\n", con->data[i].name, con->data[i].sex, con->data[i].age, con->data[i].tel, con->data[i].addr);
}
//查找
void Search(const Contact* con)
{
assert(con);
if (con->sz == 0)
{
printf("通讯录为空,不可查找\n");
}
int i = 0;
char cmp[20] = { 0 };
printf("请输入要查找的人:");
scanf("%s", cmp);
i = Search_By_Name(con, cmp);
if (i != -1)
{
printf("信息如下:\n");
printf("%20s %5s %5s %15s %30s\n", "姓名", "性别", "年龄", "电话", "住址");
PrintP(con, i);
}
else
{
printf("查找的人不存在!\n");
}
}
d、修改
- 修改前,首先需要查询此联系人;
- 修改需要确定修改的内容,需确定选项;
- 以switch语句修改对应的内容;
//修改选项
void Mmenu()
{
printf("************************************\n");
printf("******* 1、Name 2、Sex *******\n");
printf("******* 3、Age 4、Tel *******\n");
printf("******* 5、Addr 0、Exit ******\n");
printf("************************************\n");
}
//修改选项
enum Option2
{
Exit,
Name,
Sex,
Age,
Tel,
Addr
};
//修改函数
void Modify(Contact* con)
{
assert(con);
if (con->sz == 0)
{
printf("通讯录为空!\n");
}
char cmp[20] = { 0 };
printf("请输入要修改的人的名字:");
scanf("%s", cmp);
int i = Search_By_Name(con, cmp);
if (i == -1)
{
printf("此人不存在!\n");
}
else
{
int input = 0;
do
{
Mmenu();
printf("%20s %5s %5s %15s %30s\n", "姓名", "性别", "年龄", "电话", "住址");
PrintP(con, i);
printf("请输入修改选项:");
scanf("%d", &input);
switch (input)
{
case Name:
printf("请输入新名字:");
scanf("%s", con->data[i].name);
break;
case Sex:
printf("请输入性别:");
scanf("%s", con->data[i].sex);
break;
case Age:
printf("请输入年龄:");
scanf("%d", con->data[i].age);
break;
case Tel:
printf("请输入新电话:");
scanf("%s", con->data[i].tel);
break;
case Addr:
printf("请输入新住址:");
scanf("%s", con->data[i].addr);
break;
case Exit:
printf("退出修改\n");
break;
default:
break;
}
} while (input);
}
}
e、排序
- 借用qsort函数,排序的实现较为简单;
- 自定义函数的实现
//按名字比大小
int cmp_by_name(const void* e1, const void* e2)
{
return strcmp(((Peo*)e1)->name, ((Peo*)e2)->name);
}
//按年龄比大小
int cmp_by_age(const void* e1, const void* e2)
{
return ((Peo*)e1)->age - ((Peo*)e2)->age;
}
//排序选项
void Smenu()
{
printf("************************************\n");
printf("******* 1、Name 2、Age *******\n");
printf("************************************\n");
}
//通讯录排序
void Sort(Contact* con)
{
assert(con);
int input = 0;
Smenu();
printf("请选择要排序的方式:");
scanf("%d", &input);
int (*pc[3])(const void* e1, const void* e2) = { 0 , cmp_by_name, cmp_by_age };
qsort(con->data, con->sz, sizeof(Peo), pc[input]);
}
f、打印
- 借用在查询时定义的打印单个联系人的函数,定义一层for循环,便可满足要求;
//打印通讯录
void PrintC(const Contact* con)
{
assert(con);
if (con->sz == 0)
{
printf("通讯录为空!\n");
}
int i = 0;
printf("%20s %5s %5s %15s %30s\n", "姓名", "性别", "年龄", "电话", "住址");
for (i = 0; i < con->sz; i++)
{
PrintP(con, i);
}
printf("***** 打印完成 *****\n");
}
三、小tips与头文件
1、枚举的使用
枚举函数的第一个值默认为0,因此,借用枚举函数可以在提高代码可读性的同时,不影响性能;
2、优化方向
- 对用户的输入没有限制,也未对用户输入进行判断;
- 联系表的大小不可轻易改变,借用链表,可以实现联系表的动态增加;
3、头文件
//头文件
#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
//枚举提高代码可读性
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
//类型声明,结构体各成员的大小
#define NAME_MAX 20
#define SEX_MAX 5
#define TEL_MAX 15
#define ADDR_MAX 30
//联系表大小
#define MAX 1000
//联系人结构体
typedef struct Peo
{
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}Peo;
//联系表结构体
typedef struct Contact
{
Peo data[MAX];
int sz;
}Contact;
//函数声明
//菜单
void menu();
//初始化
void Init_Contact(Contact* con);
//增加联系人
void Add_Contact(Contact* con);
//删除联系人
void Del_By_Name(Contact* con);
//搜索联系人
void Search(Contact* con);
//打印通讯录
void PrintC(Contact* con);
//修改联系人
void Modify(Contact* con);
//排序
void Sort(Contact* con);
//测试函数
int main()
{
int input = 0;
Contact con;
Init_Contact(&con);
do
{
menu();
printf("请输入选项:");
scanf("%d", &input);
switch (input)
{
case ADD:
Add_Contact(&con);
break;
case DEL:
Del_By_Name(&con);
break;
case SEARCH:
Search(&con);
break;
case MODIFY:
Modify(&con);
break;
case SORT:
Sort(&con);
break;
case PRINT:
PrintC(&con);
break;
case EXIT:
printf("退出通讯录");
break;
default:
break;
}
} while (input);
}