之前说写一个改良版的通讯录,一直没有时间来写,下面我就讲一下这个要怎么写,只要跟着我的思路,相信你读完一定会有所收获(大佬请忽视这句话-.-!)
涉及知识:
1.C语言_多文件编程
2.数据结构_双向链表
3.设计模式_测试驱动开发(TDD)
设计思路:我把这个项目抽象为三层来设计。
第一层:应用层,直接面向用户,一般怎么用怎么写,直接设计这个项目需要的业务流程代码,不需要关心所使用函数的具体实现,直接用就行。
第二层:实现层,这一层来具体实现应用层所用到的函数。
第三层:结构底层和工具层,这一层提供实现层的底层所用到的数据结构和工具函数。
由于处理同名同姓这种情况过于复杂,这个版本的通讯录就先不做这个处理
后面如果有机会,再做一个有界面的通讯录,到时候再处理所言情况
下面我一层一层来讲解:
第一层、应用层
#include<stdio.h>
#include<Windows.h>
#include"head.h"//第二层的头文件
int main()
{
char option; //用char来接收选项比int型更省空间
while (1)
{
Menu();//打印通讯录的菜单
Init();//初始化通讯录
printf("\n请选择要进行的操作:>");
scanf("%c", &option);
switch (option)
{
case '1':
FindHuman();//查找指定联系人
break;
case '2':
ChangeHuman();//修改指定联系人
break;
case '3':
DeleteHuman();//删除指定联系人
break;
case '4':
AddHuman();//添加指定联系人
break;
case '5':
PrintHuman();//打印所有联系人
break;
case '6':
Destroy();//清空通讯录
break;
case '7':
return 0;//退出通讯录
default:
printf("输入错误,请重新选择!\n");
break;
}
system("cls");//结束一次操作后清屏
}
return 0;
}
第二层、实现层
//head.h
#pragma once
#include"ListTable.h"//第三层的头文件
//用宏来定义文件路径是一个技巧,方便以后修改
#define FILENAME "MailList.data"
PNode header;//将头指针设置为全局变量
FILE *fp;
void Menu();
void Init();
void FindHuman();
void ChangeHuman();
void DeleteHuman();
void AddHuman();
void PrintHuman();
void Destroy();
//head.c
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#include"head.h"
#include"tools.h"//第三层的头文件
void Menu()
{
printf("###-----------------------------###\n");
printf("### 1.>查找指定联系人 ###\n");
printf("###-----------------------------###\n");
printf("### 2.>修改指定联系人 ###\n");
printf("###-----------------------------###\n");
printf("### 3.>删除指定联系人 ###\n");
printf("###-----------------------------###\n");
printf("### 4.>添加指定联系人 ###\n");
printf("###-----------------------------###\n");
printf("### 5.>显示所有联系人 ###\n");
printf("###-----------------------------###\n");
printf("### 6.>删除所有联系人 ###\n");
printf("###-----------------------------###\n");
printf("### 7.>退出通讯录 ###\n");
printf("###-----------------------------###\n");
}
void Init()
{
HumanInfo human;
TableInit(&header);//初始化链表
if ((fp = fopen(FILENAME, "rb+")) == NULL && (fp = fopen(FILENAME, "wb+")) == NULL){
printf("Error on open %s file!", FILENAME);//pause
getch();
exit(1);
}
if (getFileSize(fp))
{//如果文件不为空,则将文件内容加载到内存,用链表管理
while (fread(&human, 1, sizeof(HumanInfo), fp))
{
if (TablePushBack(header, human) != 1){
printf("初始化失败!\n");
//用getch()接收一个无回显的空格作为暂停,以至于用户可以看见
//“初始化失败!”这句话,下面的所有getch()用法均为此
getch();
exit(1);
}
}
}
}
//每次写操作后需要更新文件,将链表内容重新写回文件
//否则在程序运行期间进行的更改不会影响文件的实际内容
void UpdataFile()
{
PNode str;
freopen(FILENAME, "wb+", fp);
for (str = header->_next; str != header; str = str->_next)
{
fwrite(&(str->_human), sizeof(HumanInfo), 1, fp);
}
fflush(fp);
}
void FindHuman()
{
char name[30];
printf("\n输入要查找联系人的姓名:");
fflush(stdin);//每次输入前都要清空输入缓冲区
//fgets这个函数相比gets要更加安全,推荐实际中使用这个函数
fgets(name, 30, stdin);
PNode pos;
if (pos = TableFind(header, name))
{
Print(pos);
}
else
{
printf("没有找到该联系人的信息!\n");
}
getch();//暂停
}
void AddHuman()
{
HumanInfo dest;
printf("\n输入需要添加联系人的信息\n");
printf("姓名:");
fflush(stdin);//清空输入缓冲区
fgets(dest.name, 30, stdin);
printf("性别:");
fflush(stdin);
fgets(dest.sex, 10, stdin);
printf("手机号码:");
fflush(stdin);
fgets(dest.phonen, 15, stdin);
printf("住址:");
fflush(stdin);
fgets(dest.site, 40, stdin);
if (TablePushBack(header, dest) == 1)
{
printf("\n添加成功!\n");
UpdataFile();//每次更改链表内容后,都要将更新写回文件
}
else
{
printf("\n添加失败!\n");
}
getch();//暂停
}
void PrintHuman()
{
PNode str = header->_next;
if (str != header){
//如果通讯录不为空,则清屏。提供更好的显示效果
system("cls");
}
printf("------------------\n");
for (;str != header;str = str->_next)
{
Print(str);
printf("------------------\n");
}
getch();//暂停
}
void ChangeHuman()
{
char name[30] = { 0 };
printf("\n输入需要修改的联系人的姓名:");
fflush(stdin);
fgets(name, 30, stdin);
PNode pos;
if (pos = TableFind(header, name))
{
printf("输入需要修改的联系人的信息\n");
printf("姓名:");
fflush(stdin);
fgets(pos->_human.name, 30, stdin);
printf("性别:");
fflush(stdin);
fgets(pos->_human.sex, 10, stdin);
printf("手机号码:");
fflush(stdin);
fgets(pos->_human.phonen, 15, stdin);
printf("住址:");
fflush(stdin);
fgets(pos->_human.site, 40, stdin);
UpdataFile();//更新文件
printf("修改成功!\n");
}
else
{
printf("未找到该联系人,修改失败!\n");
}
getch();//暂停
}
void DeleteHuman()
{
char name[30];
printf("\n输入需要删除联系人的姓名:");
fflush(stdin);
fgets(name, 30, stdin);
PNode del;
if (del = TableFind(header, name))
{
del->_pre->_next = del->_next;
del->_next->_pre = del->_pre;
free(del);
UpdataFile();//更新文件
printf("删除成功!\n");
}
else
{
printf("未找到该联系人!\n");
}
getch();//暂停
}
void Destroy()
{
if (!TableIsEmpty(header))
{
TablePopBack(header);
}
UpdataFile();
printf("销毁成功!\n");
getch();//暂停
}
第三层、管理空间层和工具层
//tools.h
#pragma once
#include<stdio.h>
#include<Windows.h>
//获取文件大小(以字节计)
long getFileSize(FILE *fp){
long fsize;
fpos_t fpos; //当前位置
fgetpos(fp, &fpos); //获取当前位置
fseek(fp, 0, SEEK_END);
fsize = ftell(fp);
fsetpos(fp, &fpos); //恢复之前的位置
return fsize;
}
//ListTable.h
#pragma once
typedef struct HumanInfo
{
char name[20];
char sex[10];
char phonen[15];
char site[40];
}HumanInfo;
//用双链表来管理底层的空间
typedef struct Node
{
HumanInfo _human;
struct Node* _next;
struct Node* _pre;
}Node,*PNode;
PNode BuyNewNode(HumanInfo data);
void TableInit(PNode *head);
_Bool TablePop(PNode head, PNode pos);
_Bool TablePushBack(PNode head, HumanInfo data);
_Bool TablePopBack(PNode head);
PNode TableFind(PNode head, char* name);
_Bool TableIsEmpty(PNode head);
void Print(PNode pos);
//ListTable.c
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<malloc.h>
#include"ListTable.h"
//申请节点
PNode BuyNewNode(HumanInfo data)
{
PNode new_node = (PNode)malloc(sizeof(Node));
new_node->_human = data;
new_node->_pre = NULL;
new_node->_next = NULL;
return new_node;
}
//链表初始化
void TableInit(PNode *head)
{
assert(head);
HumanInfo temp = { "","","",""};
*head = BuyNewNode(temp);
(*head)->_pre = *head;
(*head)->_next = *head;
}
//在链表的指定位置删除操作
_Bool TablePop(PNode head, PNode pos)
{
assert(head);
assert(pos);
if (head->_next == head){
return 0;
}
PNode str = head;
while (str->_next != str)
{
if (str->_next == pos)
{
str->_next->_next->_pre = str;
str->_next = str->_next->_next;
free(str->_next);
return 1;
}
}
return 0;
}
//链表的尾插操作
_Bool TablePushBack(PNode head, HumanInfo data)
{
assert(head);
if (head->_next == head)
{
PNode new_node = BuyNewNode(data);
new_node->_pre = head;
new_node->_next = head;
head->_next = new_node;
head->_pre = new_node;
}
else
{
PNode new_node = BuyNewNode(data);
new_node->_next = head;
new_node->_pre = head->_pre;
head->_pre->_next = new_node;
head->_pre = new_node;
}
return 1;
}
//链表的尾删操作
_Bool TablePopBack(PNode head)
{
assert(head);
if (head->_next == head){
return 1;
}
PNode temp = head->_pre;
head->_pre = head->_pre->_pre;
head->_pre->_pre->_next = head;
free(temp);
return 1;
}
//链表的节点查找操作
PNode TableFind(PNode head, char *name)
{
assert(head);
PNode str = head->_next;
while ((str != head) && strcmp(str->_human.name, name) != 0)
{
str = str->_next;
}
if (head == str){
return NULL;
}
else{
return str;
}
}
//判断链表是否为空的操作
_Bool TableIsEmpty(PNode head)
{
return head->_pre == head;
}
//链表的节点数据打印操作
void Print(PNode pos)
{
printf("姓名:%s\n", pos->_human.name);
printf("性别:%s\n", pos->_human.sex);
printf("电话:%s\n", pos->_human.phonen);
printf("住址:%s\n",pos->_human.site );
}
//运行结果示例
主界面
添加联系人
查找操作
修改操作