对指针的深入运用-通讯录的初步实现
1.通讯录的功能
手机里的通讯录,是能够存放联系人的信息,包括姓名,性别,地址,电话号码,也可以加上性别。而且手机中的通讯录肯定有增删查改的功能,而且在list里是按照顺序排序的,可以按姓名的顺序来排,也可以选择其他的方式来排序。本次就来简单实现一下通讯录的基本功能。
2.功能模块化
基本框架和三子棋,扫雷一样,诸如此类比较大规模,代码比较多的项目,为便于管理和维护,不散乱,分三个文件,两个后缀为.c的文件,一个是后缀为.h的头文件。
2.1 测试文件
这一文件是该项目的主体逻辑部分,从一开始的运行,选择,程序的大框架是写在这个文件的,它保证了通讯录的顺利稳定运行。
test.c
2.2 通讯录实现部分
这一部分是具体怎么去实现通讯录功能的,是通过测试部分的选择,跳转到具体的执行过程。而具体的执行代码就是写在这一个部分。例如选择增加联系人,则会从对应的选择进入到对应函数功能中来实现如何添加联系人;要显示你目前加了多少联系人进入,那么选择显示功能,就会帮你查看此时的通讯录中有哪些信息。
contact.c
2.3 函数声明头文件
contact.h
在头文件中,要思考有哪些信息是要被重复多次使用的,有的信息为了便于修改和维护,是重新define的,防止后期修改数值要在对应的运行函数中一次次修改,这样会很浪费时间。最后就是声明函数,函数的使用,需要先声明,由于在测试文件和实现部分都需要有函数声明,才会起作用,那就在头文件写一份,然后把头文件拷贝到各个模块就可以了。
3.通讯录头文件模块(contact.h)
3.1 联系人信息定义
由于通讯录包含了联系人的信息,而联系人的信息是不是单一类型的,有整形,字符串,字符,什么可以满足这个需要,那就是自定义类型,结构体。为了使用方便可以typedf。
typedef struct people
{
char name[NameMax];
int age;
char sex[SexMax];
char adds[AddsMax];
char tele[TeleMax];
}PIO;
3.2 通讯录定义
这个项目里假定最多加100个人,就创建一个数组,数组每个元素是结构体。还有需要个数来知道目前是第几个联系人。所以这些思路转化成代码就是这样子。
typedef struct contact
{
PIO data[100];//存放人的信息数组
int sz;//个数
}contact;
3.3 define部分
为了方便日后修改,我选择把通讯的数组大小,包括联系人信息的各个数组空间都统一重新定义最大容量。以后直接在这修改数值就可以了。
#define DataMAX 100
#define NameMax 10
#define SexMax 5
#define AddsMax 20
#define TeleMax 20
3.4 各个功能初始化部分
//初始化通讯录
void init(contact* pc);
//增加联系人
void Addcon(contact* pc);
//删除联系人
void Delecon(contact* pc);
//查找联系人
void Searchcon(contact* pc);
//修改联系人
void Modifycon(contact* pc);
//显示目前联系人
void Showcon(const contact* pc);
3.5 头文件包含
在这个项目中所需要用的所有库函数的头文件需要包含,然后形成一个contact.h,测试部分和函数实现部分只需要包含一个包含了所有库函数的文件就行了,用#include""写。(后面简写为总头文件)
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
4.通讯录主体实现模块(test.c)
4.1 插入语
头文件的部分讲完后,那么就可以讲述测试部分了,在实际写这个项目的时候,正确的顺序应该是先写测试部分,然后需要用到什么库函数,考虑到哪些函数是需要调用多次的,都会声明在头文件中,包括为了方便维护的定义的内容。不是写完再测试,而是是写一部分就测试一下,看看这一部分有没有问题,因为内容一多,检查起来就比较难,可能要改很多。所以一点点来写,一点点测。比如先写框架,增删查改先不写,框架搭建完,测试没有出现问题,再写一个增,通讯录实现部分对应写一个增功能的代码,实现一个写下一个。大的项目都是由一个个小代码块组成的。
4.2 测试模块
通讯录的基本思路是:
- 创建通讯录
- 初始化
- 选择通讯录功能
- 结束使用
创建和初始化之后,我们就要使用了,那使用就要多个选项,告诉我们可以做什么,就像扫雷和三子棋一样,只不过它们是开始和退出。这里是多功能化。可以增删查改显示(目前仅这些功能,后续功能升级敬请期待~)。那就使用switch语句了,而一开始就要弹出功能选择,可以用do while来实现,键盘输入来选择,遇到0退出。很简单的逻辑是不是?来看代码
void menu()
{
printf("-------1.Add---------\n");
printf("-------2.Delete------\n");
printf("-------3.Search------\n");
printf("-------4.Show--------\n");
printf("-------5.Modify------\n");
printf("-------0.Exit--------\n");
}
int main()
{
int input = 0;
//创建通讯录
contact con;
//初始化
init(&con);
do
{
menu();
printf("请选择通讯录功能:\n");
scanf("%d", &input);
switch (input)
{
case 1:
Addcon(&con);
break;
case 2:
Delecon(&con);
break;
case 3:
Searchcon(&con);
break;
case 4:
Showcon(&con);
break;
case 5:
Modifycon(&con);
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("无该功能,请重新输入\n");
break;
}
} while (input);
return 0;
}
和之前写的其实没有啥太多区别,就是功能增多了而已 。接下来才是重头戏,通讯录使用流程,功能框架都有了,那就是该完善每个功能的实现和细节问题了。
5. 通讯录具体实现部分
创建了一个通讯录后是要初始化的,就像创建棋盘后初始化它,起初个数肯定是0,然后把联系人数组里的数据全部初始化为0,可以用for循环,这里我选择用内存函数来重置内容全部为0。
//初始化通讯录
void init(contact* pc)
{
//下标为sz的设置为0
pc->sz = 0;
//data里面的信息全部初始化为0
memset(pc->data,0,sizeof(pc->data));
}
初始化之后根据test.c一步步走,就是显示一个菜单,让我们选择增删查改&显示功能,一个个个写
5.1 增加(Addcon)
函数传过去一个通讯录的地址,然后用指针去接收,由于是个指针,所以严谨起见,在进行操作之前先assert断言,防止是个空指针,是空指针就会报错。接着判断通讯录是不是已经满的,虽然大概率是不会的,但还是得判断个数满了没有,没满就可以进行添加了,按照顺序添加信息,注意的是输入信息时,数字录入要取地址符,其他不用,根据结构体里的信息,数字age是变量,其他是字符数组,数组名就是首元素地址。写一个个数就++。
void Addcon(contact* pc)
{
assert(pc);
if (pc->sz==100)
{
printf("通讯录已满");
return ;
}
//添加联系人信息
else
{
printf("请输入名字:\n");
scanf("%s",pc->data[pc->sz].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别:\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[pc->sz].adds);
}
pc->sz++;
}
5.2 删除(Delecon)
依旧传过来一个地址,用指针接收,断言,这两步时增删查改都要写的,要严谨。删除的函数,需要判断一开始有没有数据存放在里面,没有的话就没有数据供我删除,这里可以结合显示界面观察(后面会写)。判断完就看想要删除哪个就选择哪个,先找到要删除的人的下标,之后删除,删除的思想有两种方法。
- 用数据前移的思想,比如删除第3个,就把数组下标为3的联系人信息前挪,替换到下标为2的。然后循环,直到最后一个数据也往前移动,不要忘了个数-1。
- 由于删除的对象是内存数据,所以也可以用memmove这个内存函数,memmove函数的解释如下:

