1.前言
前段时间用了固定长度的数组以及动态增长空间的结构体指针构建了通讯录信息保存系统,这次发现有柔性数组这个好玩的东西,于是这次用柔性数组试着构建一下通讯录;
上两次通讯录链接:链接: link.链接: link.
2.效果展示
3.功能介绍
4.功能实现过程
4.1保存结构
两个结构体,一个用来记录当前的成员个数和空间容量,另外一个结构体即储存成员信息;
typedef struct List //储存成员信息
{
char name[20];
char sex[20];
int age;
char tel[20];
char address[50];
}list;
typedef struct Preson
{
int capacity;//容量
int size;//当前人数
list people[0];//结构体柔性数组,存储客户信息结构体
}preson;
4.2菜单函数Menu()的实现比较简单,直接贴代码
void Menu()
{
printf("###1.添加联系人信息############2.删除指定联系人信息####################\n");
printf("###3.查找指定联系人信息#########4.修改指定人信息#######################\n");
printf("###5.显示所有联系人信息#########6.清空所有联系人#######################\n");
printf("###7.以名字排序所有联系人#######8.退出#################################\n");
}
4.3开辟空间函数Open()的实现
preson(记录成员个数的结构体)结构体只需要开辟一个大小为sizeof(preson)空间,因为只需要保存成员个数和空间容量的大小,柔性数组在结构体中是不占大小的;
因此开辟的时候多余的空间都是分配给柔性数组的;
记住传二级指针,传一级指针是不可以能够改变外部结构体指针的内容的;(这个在后续就不再重复啰嗦了)
void Open(preson **peo)//开辟空间函数
{
(*peo)= (preson*)malloc(sizeof(preson)+sizeof(list)* 2);//柔性数组在结构体中不占据空间,因此需要额外开辟空间
if (*peo== NULL)
{
printf("开辟空间失败\n");
exit(-1);
}
(*peo)->size = 0;//记录当前成员数
(*peo)->capacity = 2;//记录总空间容量
}
4.4空间判定函数Judge()
这个函数并不需要被其它源文件读取,带上static是一种良好的习惯;
这个函数存在的意义是判定当前成员个数以及空间容量,假如空间容量不够了就增加空间;
你可能会看到有个奇怪的 Open(peo),不仅仅在这里,在其余的函数内也大都具有,这个我稍后解释;
还有需要注意的点是空间增容完毕后记得将空间容量的值变大;
static void Judge(preson **peo)//进行当前空间容量判定
{
preson *cur = *peo;
if (cur == NULL)//清空联系人后再添加
{
Open(peo);
}
else if (cur->capacity == cur->size)//当前成员数和空间容量相等代表通讯录容量满了
{
cur = (preson *)realloc(cur, sizeof(preson)+sizeof(list)*(cur->capacity) * 2);//增多了浪费,增少了麻烦,每次增加当前容量的两倍
if (cur!= NULL)
{
printf("增容成功\n");
cur->capacity = 2 * cur->capacity;
printf("增容成功\n");
*peo = cur;
}
else
exit(-1);
}
}
4.5联系人添加函数Add()
这里需要注意的是每次添加数据前都需要进行一次判定,因为你不确定空间的容量是否足够;
还有一个细节非常容易遗忘,柔性数组中保存的是结构体,访问成员的时候除了年龄成员变量,其余成员变量全是数组,因此输入参数的时候不需要取地址,输入年龄的时候记得将&带上;
还有在函数的末尾别忘记成员计算器size+1;
void Add(preson **peo)//添加联系人信息
{
Judge(peo);
preson *sp = *peo;
printf("请输入联系人姓名\n");
scanf("%s", sp->people[sp->size].name);
printf("请输入联系人性别\n");
scanf("%s", sp->people[sp->size].sex);
printf("请输入联系人年龄\n");
scanf("%d", &(sp->people[sp->size].age));//这里一定要取地址,因为这个表达式对应的是数组中的某个值
printf("请输入联系人电话\n");
scanf("%s", sp->people[sp->size].tel);
printf("请输入联系人地址\n");
scanf("%s", sp->people[sp->size].address);
sp->size++;//成员个数+1
}
4.6寻找联系人函数Found()
我这里采用的是名字,名字肯能会重复,可以改用为电话号码
这个函数的返回值为什么是int呢,这个和下面的一些函数的实现有关系,为了避免代码冗余下面的函数会调用到这个函数;
用strcmp比较名字是否相同,相同strcmp的返回值为0,不同位非零;
int Found(preson **peo)//找到并打印联系人信息
{
if (*peo== NULL)
{
Open(peo);
}
preson *sp = *peo;
printf("请输入名字\n");
char name[20];//临时储存输入的名字
scanf("%s", name);
int i = 0;
for (i = 0; i < sp->size; i++)
{
if (strcmp(sp->people[i].name, name) == 0)//找到了
{
printf("姓名:%s 性别:%s 年龄:%d 电话:%s 地址:%s\n", sp->people[i].name, sp->people[i].sex,
sp->people[i].age, sp->people[i].tel, sp->people[i].address);
break;
}
}
if (i == sp->size || sp->size == 0)//上面循环跑完了或者成员个数为零时代表没有这个人;
{
printf("没有这个人,请确认你的输入信息是否正确\n");
return -1;
}
else
return i;
}
4.7删除联系人函数Delect()
删除一个人之前,得先找到他,没错这里调用了上面的Found函数;
删除的方式是假如不是最后面一个就利用覆盖的办法,直接将数组后面的元素覆盖前面的元素,假如是最后一个直接将size-1,因为下次新数据的输入直接就将其覆盖了;
void Delect(preson **peo)//删除联系人信息
{
int sub = Found(peo);
preson *sp = *peo;
if (sub!=-1)
{
while (sub < sp->size-1)
{
sp->people[sub] = sp->people[sub + 1];//覆盖掉
sub++;
}
sp->size--;//成员减1
printf("删除成功\n");
}
}
4.8修改联系人信息函数Alter()
这里还是需要调用Found找到该联系人,然后利用switch函数让我们有选择性的修改联系人的属性;
void Alter(preson **peo)//修改联系人信息
{
int sub=Found(peo);
preson *sp = *peo;
int select = 0;
int quit = 0;
if (sub != -1)
{
while (!quit)
{
printf("请选择你要修改联系人什么信息\n");
printf("##1.姓名#############2.性别#######\n");
printf("##3.年龄#############4.电话#######\n");
printf("###5.地址############6.退出#######\n");
printf("#################################\n");
scanf("%d", &select);
switch (select)
{
case 1:
printf("请输入姓名\n");
scanf("%s", sp->people[sub].name);
system("cls");
break;
case 2:
printf("请输入性别\n");
scanf("%s", sp->people[sub].sex);
system("cls");
break;
case 3:
printf("请输入年龄\n");
scanf("%d", &(sp->people[sub].age));
system("cls");
break;
case 4:
printf("请输入电话\n");
scanf("%s", sp->people[sub].tel);
system("cls");
break;
case 5:
printf("请输入地址\n");
scanf("%s", sp->people[sub].address);
system("cls");
break;
case 6:
quit = 1;
break;
default:
printf("输入错误请从新输入\n");
}
}
printf("修改成功\n");
}
}
4.9 显示所有联系人函数Show()
这里就比较简单了,通过for循环将所有成员的信息打印出来,需要注意的是边界问题,注意循环前的判断和循环条件;
void Show(preson **peo)//显示所有联系人
{
if (*peo == NULL)//联系人列表为空
{
Open(peo);
}
preson *sp = *peo;
if (sp->size == 0)
printf("联系人名单为空\n");
for (int i = 0; i < sp->size; i++)
{
printf("姓名:%s 性别:%s 年龄:%d 电话:%s 地址:%s\n", sp->people[i].name, sp->people[i].sex,
sp->people[i].age, sp->people[i].tel, sp->people[i].address);
}
}
4.10清空所有联系人函数Empty()
这里就和前面的函数为什么带上下面的这几行代码有关系了
if (*peo == NULL)//联系人列表为空
{
Open(peo);
}
当我们对应结构体指针对应的指针给释放后再进行操作会导致程序的崩溃,因此我们需要在其它函数中对其进行检测,如果为空则重新开辟空间;
void Empty(preson **peo)//清空所有联系人
{
free(*peo);
*peo = NULL;//防止野指针
printf("联系人已清空\n");
}
4.11排序函数Rank()和排序回调函数Compare()
利用qsort可以进行无类型排序,只需要我们自己完成大小比较函数就可以了
//大小比较函数,同样不需要被其它源文件访问,带上static
static int Compare(const list *cur, const list *next)//排序回调函数
{
return (strcmp(cur->name, next->name));
}
void Rank(preson *sp)//以姓氏排序
{
if (sp == NULL || sp->size == 0)
printf("联系人链表为空\n");
else
qsort(sp->people[0].name,sp->size,sizeof(list),Compare);
}
这里值得一提的是qsort函数的传参,第一个参数表明了我们第一个元素的起始地址,第二个元素表明了需要多少个元素进行排序,第三个函数表示元素的大小,即每次交换位置时,需要交换多大的空间,第四个函数为自己编写的比较函数;
比较函数中的形参 用list *cur是因为柔性数组的类型为 list;这块和无类型排序函数qsort有一些关系,如果不太清楚的可以看我另外一篇关于qsort介绍的博客;链接: link.
4.12文件存储函数Save_book()
还记得我们提过的柔型数组不占据结构体的空间吗?
因此我们存储文件至磁盘时需要分两步,先将结构体preson的空间存入磁盘,再将柔型数组开辟对应的空间存入磁盘。数组中存储的是结构体因此数组的大小为结构体list的大小与成员个数size的积,当然你想存入capacity大小的空间也是可以的;
void Save_book(preson *sp)//将数据保存到磁盘之中
{
assert(sp);
FILE *fp = fopen("code.log", "wb");
if (fp == NULL)
{
printf("打开文件失败\n");
}
fwrite(sp, sizeof(preson), 1, fp);//先将一个结构体大小存入
fwrite(sp->people, sizeof(list), sp->size, fp);//再将柔型数组的大小存入
printf("保存成功\n");
fclose(fp);
}
4.13读取文件函数
读取的时候按存储时候的顺讯来,不然文件指针的位置错乱了,读取出来的数据也是错误的;
先读取存储的结构体数据preson,这里需要注意的是fread函数是将对应的空间大小保存在开辟出来的内存块内,因此不可以用结构体指针去接收,需要用结构体变量;
void Read(preson **peo)//读取文件
{
assert(*peo);
preson prev;
FILE *fp = fopen("code.log", "rb");
if (fp == NULL)
{
printf("文件打开失败\n");
return;
}
fread(&prev, sizeof(preson), 1, fp);//注意fread是将读取的内容存储于prev指向的内存块,因此不能定义一个空指针;
if (prev.capacity != 0)//文件夹中的内容不能为空
{
int count = sizeof(preson)+(prev.size)*sizeof(list);
*peo = (preson *)malloc(count);//开辟好空间
fread((*peo)->people,sizeof(list),prev.size,fp);
(*peo)->capacity = prev.size;
(*peo)->size = prev.size;
fclose(fp);
printf("数据读取成功\n");
}
else
printf("文件夹中没有内容\n");
}
5 完整代码-头文件、函数文件,main文件
#ifndef _PRESOM_H_
#define _PRESOM_H_
#include <stdio.h>
#include <windows.h>
#include <assert.h>
#pragma warning (disable :4996)
typedef struct List
{
char name[20];
char sex[20];
int age;
char tel[20];
char address[50];
}list;
typedef struct Preson
{
int capacity;//容量
int size;//当前人数
list people[0];//结构体柔性数组,存储客户信息结构体
}preson;
void Menu();//菜单
void Open(preson **peo);//开辟空间函数
void Add(preson **pep);//添加联系人信息
int Found(preson **peo);//找到并打印联系人信息
void Delect(preson **peo);//删除联系人信息
void Alter(preson **peo);//修改联系人信息
void Show(preson **peo);//显示所有联系人
void Empty(preson **peo);//清空所有联系人
void Rank(preson *sp);//以姓氏排序
void Read(preson **peo);//读取文件
void Save_book(preson *sp);//将数据保存到磁盘之中
#endif
#include "preson.h"
void Menu()
{
printf("###1.添加联系人信息############2.删除指定联系人信息####################\n");
printf("###3.查找指定联系人信息#########4.修改指定人信息#######################\n");
printf("###5.显示所有联系人信息#########6.清空所有联系人#######################\n");
printf("###7.以名字排序所有联系人#######8.退出#################################\n");
}
void Open(preson **peo)//开辟空间函数
{
(*peo)= (preson*)malloc(sizeof(preson)+sizeof(list)* 2);//柔性数组在结构体中不占据空间,因此需要额外开辟空间
if (*peo== NULL)
{
printf("开辟空间失败\n");
exit(-1);
}
(*peo)->size = 0;//记录当前成员数
(*peo)->capacity = 2;//记录总空间容量
}
static void Judge(preson **peo)//进行当前空间容量判定
{
preson *cur = *peo;
if (cur == NULL)//清空联系人后再添加
{
Open(peo);
}
else if (cur->capacity == cur->size)//当前成员数和空间容量相等代表通讯录容量满了
{
cur = (preson *)realloc(cur, sizeof(preson)+sizeof(list)*(cur->capacity) * 2);//增多了浪费,增少了麻烦,每次增加当前容量的两倍
if (cur!= NULL)
{
printf("增容成功\n");
cur->capacity = 2 * cur->capacity;
printf("增容成功\n");
*peo = cur;
}
else
exit(-1);
}
}
void Add(preson **peo)//添加联系人信息
{
Judge(peo);
preson *sp = *peo;
printf("请输入联系人姓名\n");
scanf("%s", sp->people[sp->size].name);
printf("请输入联系人性别\n");
scanf("%s", sp->people[sp->size].sex);
printf("请输入联系人年龄\n");
scanf("%d", &(sp->people[sp->size].age));//这里一定要取地址,因为这个表达式对应的是数组中的某个值
printf("请输入联系人电话\n");
scanf("%s", sp->people[sp->size].tel);
printf("请输入联系人地址\n");
scanf("%s", sp->people[sp->size].address);
sp->size++;//成员个数+1
}
int Found(preson **peo)//找到并打印联系人信息
{
if (*peo== NULL)
{
Open(peo);
}
preson *sp = *peo;
printf("请输入名字\n");
char name[20];//临时储存输入的名字
scanf("%s", name);
int i = 0;
for (i = 0; i < sp->size; i++)
{
if (strcmp(sp->people[i].name, name) == 0)//找到了
{
printf("姓名:%s 性别:%s 年龄:%d 电话:%s 地址:%s\n", sp->people[i].name, sp->people[i].sex,
sp->people[i].age, sp->people[i].tel, sp->people[i].address);
break;
}
}
if (i == sp->size || sp->size == 0)//上面循环跑完了或者成员个数为零时代表没有这个人;
{
printf("没有这个人,请确认你的输入信息是否正确\n");
return -1;
}
else
return i;
}
void Delect(preson **peo)//删除联系人信息
{
int sub = Found(peo);
preson *sp = *peo;
if (sub!=-1)
{
while (sub < sp->size-1)
{
sp->people[sub] = sp->people[sub + 1];//覆盖掉
sub++;
}
sp->size--;//成员减1
printf("删除成功\n");
}
}
void Alter(preson **peo)//修改联系人信息
{
int sub=Found(peo);
preson *sp = *peo;
int select = 0;
int quit = 0;
if (sub != -1)
{
while (!quit)
{
printf("请选择你要修改联系人什么信息\n");
printf("##1.姓名#############2.性别#######\n");
printf("##3.年龄#############4.电话#######\n");
printf("###5.地址############6.退出#######\n");
printf("#################################\n");
scanf("%d", &select);
switch (select)
{
case 1:
printf("请输入姓名\n");
scanf("%s", sp->people[sub].name);
system("cls");
break;
case 2:
printf("请输入性别\n");
scanf("%s", sp->people[sub].sex);
system("cls");
break;
case 3:
printf("请输入年龄\n");
scanf("%d", &(sp->people[sub].age));
system("cls");
break;
case 4:
printf("请输入电话\n");
scanf("%s", sp->people[sub].tel);
system("cls");
break;
case 5:
printf("请输入地址\n");
scanf("%s", sp->people[sub].address);
system("cls");
break;
case 6:
quit = 1;
break;
default:
printf("输入错误请从新输入\n");
}
}
printf("修改成功\n");
}
}
void Show(preson **peo)//显示所有联系人
{
if (*peo == NULL)//联系人列表为空
{
Open(peo);
}
preson *sp = *peo;
if (sp->size == 0)
printf("联系人名单为空\n");
for (int i = 0; i < sp->size; i++)
{
printf("姓名:%s 性别:%s 年龄:%d 电话:%s 地址:%s\n", sp->people[i].name, sp->people[i].sex,
sp->people[i].age, sp->people[i].tel, sp->people[i].address);
}
}
void Empty(preson **peo)//清空所有联系人
{
free(*peo);
*peo = NULL;//防止野指针
printf("联系人已清空\n");
}
static int Compare(const list *cur, const list*next)//排序回调函数
{
return (strcmp(cur->name, next->name));
}
void Rank(preson *sp)//以姓氏排序
{
if (sp == NULL || sp->size == 0)
printf("联系人链表为空\n");
else
qsort(sp->people[0].name,sp->size,sizeof(list),Compare);
}
void Save_book(preson *sp)//将数据保存到磁盘之中
{
assert(sp);
FILE *fp = fopen("code.log", "wb");
if (fp == NULL)
{
printf("打开文件失败\n");
}
fwrite(sp, sizeof(preson), 1, fp);//先将一个结构体大小存入
fwrite(sp->people, sizeof(list), sp->size, fp);//再将柔型数组的大小存入
printf("保存成功\n");
fclose(fp);
}
void Read(preson **peo)//读取文件
{
assert(*peo);
preson prev;
FILE *fp = fopen("code.log", "rb");
if (fp == NULL)
{
printf("文件打开失败\n");
return;
}
fread(&prev, sizeof(preson), 1, fp);//注意fread是将读取的内容存储于prev指向的内存块,因此不能定义一个空指针;
if (prev.capacity != 0)//文件夹中的内容不能为空
{
int count = sizeof(preson)+(prev.size)*sizeof(list);
*peo = (preson *)malloc(count);//开辟好空间
fread((*peo)->people,sizeof(list),prev.size,fp);
(*peo)->capacity = prev.size;
(*peo)->size = prev.size;
fclose(fp);
printf("数据读取成功\n");
}
else
printf("文件夹中没有内容\n");
}
#include "preson.h"
int main()
{
preson *peo=NULL;
Open(&peo);//开辟空间
Read(&peo);
int quit = 0;
int select = 0;
while (!quit)
{
Menu();
printf("请根据序号选择功能\n");
scanf("%d", &select);
switch (select)
{
case 1:
system("cls");
Add(&peo);//添加联系人,传址
break;
case 2:
system("cls");
Delect(&peo);//删除联系人
break;
case 3:
system("cls");
Found(&peo);//查找
break;
case 4:
system("cls");
Alter(&peo);//修改
break;
case 5:
system("cls");
Show(&peo);//显示所有联系人
break;
case 6:
system("cls");
Empty(&peo);//清空
break;
case 7:
system("cls");
Rank(peo);//以姓氏排序
break;
case 8:
quit = 1;
Save_book(peo);
break;//退出
default:
printf("输入有误\n");
}
}
system("pause");
return 0;
}
6注意点总结
1.增加联系人前记得空间判定,删除联系人后记得计数器减1;
2.注意函数传参,因为传值传参是不能改变外部变量的值的;
3.qsort的形参,这里比较细节,建议多留心;
4.文件的存储和读取,这里也是考验细心的地方,如何存储,存储什么读取的顺序以及接受内容的变量选择,不妨先有了思路再动手;
如有错误,欢迎指正;