实现通讯录主要使用到结构体和指针相关知识,这些知识在我的博客中都有介绍,欢迎观看。
源码:Contact · 斯文/mytest - 码云 - 开源中国 (gitee.com)
目录
1. 了解需求
首先我们了解一下需求:
1.1 可以保存1000个联系人信息,包括姓名,号码。
1.2 需要实现的功能:1增加,2删除,3修改,4查找,5排序,6打印,7退出
2. 代码实现
我选择将代码分为三个部分来实现,
1. contact.g 用于声明 包括创造结构体的说明,功能函数的声明,枚举,#define定义的标识符常量
2. contact.c 用于实现具体的函数功能,比如增加,删除,一系列的自定义函数。
3. test.c 主函数,调试程序
代码封装为3个部分,更加合理,便于调试,更有条理性。
2.1 创建合适的结构体变量来保存联系人信息。
typedef struct peoinfo
{
char name[NAME_MAX];
char tele[TELE_MAX];
}peoinfo;
创造一个结构体,包含俩个字符数组,用来存放联系人姓名和号码。但是字符数组的大小最好是通过 #define 定义的标识符常量,便于程序的创作及调试和后续如果有修改字符数组大小的需要。
#define NAME_MAX 7
#define TELE_MAX 12
2.2 创建通讯录
虽然我们通过第一个结构体,可以保存一个联系人信息。但是如果直接使用这个结构体创造1000个变量,显然过于繁琐,也不太实际。
#define MAX 1000
typedef struct contact
{
peoinfo data[MAX];
int sz;
}contact;
我们选择使用结构体嵌套,结构体中,包含用联系人结构体创造的数组,数组的大小同样使用#define 定义。同时,为了掌握我们具体存入了多少个联系人信息,我们创建整形变量sz来保存。(关于结构体嵌套,我的关于结构体的博客有详细解释)
2.3 初始化通讯录
创造完通讯录后,其中的内容,比如sz都是随机值,我们需要将其重置为0.
contact con;
Init_Contact(&con);
创造结构体 con ,使用初始化函数时需注意,我们不推荐直接传整个结构体,数据太大,没有必要,,而且 形参只是实参的一份临时拷贝,对于形参的修改不会影响实参,所以我们更推荐使用传递结构体的地址。
void Init_Contact(contact* pc)
{
pc->sz = 0;
memset((pc->data), 0, sizeof(pc->data));
}
使用结构体指针来接收参数,使用memset函数来初始化联系人数组的数据。
2.3 打印菜单,switch case 语句,实现选择。
enum option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT,
};
void menu()
{
printf("*************************************\n");
printf("****** 1.add 2.del ******\n");
printf("****** 3.search 4.modify ******\n");
printf("****** 5.sort 6.print ******\n");
printf("****** 0.exit ******\n");
printf("*************************************\n");
}
void test()
{
int input = 0;
contact con;
Init_Contact(&con);
do
{
menu();
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 MODIFY:
Modifycontact(&con);
break;
case SORT:
Sortcontact(&con);
break;
case PRINT:
Printcontact(&con);
break;
default:
printf("输入错误,重新输入\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
值得一提的是,我们可以使用枚举常量,来代替数字,增加代码的关联和可读性。
2.4 增加函数的实现
void Addcontact(contact* pc)
{
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入号码:>");
scanf("%s", pc->data[pc->sz].tele);
pc->sz++;
printf("添加成功\n");
}
同样的,我们使用结构体指针来接收,但是要注意的是,当我们记录第一个联系人信息时,sz为0,我们增加信息时,可以直接用sz作为下标 pc->data[pc->sz].name,增加信息完成后,sz++ .
2.5 查找函数的实现
我们首先封装一个搜索函数,因为无论是修改,删除,查找函数,都需要搜索到这个数据,因此我们直接封装一个搜索函数,其他部分的函数也可以使用。
//封装一个查找函数 找到了返回下标,没找到返回 -1
int FindByName(contact* pc, char arr[])
{
int j = 0;
for (j = 0; j < pc->sz; j++)
{
if (0 == strcmp(pc->data[j].name, arr))
{
return j;
}
}
return -1;
}
我们是通过姓名来查找,比较字符串是否相等,使用 strcmp 函数(之前的博客有分析)
查找函数
void Searchcontact(contact* pc)
{
assert(pc);
printf("请输入要查找联系人的姓名:>");
char arr[NAME_MAX] = { 0 };
scanf("%s", arr);
int b = FindByName(pc, arr);
if (b == -1)
{
printf("未找到联系人\n");
return;
}
printf("%7s,%12s", "姓名", "号码");
printf("%7s %12s\n", pc->data[b].name, pc->data[b].tele);
}
上面代码中 %7s ,7的作用是以指定宽度来打印,实现右对齐,如果我们想实现左对齐,数字前加负号
2.6 删除函数
void Delcontact(contact* pc)
{
assert(pc);
//查找
char arr[NAME_MAX] = { 0 };
printf("请输入姓名:>");
scanf("%s", arr);
int b = FindByName(pc, arr);
//删除
if (b == -1)
{
printf("未找到联系人\n");
return;
}
int j = 0;
for (j = b; j < pc->sz - 1; j++)
{
pc->data[j] = pc->data[j + 1];
}
pc->sz--;
printf("删除成功。\n");
}
找到联系人信息后,我们可以选择将后一个数据复制到当前的位置上,依次类推,将后面所以的数据前移一个,实现删除功能。同时减少sz 。 但是我们实现循环时,需要注意避免越界访问。
当我们需要删除的数据在下标为999,即最后一个元素时,我们可以看到后面没有数据前移。但是当sz减一时,就自动将最后一个数据重置。这样既删除了数据,又不会造成数据的越界访问。
我们在设计时,应该仔细考虑边界问题,实现功能的同时保证没有错误
2.7 修改函数
void Modifycontact(contact* pc)
{
printf("请输入需要修改联系人的姓名:>");
char arr[NAME_MAX] = { 0 };
scanf("%s", arr);
int b = FindByName(pc, arr);
if (b == -1)
{
printf("未找到联系人\n");
return;
}
printf("请输入修改后的姓名:>");
scanf("%s", pc->data[b].name);
printf("请输入修改后的联系人电话号码:>");
scanf("%s", pc->data[b].tele);
printf("修改完成\n");
}
找到数据后,直接输入,然后覆盖源数据。
2.8 排序函数
我选择使用qsort函数实现
//排序 qsort排序
void cmp_by_name(const void* p1, const void* p2)
{
return strcmp(((contact*)p1)->data->name, ((contact*)p2)->data->name);
}
void Sortcontact(contact* pc)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);
printf("排序成功.");
}
后面我会出一个qsort函数的博客,先不做分析(很快就出)
2.9 打印函数
oid Printcontact(contact* pc)
{
printf("%-7s,%-12s\n", "姓名","号码");
int j = 0;
for (j = 0; j < pc->sz; j++)
{
printf("%-7s %-12s\n", pc->data[j].name, pc->data[j].tele);
}
}
退出函数,比较简单,do while() 括号中 为输入,输入为0时直接退出。
本文中的函数很多用指针来接收地址,因为他们的数据都是数组,数据名就是地址,也就没有用&
其他类型的数据传地址时记得使用哈。
3.优化为动态版本通讯录
本文介绍的为静态版本,较为基础,有较多优化空间,后续会分享不同的优化版本。
动态版本博客介绍:http://t.csdn.cn/snW0G
源码分享:Contact2.0 · 斯文/mytest - 码云 - 开源中国 (gitee.com)
全文结束,谢谢观看!如果有什么不同的见解,评论区欢迎讨论。