目录
1.通讯录需求分析
信息记录要存放到文件中去,因而要实现文件的输入输出操作;要实现数据的插入、删除、修改和显示功能,因而要实现插入、删除、修改和显示操作;要实现数据容量的改变还需动态开辟空间;要实现按人名或电话号码进行查询的功能,因而要提供查找操作;要实现按类别分类展示,还需结合查找和展示功能;另外还应该提供键盘式选择菜单以实现功能选择。
2.通讯录模块设计
设计一个通讯录,首先考虑到的是通讯录变量类型有多个,因此我们需要构建一个结构体将所有通讯录包含的成员概括进去。
//一个人的信息
typedef struct PeoInfo
{
char name[NAME];
char tele[TELE];
char sort[SORT];
char emi[EMI];
}PeoInfo;
但这样设计后,我们在后面的模块中将有很大的缺陷,第一就是信息的个数无法统计,再然后就是之后的信息会覆盖,因为没有下标无法多次创建人的信息。于是我们再构建一个通讯录类型:
//构造一个通讯录类型
typedef struct contact
{
struct PeoInfo *date;//定义一个指针变量便于开辟空间
int capacity;//通讯录当前最大容量
int size;//记录已有的元素个数
}contact;
struct contact *ps;//定义一个结构体指针
struct PeoInfo* str ;//与struct PeoInfo *date类型一样目的不同
其中*date 是一个指向结构体的指针,通过它我们能进行访问PeoInfo 结构体,而为了节省空间,我们建立的通讯录将会是一个动态版的,起始容量为3,当保存的个数超过时,将会进行自动扩容。
void InitContact(contact* ps)//初始化容量为3,当通讯录已满时每次增容2个
{
ps->date = (PeoInfo*)malloc(3 * sizeof(PeoInfo));//开辟3个空间
if (ps->date == NULL)
{
return;
}
ps->capacity = 3;
ps->size = 0;
LoadContact(ps);
}
LoadContact(ps)是一个加载函数,作用在保存模块讲述。
2.1 增添模块
因为我们设计的通讯录是动态版,因此涉及到此时的最大容量是否已满,于是我们在增添模块前再添加一个检查模块,用来判断通讯录是否已满。
void AddContact(contact* ps)//添加函数模块
{
//1.检测通讯录容量是否已满
//2.已满,增容
//3.未满,啥事也不干
CheckContact(ps);
printf("请输入名字:>");
scanf("%s", ps->date[ps->size].name );
printf("请输入电话:>");
scanf("%s", ps->date[ps->size].tele );
printf("请输入类别:>");
scanf("%s", ps->date[ps->size].sort );
printf("请输入邮箱:>");
scanf("%s", ps->date[ps->size].emi );
system("cls");
printf("添加成功\n");
ps->size++;
}
2.2 打印模块
void ListContact(struct contact* ps)//展示函数模块
{
int i;
if (ps->size == 0)
{
printf("通讯录为空\n");
}
else
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "姓名", "电话","类别","邮箱");//打印一行标题,负号实现左对齐
for (i = 0; i < ps->size; i++)
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[i].name,
ps->date[i].tele,
ps->date[i].sort,
ps->date[i].emi);
}
}
}
2.3 查找模块
在查找模块中,我们能通过姓名或电话查找,但是需要选择,如选1姓名查找,2电话查找。
于是在其中我们又封装了两个小函数分别通过姓名或电话查找通讯录中该人的结构体下标。
而这两个函数在之后的修改和删除模块均能用到,这样就能去除程序的冗余性。
int Search_name_Contact(struct contact* ps, char name[NAME])//查找函数下标
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (strcmp(name, ps->date[i].name) == 0)//字符数组无法直接用赋值运算符,借用函数实现
{
return i;
}
}
return -1;//数组下标从0开始,返回一个负数代表没有
}
int Search_tele_Contact(contact* ps, char tele[TELE])//查找函数下标
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (strcmp(tele, ps->date[i].tele) == 0)
{
return i;
}
}
return -1;//数组下标从0开始,返回一个负数代表没有
}
void FindContact( struct contact* ps)//查找函数模块
{
int i, key,keey,j;
char name[NAME];
char tele[TELE];
printf("请选择姓名或电话:>1:姓名\n 2:电话\n");
scanf("%d", &j);
if (j == 1)
{
printf("请输入姓名:>");
scanf("%s", &name);
key = Search_name_Contact(ps, name);
if (key == -1)
{
printf("查无此人\n");
}
else
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "姓名", "电话", "类别", "邮箱");
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[key].name,
ps->date[key].tele,
ps->date[key].sort,
ps->date[key].emi);
}
}
if (j == 2)
{
printf("请输入电话:>");
scanf("%s", &tele);
keey = Search_tele_Contact(ps, tele);
if (keey == -1)
{
printf("查无此人\n");
}
else
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "姓名", "电话", "类别", "邮箱");
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[keey].name,
ps->date[keey].tele,
ps->date[keey].sort,
ps->date[keey].emi);
}
}
}
2.4 修改模块
修改模块其实就是增添模块和查找模块的结合,写法大同小异。
只是在后面修改中添加了指定数据的修改模块,可以只修改一样数据。
2.5 删除模块
删除模块简单运用覆盖删除。
void DeletContact(struct contact* ps)//删除函数模块
{
int i; char name[NAME];
printf("请输入要删除的人:>");
scanf("%s", &name);
int s = Search_name_Contact(ps, name);
if (s == -1)
{
printf("要删除的人不存在\n");
}
else
{
//假设size=5,s=2,3>2,4>3,5>4
for (i = s; i < ps->size; i++)
{
ps->date[i] = ps->date[i+1];
}
printf("删除成功\n");
ps->size--;
}
}
2.6 排序模块
排序即按照英文字母的顺序排序联系人的顺序,但博主特此提醒下,排序中文现在还不能实现,还需在调用一个函数,但这已经足够用了。
int cmp_stu_by_name(void* ele1, void* ele2)//比较
{
int i = strcmp(((PeoInfo*)ele1)->name, ((PeoInfo*)ele2)->name);
return i;
}
void swap(char* a, char* b, int witdh)//交换
{
int i = 0;
for (i = 0; i < witdh; i++)
{
char tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
}
}
void cmp_name_sort(void* base, int sz, int witdh, int(*cmp)(void* ele1, void* ele2))//模仿qsort函数用法 *cmp是一个函数指针,括号
{ //后的部分是函数的参数
int i = 0, j = 0, tmp = 0; //width表示一个结构体元素所占的字节宽度
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * witdh, (char*)base + (j + 1)*witdh) > 0)//比较
{
//交换
swap((char*)base + j * witdh, (char*)base + (j + 1)*witdh, witdh);
}
}
}
}
void SortContact(contact* ps)//冒泡排序法的结构体用法
{
cmp_name_sort(ps->date, ps->size, sizeof(ps->date[0]), cmp_stu_by_name);
}
简单解释下自制原理,首先cmp_name_sort函数形参为任意型指针void*base,实参是ps指向的结构体的指针,形参sz的实参是通讯录元素已有的个数,witdh一个元素所占的字节宽度。因为我们将ps指向的结构体内的元素强制转换成char类型,而我们知道char所占字节为1,因此首元素地址加上一个元素所占字节宽度就相当于向后跳了一个元素,其实也就类似于冒泡排序法。
2.7 分类模块
分类就是通过选择一个类别将该类别的人全部展示出来。
void ClassifyContact(contact* ps)//按类别展示
{
char classify[CLASSIFY];
int i = 0,key;
printf("请输入类别:>");
scanf("%s", &classify);
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "类别", "姓名", "电话", "邮箱");
for (i = 0; i < ps->size; i++)
{
key=strcmp(ps->date[i].sort, classify);
if(key==0)
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[i].sort,
ps->date[i].name,
ps->date[i].tele,
ps->date[i].emi);
}
}
}
2.8 保存模块
我们在通讯录中建立的信息要想下次打开程序还能显示,就需要这个模块了。
void SaveContact(struct contact* ps)//保存模块
{
FILE *pfWrite;//定义一个写文件指针
pfWrite = fopen("contact.dat", "wb");//打开一个文件用来存储通讯录中的数据
if (pfWrite == NULL)
{
printf("SaveContact::%s\n", strerror(errno));
return;
}
//写入通讯录中的信息到文件中
int i = 0;
for (i = 0; i < ps->size; i++)
{
fwrite(&ps->date[i], sizeof(PeoInfo), 1, pfWrite);
}
fclose(pfWrite);
pfWrite = NULL;//使pf指向空指针,防止乱用
printf("保存成功\n");
}
但这仅仅是保存在文件中了,我们下次打开要看到这些信息呢?于是我们在初始化通讯录前加入了一个加载模块,将保存在通讯录中的信息加载出来。
void LoadContact(contact* ps)//加载函数模块
{
PeoInfo a = { 0 };//定义一个PeoInfo类型的数组用来存储数据
FILE *pfRead;//定义一个只读文件的指针
pfRead = fopen("contact.dat", "rb");
if (pfRead == NULL)
{
printf("LoadContact::%s\n", strerror(errno));//报错
return;
}
//读入通讯录中的信息
//什么时候停止?fread返回值是读入到的实际数据,一次读一个,读完后返回0
while (fread(&a, sizeof(PeoInfo), 1, pfRead))
{
CheckContact(ps);//如果之前存入数据大于起始容量,还需增容
ps->date[ps->size] = a;
ps->size++;
}
fclose(pfRead);
pfRead = NULL;
}
但读取信息过程中我们要注意什么时候才能停止呢?细心的我们发现fread函数的返回值是实际读到的数据,什么意思,就是当有12个数据时,你一次读10个,那么返回值是2,那么久停止读入了。而当我们一次读一个时只有当返回值为0时,才是读入完毕。
3.代码模块
contact.h
//声明函数的作用
#pragma once
#define NAME 20
#define TELE 12
#define SORT 20
#define EMI 20
#define INI 3
#define CLASSIFY 20
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
//一个人的信息
typedef struct PeoInfo
{
char name[NAME];
char tele[TELE];
char sort[SORT];
char emi[EMI];
}PeoInfo;
//构造一个通讯录类型
typedef struct contact
{
struct PeoInfo *date;//定义一个指针变量便于开辟空间
int capacity;//通讯录当前最大容量
int size;//记录已有的元素个数
}contact;
struct contact *ps;//定义一个结构体指针
struct PeoInfo* str ;//与struct PeoInfo *date类型一样目的不同
enum optiation//用枚举增强代码可读性
{
EQIT,//默认初始为0
ADD,
FIND,
ALTER,
DELET,
LIST,
Save,
Classify,
Sort
};
enum choose//枚举解决数组存放汉字便于比较
{
名字,
电话,
类别,
邮箱
};
//声明函数
//初始化
void InitContact( contact* ps);
//增加数据到通讯录中
void AddContact(contact* ps);
//展示通讯录中的所有内容
void ListContact( contact* ps);
//通过名字或电话查找指定人信息
void FindContact( contact* ps);
//修改信息
void AlterContact( contact* ps);
//搜索下标
int Search_name_Contact( contact* ps,char name[NAME]);
int Search_tele_Contact(contact* ps, char tele[TELE]);
//删除
void DeletContact( contact* ps);
//保存
void SaveContact( contact* ps);
//检查是否需要增容
void CheckContact(contact* ps);
//释放动态空间
void DestoryContact(contact* ps);
//加载之前录入通讯录中的数据
void LoadContact(contact* ps);
//按照类别分类显示
void ClassifyContact(contact* ps);
//按名字长短排序
void SortContact(contact* ps);
contact.c
//实现函数的功能
#define _CRT_SECURE_NO_WARNINGS
#include "通讯录.h"
void CheckContact(contact* ps);
void LoadContact(contact* ps)//加载函数模块
{
PeoInfo a = { 0 };//定义一个PeoInfo类型的数组用来存储数据
FILE *pfRead;//定义一个只读文件的指针
pfRead = fopen("contact.dat", "rb");
if (pfRead == NULL)
{
printf("LoadContact::%s\n", strerror(errno));//报错
return;
}
//读入通讯录中的信息
//什么时候停止?fread返回值是读入到的实际数据,一次读一个,读完后返回0
while (fread(&a, sizeof(PeoInfo), 1, pfRead))
{
CheckContact(ps);//如果之前存入数据大于起始容量,还需增容
ps->date[ps->size] = a;
ps->size++;
}
fclose(pfRead);
pfRead = NULL;
}
void InitContact(contact* ps)//初始化容量为3,当通讯录已满时每次增容2个
{
ps->date = (PeoInfo*)malloc(3 * sizeof(PeoInfo));//开辟3个空间
if (ps->date == NULL)
{
return;
}
ps->capacity = 3;
ps->size = 0;
LoadContact(ps);
}
void CheckContact(contact* ps)//检测函数模块
{
if (ps->capacity == ps->size)
{
//增容
PeoInfo* str = (PeoInfo*)realloc(ps->date, (ps->capacity + 2) * sizeof(PeoInfo));//(ps->capacity + 2) * sizeof(PeoInfo)
if (str != NULL)//防止struct PeoInfo *date接收到一个空指针,影响后续操作 //新的元素个数乘以每个元素大小是总大小
{
ps->date = str;
ps->capacity += 2;
printf("增容成功\n");
}
else
{
printf("增容失败\n");
}
}
}
void AddContact(contact* ps)//添加函数模块
{
//1.检测通讯录容量是否已满
//2.已满,增容
//3.未满,啥事也不干
CheckContact(ps);
printf("请输入名字:>");
scanf("%s", ps->date[ps->size].name );
printf("请输入电话:>");
scanf("%s", ps->date[ps->size].tele );
printf("请输入类别:>");
scanf("%s", ps->date[ps->size].sort );
printf("请输入邮箱:>");
scanf("%s", ps->date[ps->size].emi );
system("cls");
printf("添加成功\n");
ps->size++;
}
void ListContact(struct contact* ps)//展示函数模块
{
int i;
if (ps->size == 0)
{
printf("通讯录为空\n");
}
else
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "姓名", "电话","类别","邮箱");//打印一行标题,负号实现左对齐
for (i = 0; i < ps->size; i++)
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[i].name,
ps->date[i].tele,
ps->date[i].sort,
ps->date[i].emi);
}
}
}
void FindContact( struct contact* ps)//查找函数模块
{
int i, key,keey,j;
char name[NAME];
char tele[TELE];
printf("请选择姓名或电话:>1:姓名\n 2:电话\n");
scanf("%d", &j);
if (j == 1)
{
printf("请输入姓名:>");
scanf("%s", &name);
key = Search_name_Contact(ps, name);
if (key == -1)
{
printf("查无此人\n");
}
else
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "姓名", "电话", "类别", "邮箱");
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[key].name,
ps->date[key].tele,
ps->date[key].sort,
ps->date[key].emi);
}
}
if (j == 2)
{
printf("请输入电话:>");
scanf("%s", &tele);
keey = Search_tele_Contact(ps, tele);
if (keey == -1)
{
printf("查无此人\n");
}
else
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "姓名", "电话", "类别", "邮箱");
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[keey].name,
ps->date[keey].tele,
ps->date[keey].sort,
ps->date[keey].emi);
}
}
}
void AlterContact(struct contact* ps)//修改函数模块
{
char name[NAME];
int i = 0, j=-1, x, s;
printf("请输入要修改的人:>");
scanf("%s", &name);
s = Search_name_Contact(ps, name);
if (s == -1)
{
printf("要修改的人不存在\n");
}
else
{
printf("请输入要修改的信息\n 0>名字\n 1>电话\n 2>类别\n 3>邮箱\n:>");
scanf("%d", &x);
if (x >= 6)
{
printf("选择错误\n");
}
else
{
switch (x)
{
case 名字:printf("请输入修改后名字%s:>\n");
scanf("%s", ps->date[s].name);
printf("修改成功\n");
break;
case 电话:printf("请输入修改后电话%s:>\n");
scanf("%s", ps->date[s].tele);
printf("修改成功\n");
break;
case 类别:printf("请输入修改后类别%s:>\n");
scanf("%s", ps->date[s].sort);
printf("修改成功\n");
break;
case 邮箱:printf("请输入修改后邮箱%s:>\n");
scanf("%s", ps->date[s].emi);
printf("修改成功\n");
break;
}
}
}
}
int Search_name_Contact(struct contact* ps, char name[NAME])//查找函数下标
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (strcmp(name, ps->date[i].name) == 0)//字符数组无法直接用赋值运算符,借用函数实现
{
return i;
}
}
return -1;//数组下标从0开始,返回一个负数代表没有
}
int Search_tele_Contact(contact* ps, char tele[TELE])//查找函数下标
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (strcmp(tele, ps->date[i].tele) == 0)
{
return i;
}
}
return -1;//数组下标从0开始,返回一个负数代表没有
}
void DeletContact(struct contact* ps)//删除函数模块
{
int i; char name[NAME];
printf("请输入要删除的人:>");
scanf("%s", &name);
int s = Search_name_Contact(ps, name);
if (s == -1)
{
printf("要删除的人不存在\n");
}
else
{
//假设size=5,s=2,3>2,4>3,5>4
for (i = s; i < ps->size; i++)
{
ps->date[i] = ps->date[i+1];
}
printf("删除成功\n");
ps->size--;
}
}
void DestoryContact(contact* ps)//销毁通讯录
{
free(ps->date);
ps->date = NULL;
}
void SaveContact(struct contact* ps)//保存模块
{
FILE *pfWrite;//定义一个写文件指针
pfWrite = fopen("contact.dat", "wb");//打开一个文件用来存储通讯录中的数据
if (pfWrite == NULL)
{
printf("SaveContact::%s\n", strerror(errno));
return;
}
//写入通讯录中的信息到文件中
int i = 0;
for (i = 0; i < ps->size; i++)
{
fwrite(&ps->date[i], sizeof(PeoInfo), 1, pfWrite);
}
fclose(pfWrite);
pfWrite = NULL;//使pf指向空指针,防止乱用
printf("保存成功\n");
}
void ClassifyContact(contact* ps)//按类别展示
{
char classify[CLASSIFY];
int i = 0,key;
printf("请输入类别:>");
scanf("%s", &classify);
printf("%-12s\t%-12s\t%-6s\t%-12s\n", "类别", "姓名", "电话", "邮箱");
for (i = 0; i < ps->size; i++)
{
key=strcmp(ps->date[i].sort, classify);
if(key==0)
{
printf("%-12s\t%-12s\t%-6s\t%-12s\n",
ps->date[i].sort,
ps->date[i].name,
ps->date[i].tele,
ps->date[i].emi);
}
}
}
int cmp_stu_by_name(void* ele1, void* ele2)//比较
{
int i = strcmp(((PeoInfo*)ele1)->name, ((PeoInfo*)ele2)->name);
return i;
}
void swap(char* a, char* b, int witdh)//交换
{
int i = 0;
for (i = 0; i < witdh; i++)
{
char tmp = *a;
*a = *b;
*b = tmp;
a++;
b++;
}
}
void cmp_name_sort(void* base, int sz, int witdh, int(*cmp)(void* ele1, void* ele2))//模仿qsort函数用法 *cmp是一个函数指针,括号
{ //后的部分是函数的参数
int i = 0, j = 0, tmp = 0; //width表示一个结构体元素所占的字节宽度
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * witdh, (char*)base + (j + 1)*witdh) > 0)//比较
{
//交换
swap((char*)base + j * witdh, (char*)base + (j + 1)*witdh, witdh);
}
}
}
}
void SortContact(contact* ps)//冒泡排序法的结构体用法
{
cmp_name_sort(ps->date, ps->size, sizeof(ps->date[0]), cmp_stu_by_name);
}
test.c
//测试
#define _CRT_SECURE_NO_WARNINGS
#include "通讯录.h"
#include <stdio.h>
#include <string.h>
#include "通讯录.c"
void menu()
{
printf("************菜单****************\n");
printf("********** 1. Add ************\n");
printf("********** 2. Find ************\n");
printf("********** 3. Alter ************\n");
printf("********** 4. Delet ************\n");
printf("********** 5. List ************\n");
printf("********** 6. Save ************\n");
printf("********** 7. Classify ********\n");
printf("********** 8. Sort ************\n");
printf("********** 0. Quit ************\n");
}
int main()
{
struct contact con;//创建一个通讯录con包含:date指针,size,capacity
InitContact(&con);//初始化通讯录
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case EQIT:
SaveContact(&con);//自动保存
DestoryContact(&con);//销毁通讯录,释放开辟的动态内存
printf("已退出,谢谢使用");
break;
case ADD:
AddContact(&con);
break;
case FIND:
FindContact(&con);
break;
case ALTER:
AlterContact(&con);
break;
case DELET:
DeletContact(&con);
break;
case LIST:
system("cls");
ListContact(&con);
break;
case Save:
system("cls");
SaveContact(&con);
break;
case Classify:
system("cls");
ClassifyContact(&con);
break;
case Sort:
SortContact(&con);
system("cls");
SaveContact(&con);
ListContact(&con);
break;
default:printf("选择错误\n");
break;
}
}while (input);
return 0;
}