距离第一篇已经过去很久,我之所以暂时放下通讯录,是因为学业颇多,无暇顾及。现在放假已经有一段时间了,脱离每天忙碌的生活后,我只想享受一下整日无事,浑浑噩噩过一天的感觉,只不过差点没收得住。前段时间是期末考试周,除去复习以及完成一些报告书之外,每天没有别的的任务,这样的情况下,我自然是想过打开通讯录,继续编辑的,但终归还是懒了点,一直到24号,回国后,又睡了一夜,我才开始修改代码。
学习了一段时间,能力不会不涨的,现在回看自己以前所写,真的感觉有很多漏洞。我首先就修改了变量的命名,比如那些拼音式的命名。其次又去掉了几个全局变量,以及不需要的代码,比如虚拟回收站,下面会写缘由。最后还要更改实现功能的代码。除去基本框架留存,每个具体功能都需要想清楚是否会联系到一起,以及可能要cv一下其他代码。
接下来我会记录一下现在的改动,之后有新方法时再写博客。
针对通讯录要实现的功能,我添加了分类功能,具体思路还没有,但大概会做一个固定分类形式的代码。
先看框架
Book.c
void menu()
{
printf("\n");
printf("*************************************\n");
printf("******* 1. 增加 2. 删除 *******\n");
printf("******* 3. 查找 4. 修改 *******\n");
printf("******* 5. 展示 6. 排序 *******\n");
printf("******* 7. 分类 8. 保存 *******\n");
printf("******* 0. 退出 *******\n");
printf("*************************************\n");
printf("\n");
}
enum Options
{
Exit,
Add,
Del,
Find,
Modify,
Show,
Sort,
//Classify,
Save,
};
int main()
{
int input = 0;
Contact book;
InitContact(&book);
LoadContact(&book);
do
{
menu();
printf("请选择: >");
scanf("%d", &input);
switch (input)
{
case Add:
AddContact(&book);
break;
case Del:
DelContact(&book);
break;
case Find:
FindContact(&book);
break;
case Modify:
ModifyContact(&book);
break;
case Show:
ShowContact(&book);
break;
case Sort:
SortContact(&book);
break;
/*case Classify:
ClassifyContact(&book);
break;*/
case Save:
SaveContact(&book);
break;
case Exit:
SaveContact(&book);
DestroyContact(&book);
printf("退出通讯录\n");
break;
}
} while (input);
return 0;
}
在初始化book时,原本是只调用InitContact,LoadContact放在Init里面。我把Load拿了出来,两个各司其职,是为了之后要再开一个结构体,那么就可以直接初始化而不调用Load。
Contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define MAX 100
#define NAME 20//姓名
#define GENDER 7//性别
#define ADDRESS 20//地址
#define WORK 10//职业
#define NUMBER 15//电话
#define SIZE 30//初始化时动态开辟的空间
#define ADD_SZ 10//增容时所用a
typedef struct PeoInfo
{
char name[NAME];
char gender[GENDER];
char address[ADDRESS];
char work[WORK];
char number[NUMBER];
int age;
}PeoInfo;
typedef struct Contact
{
PeoInfo* data;
int sz;
int capacity;
}Contact;
void InitContact(Contact* ps);
void LoadContact(Contact* ps);
void AddContact(Contact* ps);
void DelContact(Contact* ps);
void FindContact(Contact* ps);
void SortContact(Contact* ps);
void ModifyContact(const Contact* ps);
void ShowContact(const Contact* ps);
//void ClassifyContact(const Contact* ps);
void DestroyContact(Contact* ps);
void SaveContact(Contact* ps);
现在看具体实现内容
最终实现的通讯录要给人看,所以用来删除的回收站和写入的通讯录文件都用格式化输入和输出函数,fprintf fscanf。以及另建一个通讯录,和主通讯录一样内容,不过用二进制形式存储,在调起程序初始化的时候电脑从这个文件读取内容。
void LoadContact(Contact* ps)
{
FILE* pf = fopen("E://Computer.txt", "rb");
if (pf == NULL)
{
perror("LoadContact::fopen");
return;
}
PeoInfo ptr = { 0 };
while (fread(&ptr, sizeof(PeoInfo), 1, pf))
{
//检查是否要增容
check_capacity(ps);
ps->data[ps->sz] = ptr;
ps->sz++;
}
fclose(pf);
pf = NULL;
}
void InitContact(Contact* ps)
{
assert(ps);
ps->data = (PeoInfo*)malloc(SIZE * sizeof(PeoInfo));
if (ps->data == NULL)
{
perror("InitContact()");
return;
}
ps->sz = 0;
ps->capacity = SIZE;
}
增加
还是一样
这里其实也可以边加边放入通讯录文件中,也可以在退出时一起保存,两者都可。
void AddContact(struct Contact* pa)
{
assert(pa);
if (0 == check_capacity(pa))
{
return;
}
printf("请输入要添加的名字:>");
scanf("%s", pa->data[pa->sz].name);
printf("请输入要添加的性别:>");
scanf("%s", pa->data[pa->sz].gender);
printf("请输入要添加的电话号码:>");
scanf("%s", pa->data[pa->sz].number);
printf("请输入要添加的地址:>");
scanf("%s", pa->data[pa->sz].address);
printf("请输入要添加的职业:>");
scanf("%s", pa->data[pa->sz].work);
printf("请输入要添加的年龄:>");
scanf("%d", &pa->data[pa->sz].age);
pa->sz++;
printf("已成功添加第%d个联系人\n", pa->sz);
}
删除
删除分为指定删除和批量删除。之前的代码做了很多变量来控制整个程序,比如接收返回的数字,程序正常进行就返回0,不正常或者出错就返回一些不同的数字,根据这些数字来安排下一步;以及检查虚拟回收站是否需要扩容,虚拟回收站出现的错误等等,以上看起来比较荒诞,但实际都是我之前所想,现在做了一些改变。
虚拟回收站去掉。虚拟回收站并不需要,在程序之外用一个文件用来做回收站,用户可以在那里看到删除过的联系人。那么虚拟回收站有什么作用?我想了想,之前大概是认为我自己调起程序后可以随时查看删除过的东西,非常方便。但程序员也可以看回收站文件啊,没必要做一个虚拟回收站,且这个东西是调起程序后才出现的,想要一直能够看到删除过的内容,那么这个虚拟回收站就需要保存好之前的记录,这点不算难,在Book.c里,运行删除功能前就建立一个结构体,调用函数时把它也传过去,就可以保存内容了,但指定和批量删除都需要虚拟回收站,为了表达回收站的大小,我们要不和上面一样,也先建立一个变量,传过去;或者在Contact.c里创建一个全局变量,这样就涉及到控制变量问题。所以这实在是多此一举。
这篇先不说批量删除功能
对于指定删除,它仍然是int类型的函数,正常运行后返回0,那么程序结束,用户再去选择功能;出现错误,返回非0整数,程序继续,如果连续3次出现错误,那就停止程序,用户重新选择功能,所以这里需要一个计数的变量。不过连续3次错误应该是很少出现的,看下面的代码图就明白了。
用户选择好要删除的联系人后,先把它放到回收站文件里,再删除
static int FindByName(const char name[], const struct Contact* pf)
{
assert(name && pf);
int i = 0;
for (i = 0; i < pf->sz; i++)
{
if (0 == strcmp(pf->data[i].name, name))
{
return i;
}
}
return -1;
}
static int SpecifyDel(Contact* ps)
{
assert(ps);
int j = 0;
int i = 0;
char name[NAME];
printf("请输入要删除人的名字: >");
scanf("%s", name);
int m = FindByName(name, ps);
if (m == -1)
{
printf("要删除的人不存在\n");
return -1;
}
else
{
PeoInfo* n = &ps->data[m];
FILE* bin = fopen("E://recycle bin.txt", "w+");//文件指针,往回收站文本文件里写入
if (bin == NULL)
{
perror("ZDDel::fopen");
printf("文件创建失败,请重新进行删除\n");
return 1;
}
fprintf(bin, "姓名:%s 性别:%s 地址:%s 工作:%s 电话:%s 年龄:%d\n", ps->data[m].name, ps->data[m].gender, ps->data[m].address, ps->data[m].work, ps->data[m].number, ps->data[m].age);
fclose(bin);
bin = NULL;
for (j = m; j < ps->sz - 1; j++)
{
ps->data[j] = ps->data[j + 1];
}
ps->sz--;
printf("成功删除指定联系人\n");
}
return 0;
}
void DelContact(Contact* pd)
{
assert(pd);
int n = 0;
int s = 1;
int b = 1;
int i = 0;
int num = 0;
char ret[20] = "0";
printf("选择批量删除前请查询可用的序列号.是否查询?\n");
while (scanf("%s", ret) != EOF)
{
if (0 == strcmp("是", ret))
{
ShowContact(pd);
break;
}
else if (0 == strcmp("否", ret))
break;
else
{
printf("请重新输入!\n");
for (i = 0; i < 20; i++)
{
ret[i] = '0';
}
continue;
}
}
printf("请选择删除模式: 1、指定删除 2、批量删除\n");
while (scanf("%d", &n) != EOF)
{
if (n == 1)
{
while (s)
{
s = SpecifyDel(pd);
if (s != 0)
++num;
if (3 == num)
{
printf("程序自身已出现错误3次,强制退出,请重新开始\n");
s = 0;
num = 0;
}
}
break;
}
else
{
printf("请重新输入\n");
continue;
}
}
}
批量删除下一篇再细写。
查找、修改、展示
还是之前的代码
void FindContact(const struct Contact* ps)
{
assert(ps);
char name[NAME];
printf("请输入要查找的名字: >");
scanf("%s", name);
int ret = FindByName(name, ps);
if (ret == -1)
printf("要查找的人不存在\n");
else
{
printf("%-9s\t%-7s\t%-10s\t%-10s\t%-15s\t%5s\n", "姓名", "性别", "地址", "职业", "号码", "年龄");
printf("%-9s\t%-7s\t%-10s\t%-10s\t%-15s\t%5d\n", ps->data[ret].name,
ps->data[ret].gender,
ps->data[ret].address,
ps->data[ret].work,
ps->data[ret].number,
ps->data[ret].age);
}
}
void ModifyContact(const struct Contact* ps)
{
printf("请输入要修改人的名字:>");
char name[NAME];
scanf("%s", name);
int ret = FindByName(name, ps);
if (ret == -1)
printf("要修改的人不存在\n");
else
{
printf("请输入要修改的名字:>");
scanf("%s", ps->data[ret].name);
printf("请输入要修改的性别:>");
scanf("%s", ps->data[ret].gender);
printf("请输入要修改的电话号码:>");
scanf("%s", ps->data[ret].number);
printf("请输入要修改的地址:>");
scanf("%s", ps->data[ret].address);
printf("请输入要修改的职业:>");
scanf("%s", ps->data[ret].work);
printf("请输入要修改的年龄:>");
scanf("%d", &ps->data[ret].age);
printf("修改成功\n");
}
}
void ShowContact(const struct Contact* ps)
{
int i = 0;
printf("\n");
printf(" %-9s\t%-7s\t%-10s\t%-10s\t%-15s\t%5s\n", "姓名", "性别", "地址", "职业", "号码", "年龄");
for (i = 0; i < ps->sz; i++)
{
printf("%d. %-9s\t%-7s\t%-10s\t%-10s\t%-15s\t%5d\n", (i + 1),
ps->data[i].name,
ps->data[i].gender,
ps->data[i].address,
ps->data[i].work,
ps->data[i].number,
ps->data[i].age);
}
}
保存、退出
分类之后再写。
关于保存和退出,这里我写了两个函数,但是退出里也包含了保存。保存的时候我想过将通讯录文件的内容直接复制到电脑读取的文件,但是不同格式可能无法实现,我还没细查这个问题,这里我再开了一个文件指针,保存时两个文件指针各司其职即可。
void DestroyContact(Contact* ps)
{
free(ps->data);
ps->data = NULL;
ps->capacity = 0;
ps->sz = 0;
}
void SaveContact(Contact* ps)
{
FILE* pb = fopen("E://Book.txt", "w");
FILE* pc = fopen("E://Computer.txt", "wb");
if (pb == NULL || pc == NULL)
{
perror("SaveContact");
return;
}
int i = 0;
for (i = 0; i < ps->sz; i++)
{
fprintf(pb, "姓名:%s 性别:%s 地址:%s 工作:%s 电话:%s 年龄:%d\n", ps->data[i].name, ps->data[i].gender, ps->data[i].address, ps->data[i].work, ps->data[i].number, ps->data[i].age);
fwrite(&(ps->data[i]), sizeof(PeoInfo), 1, pc);
}
fclose(pb);
fclose(pc);
pb = NULL;
pc = NULL;
return;
}
下一篇再写批量删除,或者再加上分类功能。经过几次运行,现在整个程序属于正常状态。
结束。