项目名称:ATM账户存取业务
1.项目介绍
1.1意义
ATM账户系统采用哈希表这种高效数据结构,可以快速读取后台用户信息文件,通过判断用户的卡号和密码匹配,方便了管理用户的账户信息,提高了账户信息的安全性和可管理性,让使用者轻松实现账户的信息修改、销户、存取业务和查询等操作,避免人工进行操作,提高银行存取款业务,并且可在用户操作完成后,重新读取哈希表数据并写入后台用户信息文件,而不需要手动进行复杂的管理工作。
1.2主要内容
用户管理系统是一个基于哈希表的数据结构的应用程序,用于管理用户账户。该系统可以执行以下操作:
- 新用户注册
- 老用户登录
- 销户
- 查找用户
- 修改用户信息
- 用户存取业务
2.概要设计
思路:该系统使用哈希表作为主要数据结构,可以实现快速登录、查找、修改和删除操作,使得用户账户的管理变得高效和方便。
哈希表采用链式哈希法来解决哈希冲突。在链式哈希法中,每个用户都是一个链表,当哈希冲突发生时,相同哈希值的键值对会以头插法的方式添加到相应的链表中。
项目流程图
3.数据结构
4.具体算法
该系统使用以下算法来实现创建哈希表、从文件中导入用户信息到Hash表,注册新用户、老用户销户、修改老用户信息、老用户进行存取业务,导出Hash表中用户信息至文件、和销毁哈希表的功能。
4.1创建哈希表
参数:无
返回值:返回定义好的哈希表
注意点:Hash表中数组元素的个数是采用宏定义
源码为:
Hash* creat_Hash()
{
//定义一个Hash表指针
Hash *pHash=(Hash *)malloc(sizeof(Hash));
if(NULL==pHash)
{
perror("Malloc_ERR");
return NULL;
}
//赋值
//Hash表中数组中各元素的数据类型为节点
pHash->arr=(Link **)malloc((sizeof(Link*))*SIZE);
if(NULL==pHash->arr)
{
perror("Malloc_ERR");
return NULL;
}
memset(pHash->arr,0,(sizeof(Link*))*SIZE);
pHash->count=SIZE; //Hash表中数组个数值采用宏定义
return pHash;
}
4.2从文件中导入用户信息
//函数功能:从文件中读取储户信息
//参数1:Hash表首地址 Hash *pHash
//返回值:成功返回OK,失败返回失败原因
算法思路:打开文件,创建储户结构体数据类型,读取文件中储户的信息,调用Add函数添加到Hash表内存中
源码为:
int Load_Depositor(Hash *pHash)
{
Dep person={{0},0,0,{0}};//接受读取到的储户信息
//入参检查
if(NULL==pHash)
{
return Hash_NO_EXIST;
}
//打开文件读取信息
FILE *FR=fopen("user_message.txt","r");
if(NULL==FR)
{
return File_NO_EXIST;
}
while(!feof(FR))
{
if(fscanf(FR,"%s%d%lf%s",person.name, \
&person.card, \
&person.balance,person.pwd)!=EOF)//读取数据
{
Add_Depositor(pHash,person);
}
}//信息插入到Hash表中
fclose(FR);
return OK;
}
4.3添加用户
函数功能:用户登陆
参数1:Hash表中首地址 Hash *pHash
参数2:要插入的储户 Dep New_dep;
返回值:成功返回OK,失败返回失败原因
算法思路:调用Search 函数判断插入的储户结构体是否存在,不存在则申请节点空间保存储户结构体信息,调用Hashfun得出Hash表中对应的数组下标,采用头插法将节点插入Hash表中。
源码如下:
int Add_Depositor(Hash*pHash,Dep New_dep)
{
//入参判断
if(NULL==pHash)
{
return Hash_NO_EXIST;
}
//调用Serch_Depositor函数查找是否存在
Link* pRet=Search_Depositor(pHash,New_dep);
//若不存在,创建新结点赋值,并插入
if(NULL!=pRet)
{
printf("储户已存在,无须再新增!!\n");
return Dep_EXSIT;
}
//创建新结点,判断,赋值
Link *pNew=(Link *)malloc(sizeof(Link));
if(NULL==pNew)
{
return Malloc_ERR;
}
memset(pNew,0,sizeof(Link));
pNew->person=New_dep;
//调用Hashfun()函数得出数组下标
int pos=Hashfun(New_dep.card);
//插入新结点
pNew->pNext=pHash->arr[pos];
pHash->arr[pos]=pNew;
return OK;
}
4.4判断老用户是否存在
函数功能: 通过卡号密码查询保存用户信息的节点
参数1:Hash表首地址 Hash *pHash
参数2:储户类型Dep* pOld_user
返回值:成功返回储户结点首地址 失败返回NULL
算法思路:search得出银行卡号得出Hash表中对应的节点,节点确定后进一步匹配储户输入的密码,来最终确定储户节点;
#include"../include/Bank.h"
//返回值:老用户存在的结点Link *pRet
Link * determine_Olduser(Hash *pHash,Dep* pOld_user)
{
Link *pRet=NULL;
char cc[20]={0};
//调用Search_Depositor 查找用户结点
while(1)
{
printf("请输入登录账号:");
scanf("%d",&(pOld_user->card));
pRet=Search_Depositor(pHash,*pOld_user);
if(NULL==pRet)//用户不存在
{
printf("用户不存在!\n");
printf("是否重新输入? 是:Y 否:N\n");
scanf("%s",cc);
//结点不存在判断是否结束还是重新输入
if(0==strcmp(cc,"Y")||0==strcmp(cc,"y"))
{
continue;
}
else if(0==strcmp(cc,"N")||0==strcmp(cc,"n"))
{
break;
}
else
{
printf("指令有误!!\n");
return NULL;
}
}
else
{
printf("请输入登录密码:");
scanf("%s",pOld_user->pwd);
if(0==strcmp(pOld_user->pwd,pRet->person.pwd))
{
printf("登录成功!!\n");
break;
}
else//密码错误
{
printf("密码错误!!\n");
printf("是否重新输入? 是:Y 否:N\n");
scanf("%s",cc);
if(0==strcmp(cc,"Y")||0==strcmp(cc,"y"))
{
continue;
}
else if(0==strcmp(cc,"N")||0==strcmp(cc,"n"))
{
pRet=NULL;
break;
}
else
{
printf("指令有误!!\n");
return NULL;
}
}
}
break;
}
return pRet;
//找到结点返回
}
4.5修改老用户信息
函数功能:通过执行选项来修改人员信息
参数1:Hash表首地址 Hash *pHash
参数2: 老储户数据类型 Dep
返回值:成功返回OK,失败返回失败原因
算法思路:调用Search函数找到储户节点下标,在选择修改用户具体信息
int modify(Hash *pHash,Dep Old_user)
{
int put=0;//决定取钱还是存钱
char New_name[50]={0};//要修改的名字
char New_pwd[50]={0};//要修改的密码
char cc[100]={0};;//接收是否继续存储
//入参判断
if( NULL== pHash)
{
return Hash_NO_EXIST;
}
//调用Search_Depositor函数寻找目标姓名
Link *pRet=Search_Depositor(pHash,Old_user);
//目标不存在直接返回
if(NULL==pRet)
{
return Dep_NO_EXIST;
}
//目标存在执行存取操作
while(1)
{
printf("选择你要修改的信息:\n");
printf("1:姓名 2:密码\n");
scanf("%d",&put);
if(put!=1&&put!=2)
{
printf("操作有误,请重新输入");
continue;
}
break;
}
switch(put)
{
case 1://修改姓名
while(1)
{
printf("请输入新名字\n");
scanf("%s",New_name);
printf("修改前名字为:%s\n",pRet->person.name);
printf("修改后名字为:%s\n",New_name);
printf("请确认是否修改?是:Y 否:N\n");
scanf("%s",cc);
if(0==strcmp("Y",cc)||0==strcmp("y",cc))
{
strcpy(pRet->person.name,New_name);
break;
}
else if(0==strcmp("N",cc)||0==strcmp("n",cc))
{
continue;
}
else
{
printf("指令错误!!\n");
continue;
}
}
break;
case 2://修改密码
while(1)
{
printf("请输入新密码\n");
scanf("%s",New_pwd);
printf("修改前密码为:%s\n",pRet->person.pwd);
printf("修改后密码为:%s\n",New_pwd);
printf("请确认是否修改?是:Y 否:N\n");
scanf("%s",cc);
if(0==strcmp("Y",cc)||0==strcmp("y",cc))
{
strcpy(pRet->person.pwd,New_pwd);
break;
}
else if(0==strcmp("N",cc)||0==strcmp("n",cc))
{
continue;
}
else
{
printf("指令错误!!\n");
continue;
}
}
break;
}
return OK;
}
4.5 老用户进行存储业务
#include"../include/Bank.h"
//函数功能:通过执行选项来确定储户存钱还是取钱
//参数1:Hash表首地址 Hash *pHash
//参数3: 老储户 Old_user
//返回值:成功返回OK,失败返回失败原因
int money(Hash *pHash,Dep Old_user)
{
int put=0;//决定取钱还是存钱
double amount=0;//存取的金额
char cc[100]={0};;//接收是否继续存储
//入参判断
if( NULL== pHash)
{
return Hash_NO_EXIST;
}
//调用Search_Depositor函数寻找目标姓名
Link *pRet=Search_Depositor(pHash,Old_user);
//目标不存在直接返回
if(NULL==pRet)
{
return Dep_NO_EXIST;
}
//目标存在执行存取操作
while(1)
{
printf("请输入你想执行的操作:1:存钱 2:取钱\n");
scanf("%d",&put);
if(put!=1&&put!=2)
{
printf("操作有误,请重新输入");
continue;
}
break;
}
switch(put)
{
case IN://存钱
while(1)
{
printf("请输入你要存的金额\n");
scanf("%lf",&amount);
pRet->person.balance=pRet->person.balance+amount;
printf("是否退出?是:Y 退出:N\n");
scanf("%s",cc);
if(0==strcmp("Y",cc)||0==strcmp("y",cc))
{
break;
}
}
break;
case OUT://取钱
while(1)
{
printf("请输入你要取的金额\n");
scanf("%lf",&amount);
if(amount>pRet->person.balance)
{
printf("账户余额不足\n");
printf("是否继续?是:Y 否:N\n");
scanf("%s",cc);
if(0==strcmp("Y",cc)||0==strcmp("y",cc))
{
continue;
}
else if(0==strcmp("N",cc)||0==strcmp("n",cc))
{
break;
}
}
pRet->person.balance=pRet->person.balance-amount;
printf("是否继续?是:yes 退出:no\n");
scanf("%s",cc);
if(0==strcmp("n",cc)||0==strcmp("N",cc))
{
break;
}
else if(0==strcmp("Y",cc)||0==strcmp("y",cc))
{
continue;
}
else
{
printf("指令有误!!\n");
return OK;
}
}
break;
}
return OK;
}
4.6 新用户申请注册得到随机三位卡号
函数功能:得到8位随即账号
成功返回ok,失败返回失败原因
注意点:得到的卡号要重新在主函数中调用search函数,已确保没有重复卡号
int get_cardID(int *pcard)
{
*pcard=rand()%(int)(pow(10,3));
return OK;
}
4.7导出用户信息至文件
遍历整个哈希表,将其中的信息输出至指定文件中, 首先以只写方式打开指定文件,如果不存在就创建一个,每次写入信息时,清空上次的内容。遍历每个结点时,调用fprintf函数将该名用户的信息输出至文件中,末尾加上”\n”,最后关闭文件。
#include"../include/Bank.h"
int export_Depositor(Hash *pHash)
{
//入参判断
if(NULL==pHash)
{
return Hash_NO_EXIST;
}
//打开文件
FILE *FW=fopen("user_message.txt","w");
//for循还 写入数据域
Link *pFind=NULL;
int i=0;
for(i=0;i<pHash->count;i++)
{
pFind=pHash->arr[i];
while(pFind!=NULL)
{
fprintf(FW,"%s %d %.2lf %s\n",pFind->person.name,pFind->person.card, \
pFind->person.balance,pFind->person.pwd);
pFind=pFind->pNext;
}
}
fclose(FW);
return OK;
}
4.8销毁整个哈希表
函数功能:销毁整个哈希表
参数:1.哈希表首地址的地址。
返回值:成功返回OK,失败返回失败原因
哈希表申请的空间释放时,要确保申请每个空间都被释放了。
int destory(Hash **ppHash)
{
//入参判断
if(NULL==*ppHash)
{
return Hash_NO_EXIST;
}
int i=0;
Link *pDel=NULL;
//for循环 销毁链表
for(i=0;i<(*ppHash)->count;i++)
{
pDel=(*ppHash)->arr[i];//指针指向数组元素位置
while(pDel!=NULL)//数组元素不为空时
{
(*ppHash)->arr[i]=pDel->pNext;//保护后结点
free(pDel);//释放结点
pDel=(*ppHash)->arr[i];//指针重新指向数组元素位置
}
}
//销毁*ppHash->arr
free((*ppHash)->arr);
//销毁*ppHash
free(*ppHash);
return OK;
}
5.调试分析
5.1判断老用户登录
通过卡号查找到用户信息节点后,必须再继续判断输入的密码是否与节点中的密码匹配,如果不进行匹配可能会导致登录错误,防止信息出现混乱。
5.2销毁哈希表出错
销毁哈希表时,应传入存储哈希表地址的地址.