文章目录
- 前言
- 一、静态通讯录的需求分析与实现
- 1.通讯录的结构体需求
- 2.通讯录的功能需求
- 3.通讯录的主函数创建
- 4.通讯录的所用函数的实现
- 1.通讯录的初始化
- 2.通讯录的增加联系人
- 3.通讯录的查找联系人
- 4.通讯录的删除联系人
- 5.通讯录的修改联系人
- 6.联系人的排序
- 二、静态通讯录的需求分析与实现
- 1.通讯录的结构体需求
- 2.通讯录的功能需求
- 3.通讯录的所用函数的实现
- 1.通讯录的初始化
- 2.通讯录的增加联系人
- 3.通讯录的销毁
- 三、通讯录的文件操作
- 1.通讯录的加载
- 2.通讯录的保存
- 总结
前言
根据上一节的顺序表,我们来做一个小项目,就是实现一个通讯录,这个通讯录也是实现静态和动态的两个版本,并且还会加入我们所学过的文件操作,使我们的通讯录可以保存在磁盘上,也使我们可以查询以往操作的联系人的数据。话不多说,让我们快快开始吧。
一、静态通讯录的需求分析与实现
1.通讯录的结构体需求
我们先来看联系人的信息,我们要存储联系人的姓名,性别,电话,年龄,住址等等。姓名,性别,电话和住址我们分别要用一个字符数组来存储。年龄我们需要用一个int整形来存储。
这些我们要创建一个联系人的结构体来存储信息。我们在创建一个通讯录的结构体,一个结构体成员是我们的联系人结构体数组,另一个成员是我们当前联系人的个数。
#define MAX 3 //通讯录最大的容量
#define MAX_NAME 10 //联系人的姓名最大字符个数
#define MAX_GENDER 3 //联系人的性别最大字符个数
#define MAX_PHONE 15 //联系人的电话最大字符个数
#define MAX_ADDRESS 15 //联系人的住址最大字符个数
typedef struct People//联系人的信息
{
char name[MAX_NAME];//姓名
char gender[MAX_GENDER];//性别
int age;//年龄
char phone[MAX_PHONE];//电话
char address[MAX_ADDRESS];//住址
}People;
typedef struct Communicate//通讯录的结构体
{
People people[MAX];//联系人的结构体数组
int position;//通讯录中人物的个数和要插入人物的位置
}Com;
我们把存储联系人的信息结构体数组最大值设为了3,方便测试我们存满的情况,联系人的个人信息我们也设置了上限,方便我们日后修改我们的数据。我们创建联系人的结构体数组相当于我们顺序表中整形数组,只是类型不同,一个是内置类型,一个是自定义类型。但实现起来大同小异。
2.通讯录的功能需求
我们现在有了可以用来存储来联系人信息的结构体了。现在我们来看我们要实现的功能。
我们要有增加联系人的功能(我们这里直接用尾插实现),我们还要有删除联系人的功能(这里要配合查询功能实现),我们还要有查找联系人和修改联系人。最后就是我们要实现把所有联系人按照姓名排序来打印出来。
这时间我们的简易菜单和要实现的函数就出来了:
enum OPTION//选则
{
EXIT,//退出通讯录
ADD,//增加联系人
DEL,//删除联系人
FIND,//查找联系人
MODF,//修改联系人
SORT,//对通讯录的人进行姓名排序
PRIN//对通讯录的人物进行打印
};
void Initialization(Com* com);//初始话通讯录
void AddPeople(Com* com);//增加联系人
void DelPeople(Com* com);//删除联系人
void FindPeople(const Com* com);//查找联系人
void ModfPeople(Com* com);//修改联系人
void SortPeople(Com* com);//对通讯录的人进行姓名排序
void PrintPeople(const Com* com);//对通讯录的人物进行打印
我们这里用了枚举形式,方便我们选则循环时看名字直接知道我们要实现的功能。这个就是我们一会要实现的功能。我们把所需的头文件进行包含,至此我们自己的头文件的全部内容了已经完成了。当我们需要在实现其他额外的功能可以直接把我们要添加的功能函数的声明放在头文件中。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
以上是我所包含的头文件。
3.通讯录的主函数创建
主函数我们只需要实现响应的调用接口:
#include"main1.h"
void menu()
{
printf("****************************\n");
printf("**** 1.ADD 2.DEL ****\n");
printf("**** 3.FIND 4.MODF ****\n");
printf("**** 5.SORT 6.PRIN ****\n");
printf("**** 0.EXIT ****\n");
printf("****************************\n");
}
int main()
{
Com com;//创建通讯录
Initialization(&com);//初始化通讯录
int input = 0;
do
{
menu();
printf("请输入你的选则>>\n");
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("通讯录已退出\n");
break;
case ADD:
AddPeople(&com);
break;
case DEL:
DelPeople(&com);
break;
case FIND:
FindPeople(&com);
break;
case MODF:
ModfPeople(&com);
break;
case SORT:
SortPeople(&com);
break;
case PRIN:
PrintPeople(&com);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
在这里我们通过switch的选择循环语句来实现。其中我们的功能用一个菜单函数来实现,switch选择是我们的枚举类型,这样日后加入一些其他功能也方便,把我们需要的功能名称加入到枚举类型中,在我们选择结构中在加入我们所需功能的名称。这样做的好处是我们可以直接知道我们要实现的功能,而不用再去查找数字来对应我们要实现的功能。
以上就是我们主函数源文件的全部内容了。
4.通讯录的所用函数的实现
1.通讯录的初始化
在我们实现函数前要先引入我们自己的头文件。
#include"main1.h"//引入我们自己的头文件
void Initialization(Com* com)//初始话通讯录
{
assert(com);//进行断言,防止传入的指针或者地址不合法而造成的错误
com->position = 0;//开始没有元素,所以位置为0
}
初始化通讯录和我们初始化顺序表一样。当然,我们也可以选择把我们联系人的结构体也初始化一下。这里我们只需要用到一个库函数:
memset(com->people, 0, MAX*sizeof(People));//要加的代码
//函数原型:
//void *memset( void *dest, int c, size_t count );
//dest:要初始化的起始位置
//c:要初始化的内容
//count:要初始化的字节数
我们只需要在初始话函数上加上上面的代码就可以把我们联系人的结构体也进行初始化。
这是我们联系人的结构体部分内存中在执行该函数时的前后对比。
2.通讯录的增加联系人
由于我们实现的是静态的通讯录,和我们实现静态顺序表一样,需要判断我们的空间是否已经存满,只有我们在未满的时间才可以加入我们的联系人。
void AddPeople(Com* com)//增加联系人
{
assert(com);
if (com->position == MAX)//判断是否已满
{
printf("通讯录已满,无法添加\n");
return;
}
printf("请输入要添加联系人的姓名:");
scanf("%s", com->people[com->position].name);
printf("请输入要添加联系人的性别:");
scanf("%s", com->people[com->position].gender);
printf("请输入要添加联系人的年龄:");
scanf("%d", &(com->people[com->position].age));//年龄是整形,这里我们需要加上取地址符
printf("请输入要添加联系人的电话:");
scanf("%s", com->people[com->position].phone);
printf("请输入要添加联系人的住址:");
scanf("%s", com->people[com->position].address);
printf("添加成功\n");
com->position++;//对联系人进行加一
}
这里当我们添加够三个人时发现我们已经添加满了。说明我们的功能可以正常使用。
注意:我们输入年龄时要用取地址符,而其他不用取地址符,因为数组名代表数组首元素地址(当数组名单独加取地址符或数组名单独在sizeof中的情况下,数组名为整个数组的地址)。
3.通讯录的查找联系人
联系人的删除和修改都需要我们的查找功能。我们要做到函数的低耦合,高内聚。所以我们把查找函数在单独封装出来一个,这个和顺序表查找功能实现类似。
int Find(const Com* com)//查找人物,并返回该人物的下标,找不到则返回-1
{
assert(com);
char name[MAX_NAME] = { 0 };//通过姓名查找
printf("请输入要操作联系人的姓名:");
scanf("%s", name);
int i = 0;
for (i = 0; i < com->position; i++)
{
if ( 0 == strcmp(com->people[i].name, name) )//判断改联系人是否和我们要查找的联系人姓名相同
{
return i;//相同返回改联系人的下标
}
}
return -1;
}
void FindPeople(const Com* com)//查找联系人
{
int find = Find(com);//用来判断是否找到该人物
if (-1 == find)
{
printf("未找到该联系人\n");
return;
}
printf("%-10s %-5s %-5s %-15s %-10s\n", "姓名", "性别", "年龄", "电话", "住址");//打印信息
printf("%-10s %-5s %-5d %-15s %-10s\n", com->people[find].name, com->people[find].gender,
com->people[find].age, com->people[find].phone, com->people[find].address);
return;
}
我们单独封装出来一个find函数,该函数只实现查找功能。我们查找联系人的函数负责把我们查找的人物打印出来。
这里我们进行了两次操作,分别为找到和为找到的情况。
注意:我们字符串的比较不可以直接用等号来判断,而是需要用到库函数(strcmp)来实现比较。
4.通讯录的删除联系人
在实现删除联系人之前我们先实现我们的打印功能,方便我们进行观察我们是否删除成功。
void PrintPeople(const Com* com)//对通讯录的人物进行打印
{
assert(com);
if (com->position == 0)//判断我们的结构体中是否有人
{
printf("暂无联系人\n");
return;
}
int i = 0;
printf("%-10s %-5s %-5s %-15s %-10s\n", "姓名", "性别", "年龄", "电话", "住址");
for (i = 0; i < com->position; i++)
{
printf("%-10s %-5s %-5d %-15s %-10s\n", com->people[i].name, com->people[i].gender,
com->people[i].age,com->people[i].phone, com->people[i].address);
}
return;
}
打印功能和顺序表一模一样,只是把遍历整形数组改为遍历结构体数组。
现在让我们实现删除功能吧。我们首先要知道删除人的姓名,这时间就可以通过我们刚才的find函数来找到我们删除人物的下标,剩下的就是顺序表中中间删除元素的操作啦。。
void DelPeople(Com* com)//删除联系人
{
assert(com);
if (com->position == 0)//判断我们的结构体中是否有人
{
printf("暂无联系人,先添加联系人在删除吧\n");
return;
}
int find = Find(com);//查找我们要删除的人
if (-1 == find)
{
printf("未找到该联系人\n");
return;
}
int i = 0;
for (i = find; i < com->position - 1; i++)
{
com->people[i] = com->people[i + 1];//把这个联系人用后面的元素进行覆盖
}
com->position--;//对联系人的数量进行减一
printf("删除成功\n");
return;
}
根据我们上述的操作,可以看出我们的这两个功能没有什么问题。
5.通讯录的修改联系人
修改的前提还是要先找到我们的联系人
void ModfPeople(Com* com)//修改联系人
{
int find = Find(com);
if (-1 == find)
{
printf("未找到该联系人\n");
return;
}
printf("请输入要修改联系人的姓名:");
scanf("%s", com->people[find].name);
printf("请输入要修改联系人的性别:");
scanf("%s", com->people[find].gender);
printf("请输入要修改联系人的年龄:");
scanf("%d", &(com->people[find].age));
printf("请输入要修改联系人的电话:");
scanf("%s", com->people[find].phone);
printf("请输入要修改联系人的住址:");
scanf("%s", com->people[find].address);
printf("修改成功\n");
}
这里的修改就和顺序表一样,没什么可以改的,这里我们还是要调用我们的查找函数来判断是否找到。
根据我们上述的操作,可以看出我们的修改功能没有什么问题。可以完成我们的需求。
6.联系人的排序
联系人的排序我们使用的是按姓名排序,实现如下:
int compar_by_name(const void* e1, const void* e2)
{
return *((People*)e1)->name - *((People*)e2)->name;
}
void SortPeople(Com* com)//对通讯录的人进行姓名排序
{
qsort(com->people, com->position, sizeof(People), compar_by_name);
}
我这里使用的是库函数(qsort)进行排序,需要我们自己实现一个比较规则。当然也可以使用我们顺序表中的排序,不过要把那个整形比较改为结构体中姓名的比较。
这样我们静态通讯录就已经完成了。
二、静态通讯录的需求分析与实现
我们已经实现静态的通讯录,更具我们静态顺序表改动态顺序表我们也可以轻而易举的把我们的静态的通讯录改造为动态通讯录。
1.通讯录的结构体需求
typedef struct Communicate
{
People* people;
int position;//通讯录中人物的个数和要插入人物的位置
int max;//通讯录的容量
}Com;
这里我们不再需要结构体数组了,而是换成了结构体指针。增加了一个表示容量的整型值,其他的和静态顺序表一模一样。
2.通讯录的功能需求
这里我们多加入一个函数,用来实现动态开辟内存的释放。我们把这个函数放在程序退出时调用。
void Destroy(Com* com);//对通讯录进行销毁
3.通讯录的所用函数的实现
1.通讯录的初始化
void Initialization(Com* com)//初始话通讯录
{
assert(com);//进行断言,防止传入的指针或者地址不合法而造成的错误
com->position = 0;//开始没有元素,所以位置为0
com->max = 3;//初始容量赋为3
com->people = (People*)malloc(sizeof(People) * com->max);//开辟容量大小的联系人结构体
if (com->people == NULL)//开辟失败就进行报错
{
perror("malloc");
return;
}
}
在这里我们需要为我们的联系人结构体开辟一块空间,使之可以存放数据。和我们动态顺序表开辟空间一样。思路没有什么不同。
2.通讯录的增加联系人
void Expansion(Com* com)//判断是否进行扩容
{
if (com->position == com->max)
{
People* peo = (People*)realloc(com->people, sizeof(People) * com->max * 2);
if (peo == NULL)
{
perror("realloc");
return;
}
com->people = peo;
com->max *= 2;
}
}
void AddPeople(Com* com)//增加联系人
{
assert(com);
Expansion(com);//判断否进行扩容
printf("请输入要添加联系人的姓名:");
scanf("%s", com->people[com->position].name);
printf("请输入要添加联系人的性别:");
scanf("%s", com->people[com->position].gender);
printf("请输入要添加联系人的年龄:");
scanf("%d", &(com->people[com->position].age));
printf("请输入要添加联系人的电话:");
scanf("%s", com->people[com->position].phone);
printf("请输入要添加联系人的住址:");
scanf("%s", com->people[com->position].address);
printf("添加成功\n");
com->position++;
}
这里和静态通讯录相比,我们把静态通讯录中是否已满换成一个函数,这个函数用来实现扩容操作的。
3.通讯录的销毁
void Destroy(Com* com)
{
free(com->people);//释放为联系人结构体开辟的空间
com->people = NULL;//把空间指向空
com->max = 0;
com->position = 0;
return;
}
我们的销毁函数要在程序退出时调用。
至此,我们的动态通讯录就已经完成了。其他的和静态通讯录一模一样,其他无需变化。
三、通讯录的文件操作
1.通讯录的加载
void Loading(Com* com)//加载文件
{
FILE* pf = fopen("test.txt", "rb");//以二进制方式读文件
if (pf == NULL)
{
perror("read file open");
return;
}
while (fread((com->people) + com->position, sizeof(People), 1, pf))//把文件读入我们的结构体中
{
com->position++;
Expansion(com);
}
fclose(pf);
pf == NULL;
return;
}
我们把改函数放入到我们初始化函数的最后,当我们为我们结构体初始化后就可以把我们上次保存的信息加载进来了。
当我们第一次执行时:
这是我们现在还没有文件,当我们对本次操作进行保存时,下次使用就不会出现这种提示了。
2.通讯录的保存
void Save(Com* com)
{
FILE* pf = fopen("test.txt", "wb");//以二进制方式写文件
if (pf == NULL)
{
perror("write file open");
return;
}
int i = 0;
for (i = 0; i < com->position; i++)
{
fwrite((com->people) + i, sizeof(People), 1, pf);//把联系人结构体的内容写道文件中
}
fclose(pf);
pf == NULL;
return;
}
我们把改函数放入到我们销毁函数的开始,我们要在销毁我们通讯录之前进行保存。
这是我们文件保存的内容,我们使用的是二进制进行存储读写,所以里面的内容我们看不懂。
至此我们的文件版本也改造结束了。
总结
我们先实现的静态版本的通讯录,动态是在静态版本上做一点小小的改动,而文件版本又是在动态版本上加上一些内容。相信到这里你对顺序表的理解有进一步的加深,我们的通讯录也可以在加入一些其他的功能,我们做不出来是因为我们周边知识的缺失。像加入文件操作一样,当我们不知文件如何操作,就没办法改造我们动态的通讯录,随着我们学习的深入,还可以把这个通讯录改为网络版本。让我们一起进步吧。