所以只要找到要删除的联系人的下标,即为dest,那source不就是dest+1吗,字节大小为个数*一个元素的大小,是一个结构体元素的大小,也就是sizeof(pc->data[0]) 。当然也不要忘记个数--,转为代码就是
void Delecon(contact* pc)
{
assert(pc);
if (pc->sz==0)
{
printf("通讯录为空,无法删除\n");
}
//删除
else
{
//要删除的人是
printf("想要删除的姓名:>\n");
char name[NameMax] = {0};
scanf("%s",name);
int ret = FindName(pc,name);
//把要删除的人姓名的下标找到,并替换
int i = 0;
if (-1 == ret)
{
printf("要删除的人不存在\n");
return;
}
else
{
memmove(&(pc->data[ret]), &(pc->data[ret + 1]),(pc->sz-ret)*sizeof(pc->data[0]));
pc->sz--;
printf("删除成功\n");
}
}
}
5.2.3 "找人"函数单独封装
这里在查找联系人信息的时候,我是想到除增加,删除,查找和修改都要先找到再进行后续操作,所以干脆就把查找人这个功能写一个函数封装起来,一来可以多次调用,增加代码复用率,便于维护。二来减代码量,不用每次需要在写一次,那很冗余。这个函数声明写在头文件与否都可以,看自己,我的这个代码是写在删除功能的上面,函数的定义也是一种特别的声明。如果没写在头文件就最好写在要调用函数的前面,不然会报warning。
int FindName( const contact *pc,char name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;
}
5.3 查找(Searchcon)
在手机通讯录里我们肯定有搜索功能去搜索特定的对象,如图

