第一阶段 需求分析和架构设计
1 添加成员 要考虑人员如何存储,存储的文件是什么类型 人员存储的格式,人员
2 打印成员 的信息有哪些
3 删除成员
4 查找人员
5 保存文件
6 加载文件
程序可以被分为三层从上到下分别是业务逻辑层,接口层,支持层其中业务逻辑代表着用户去输入一些数据,传入数据进入接口层接口层接受到数据对应用户所想要的操作进行一定的逻辑操作,支持层是最底层的对文件的操作,数据存储
对链表比较重要的两个操作就是增加删除两个操作 可以去数据结构去复习一下
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//函数内的数字一定要通过宏定义进行实现 这样保证可读性
//关于接口层的东西最好是用宏定义的方式来实现
//define定义区我个人认为是广义定义的抽象对象
#define NAME_LENGTH 16
#define PHONE_LENGTH 32
#define BUFFER_LENGTH 128
#define MIN_TOKEN_LENGTH 5
//宏定义一个ADD
#define LIST_INSERT(item, list) do { \
item->prev = NULL; \
item->next = list; \
if ((list) != NULL) (list)->prev = item; \
(list) = item; \
} while(0)
//#define LIST_INSERT(item,list) do{item->prev = NULL; item->next = list;if ((list) != NULL) (list)->prev = item; (list) = item;} while(0)
// 将 item 的前驱指针 prev 置为 NULL。
// 将 item 的后继指针 next 指向 list 链表头部。
// 更新 list 链表头部的前驱指针为 item。
//宏定义一个删除
#define LIST_REMOVE(item,list) do{ if(item->prev != NULL) item->prev->next = item->next; if(item->next !=NULL) item->next->prev = item->prev; item->next = item->prev = NULL;} while(0)
//当我要删除的子节点上一跳和下一跳地址不为空时,将我的上一跳的下一跳 赋予我当前子节点的下一跳的地址 ,同理将我的下一跳的上一跳 赋予我的上一跳的地址 ;最后让我的子节点上下地址赋予空值
//定义一个输出 不要在接口层直接使用printf
#define INFO printf
//结构体用来存储人员信息和链表指针
struct person
{
struct person *prev; //指向上一跳地址
char name[NAME_LENGTH]; //姓名
char phone[PHONE_LENGTH]; //电话
struct person *next; //指向下一跳地址
};
//定义一个通讯录结构体由若干个人组成
struct contact
{
int count; //前面序号
struct person *people; //单个人员
};
//枚举操作数 第一个定义为1 枚举类型的会依次递增数字 用于下面业务的操作数
enum{
OPER_INSERT = 1,
OPER_PRINT,
OPER_DELETE,
OPER_SELECT,
OPER_SAVE,
OPER_LOAD
};
//接口层主要封装四个包 也就是四个功能
//接口层作用是用来隔离底层也就是说无需知道底层是使用什么方式
//define interface
//ADD功能模块
int person_insert(struct person **ppeople ,struct person *ps) //**ppeople二级指针
{
//若ps指向的是空指针直接返回
if(ps == NULL) return -1;
LIST_INSERT(ps,*ppeople);
//people指向的是一群人也就是一张完整的链表 而ps指向的是people中的一个人也就是链表的某一个节点
return 0;
}
//删除模块
int person_delete(struct person **ppeople ,struct person *ps) //使用二级指针来接收数据通常是因为需要修改调用函数中的指针变量。
//在C语言中,如果我们想要修改传入函数中的指针变量本身(而不仅仅是它所指向的内存地址),
//我们就需要将该指针的地址传递给函数,即使用二级指针。
{
//若ps指向的是空指针直接返回
if(ps == NULL) return -1 ;
if (ppeople == NULL) return -2;
LIST_REMOVE(ps,*ppeople);
return 0;
}
//查找模块
struct person* person_select(struct person *people ,const char *name)
{
struct person *item = NULL; //令指针为空
for (item = people ; item !=NULL; item = item->next) //循环遍历链表 在确定item不为空的情况下 item开始循环+1遍历
{
if(!strcmp(name,item->name))//strcmp若是两者相等返回0 不等则返回不为0的数 如果两者相等则停止遍历返回当前的结果
break;
};
return item;
}
//遍历打印模块
int person_print(struct person *people)
{
struct person *item = NULL; //令指针为空
for (item = people ; item !=NULL; item = item->next) //循环遍历链表 在确定item不为空的情况下 item开始循环+1遍历
{
INFO("name:%s,phone:%s\n",item->name,item->phone);
}
return 0;
}
//业务层
//添加
int insert_entry(struct contact *cont)
{
//注:在写函数之前一定要区判断参数是否为空 因为空指针会导致程序出异常崩溃
if(cont == NULL) return -1;
struct person *p = (struct person*)malloc(sizeof(struct person)) ;//给指针分配空间
if(p == NULL) return -2; //赋予返回值不同方便我们知道是因为什么异常导致的程序出错
memset(p, 0, sizeof(struct person));
//name
INFO("Please Input Name : \n");
scanf("%s",p->name); //scanf很少在服务器架构中使用 因为有漏洞 //Linux 下如何解决scanf数组输入溢出的问题
//phone
INFO("Please Input Phone: \n");
scanf("%s",p->phone);
//add
if(0 != person_insert(&cont->people,p)) //&是传入形参是地址 当person执行失败的时候释放p指针
{
free(p);
return -3;
}
cont->count ++;
INFO("Insert Success\n");
return 0;
}
//打印
int print_entry(struct contact *cont)
{
//注:在写函数之前一定要区判断参数是否为空 因为空指针会导致程序出异常崩溃
if(cont == NULL) return -1;
//传入相应的人员信息
//打印
person_print(cont->people);
}
//删除
int delete_entry(struct contact *cont)
{
//注:在写函数之前一定要区判断参数是否为空 因为空指针会导致程序出异常崩溃
if(cont == NULL) return -1;
//name
INFO("Please Input Want To Delete People Name :\n");
char name [NAME_LENGTH];
scanf("%s",name);
//匹配person的信息
struct person *ps = person_select(cont->people,name); //用一个指针存放返回的值
if(ps == NULL)
{
INFO("Person Dont In Contact ! \n");
return -2; //节点不存在
}
INFO("name: %s, phone: %s\n", ps->name, ps->phone);
//del
if(0!=person_delete(&cont->people,ps))
{
free(ps);
}
INFO("Delete People Success ! \n");
return 0;
}
//查找
int search_entry(struct contact *cont)
{
//注:在写函数之前一定要区判断参数是否为空 因为空指针会导致程序出异常崩溃
if(cont == NULL) return -1;
//name
INFO("Please Input Want To Search People Name :\n");
char name [NAME_LENGTH];
scanf("%s",name);
//匹配person的信息
struct person *ps = person_select(cont->people,name); //用一个指针存放返回的值
if(ps == NULL)
{
INFO("Person Dont In Contact ! \n");
return -2; //节点不存在
}
//打印
INFO("name: %s ,phone %s" , ps->name,ps->phone);
return 0;
}
//保存文件
int savefile_entry(struct person *peo,const char *filename)
{
FILE *fp = fopen(filename,"w");//打开文件 w只写
if(fp == NULL)
return -1;
struct person *item = NULL;
for(item = peo;item!=NULL;item=item->next)
{
fprintf(fp,"Name:%s ,Phone:%s \n",item->name,item->phone);//按格式打印到文件中
fflush(fp);//刷新到磁盘 和sql的commit命令一样
}
INFO("Print File is Success!");
fclose(fp);//关闭文件
}
//解析从文件中读取到的数据
int parser_token(char *buffer,char *name ,char *phone,int length)
{
if(buffer == NULL) return -1;
int i = 0,status = 0,j = 0;
for(i = 0 ;buffer[i] != ",";i++) //首先以,号分割name和phone
{
if(buffer[i] == " ") //其次以空格将name:后面的数据进行取出 运用的状态机这个方法
{
status = 1;
}else if (status == 1)
{
name[j++] = buffer[i]; //若为1代表前面是空格所以后面的数据就是我们要的
}
}
status = 0,j = 0;
for(;i<length;i++)
{
if(buffer[i] == " ") //其次以空格将phone:后面的数据进行取出 运用的状态机这个方法
{
status = 1;
}else if (status == 1)
{
phone[j++] = buffer[i] ; //若为1代表前面是空格所以后面的数据就是我们要的
}
}
return 0;
}
//加载文件
int LoadFile_entry(struct person **ppeo,int **ccount,const char *filename) //注:一级指针无法进行值修改 因为我们要对传入的peo的数据进行更改所以采用二级指针
{
FILE *fp = fopen(filename,"r");//打开文件 r只读
if(fp == NULL)
return -1;
while(!feof(fp))//feof函数用来判断是不是到尾了返回非0值 未结束返回0
{
char buffer[BUFFER_LENGTH] = {0};
fgets(buffer,BUFFER_LENGTH,fp); //一次读一行 读完放入buffer中
char name[NAME_LENGTH] = {0};
char phone[PHONE_LENGTH] = {0};
if (0 != parser_token(buffer, strlen(buffer), name, phone)) {
continue;
}
//初始化链表指针
struct person *pr = (struct person*)malloc(sizeof(struct person));
if (pr == NULL ) return -2;
memcpy(pr->name, name, NAME_LENGTH);
memcpy(pr->phone, phone, PHONE_LENGTH);
person_insert(ppeo, pr);
(*ccount) ++;
}
}
//提示信息
void menu_info(void) {
INFO("\n\n********************************************************\n");
INFO("***** 1. Add Person\t\t2. Print People ********\n");
INFO("***** 3. Del Person\t\t4. Search Person *******\n");
INFO("***** 5. Save People\t\t6. Load People *********\n");
INFO("***** Other Key for Exiting Program ********************\n");
INFO("********************************************************\n\n");
}
//主方法中方放页面操作之流的
int main()
{
struct contact *cts = malloc(sizeof(struct contact));
if (cts == NULL)
{
return -1;
}
memset(cts,0,sizeof(struct contact)); //memset还经常用于清空字符串、清空缓冲区等操作 初始化
while (1)
{
menu_info();
int select = 0;
scanf("%d" , &select);
switch (select)
{
case OPER_INSERT:
insert_entry(cts);
break;
case OPER_PRINT:
print_entry(cts);
break;
case OPER_DELETE:
delete_entry(cts);
break;
case OPER_SELECT:
search_entry(cts);
break;
case OPER_SAVE:
savefile_entry(cts,);
break;
case OPER_LOAD:
LoadFile_entry(cts,);
break;
default:
goto exit;
}
}
exit:
free(cts);
return 0;
}
在通讯录这个项目中我能够学到的是代码的分层(很重要的东西)也就是上文说软件的接口层业务层支持层 这个极大的优化了我个人写代码混乱的情况 其次是关于链表的使用还有关于文件保存 注意的是开启文件就要关文件,写入文件后一定要及时写入磁盘
总结:产品的设计思想和设计模式,软件实现代码的分层(注:在学习的前期实现软件的代码可以不是很简洁但一定要分层次 设计的时候一定要提前想好写好策划)