那么在这个通讯录项目中,也要对应的查找,查找特定的联系人并且显示,那么思路是先确定有没有这个人,有的话才能显示是吧,所以先调用"找人"函数,找到了,返回值就是该联系人的下标,通过指针指向这个数组的下标位置,打印出来,不过这个通讯录有局限性,只能显示对应的一个联系人,不能将有包含关键字的所有信息都打印出来。
void Searchcon(contact* pc)
{
assert(pc);
printf("请输入你想查找的人:\n");
char name[NameMax] = {0};
scanf("%s",name);
int ret = FindName(pc, name);
if (ret ==-1)
{
printf("要查找的人不存在\n");
return ;
}
else
{
printf("%-10s\t%-4s\t%-5s\t%-20s\t%-12s\n","姓名","年龄","性别","地址","电话号码");
printf("%-10s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[ret].name,
pc->data[ret].age,
pc->data[ret].sex,
pc->data[ret].adds,
pc->data[ret].tele);
}
}
5.4 修改(Modifycon)
修改的思路也是和删除查到一样,先确认是否有这个人,然后才可以"编辑"。编辑就是对这个位置返回的坐标重新输入。
void Modifycon(contact* pc)
{
assert(pc);
char name[NameMax] = {0};
printf("请输入你要修改的联系人\n");
scanf("%s", name);
int ret = FindName(pc, name);
if (-1==ret)
{
printf("要修改的人不存在\n");
return;
}
else
{
printf("请输入名字:\n");
scanf("%s", pc->data[ret].name);
printf("请输入年龄:\n");
scanf("%d", &(pc->data[ret].age));
printf("请输入性别:\n");
scanf("%s", pc->data[ret].sex);
printf("请输入电话:\n");
scanf("%s", pc->data[ret].tele);
printf("请输入地址:\n");
scanf("%s", pc->data[ret].adds);
}
}
5.5 显示界面
显示界面就是会显示你目前联系人个数中所有人的信息记录,并且使用qsort排序,按照名字顺序来排列。
void Showcon(const contact* pc)
{
assert(pc);
qsort(pc->data,pc->sz,sizeof(PIO),compare);
int i = 0;
printf("%-10s\t%-4s\t%-5s\t%-20s\t%-12s\n", "姓名", "年龄", "性别", "地址", "电话号码");
for ( i = 0; i <pc->sz; i++)
{
printf("%-10s\t%-4d\t%-5s\t%-20s\t%-12s\n",pc->data[i].name, pc->data[i].age,
pc->data[i].sex,
pc->data[i].adds,
pc->data[i].tele);
}
}
这是qsort库函数中的比较函数,是按名字来排序,可以自行更改,比如按照年龄。
int compare(const void* a, const void* b)
{
return strcmp(((PIO*)a)->name, ((PIO*)b)->name);
}
6.总结
以上就是我对这个基础版本的通讯录功能实现的详细介绍,后续还会衍生其他功能,因为这个版本的通讯录还是有很多不足,因为这个程序是内存中运行,我只能使用一次,用完一次,程序结束所有数据都不会保存在本地文件,第二次打开就是空的,不会保存内容,而手机中添加联系人是会保存在通讯录中的。第二个就是还没考虑扩容问题,比如我只能保存限定设置的人数个数,超出就不行,后面还会考虑扩容功能。敬请期待。
本次内容就分享到这,如果有错误请指正。