项目背景
随着银行业务的发展和客户需求的多样化,传统的银行管理方式已经不能满足现代化的要求,需要使用计算机技术来提高银行的效率和服务质量。因此,本项目旨在开发一个基于C语言的银行管理系统,实现对银行用户和管理员的信息管理和业务处理。
项目目标
本项目的主要目标是:
- 为银行用户提供一个方便、安全、高效的平台,实现注册、登录、存款、取款、转账、查询余额、修改密码、修改个人信息、余额变动记录、用户日志记录等功能。
- 为银行管理员提供一个简洁、易用、智能的工具,实现管理员登录、查看所有用户、查看单个用户信息、查看银行总体金额变化、帮助用户修改密码、管理员日志记录等功能。
- 为银行提供一个稳定、可靠、可扩展的系统,实现对用户和管理员的数据存储、检索、更新、备份等功能。
项目特色
本项目的主要的特色是:
融会贯通地使用了C语言初级课程中各种复杂结构,结构体,指针,哈希表,链表,文件,函数模块化。
1. 基础知识准备
建议初学者还是先学习C语言基础知识,这里只做简单的回顾。
1.1. 指针
指针是C语言的一种变量类型,它存储了一个内存地址。指针变量的值是一个地址,该地址指向内存中的另一个位置,而这个位置可能存储一个值或者另一个指针。
指针放在开头就证明了它在C语言中的重要性,同时也是劝退百分之五十的初学者学习C语言的拦路虎,大家要认真学呀,指针在C语言用的还是很频繁的,学透了指针也会给我们开发带来大大的方便。
指针的主要作用是允许程序间接访问内存,从而实现高效的数据操作。
指针变量的定义形式为:
数据类型 *指针变量名;
其中,数据类型
表示指针所指向的数据类型,*
表示指针类型,指针变量名
是指针变量的名称。例如,int *p;
表示定义了一个指向整型变量的指针p。
指针是初学者学习C语言的一个槛,所以在这里强调一下指针变量的使用需要注意的一些点:
- 指针变量必须先定义再使用;
- 指针变量必须初始化后才能使用,否则就是野指针;
- 指针变量可以通过
*
运算符来访问其指向的内存地址上的值,通过&
运算符来获取某个变量的地址,*
和&
是一对相反的操作; - 指针变量可以进行算术运算,表示地址的变化;
- 指针变量可以作为函数参数传递,从而实现函数间的数据共享;
1.2. 结构体
结构体是C语言的一种自定义的数据类型,它允许用户将不同类型的数据组合成一个整体。
结构体的定义形式为:
其中,struct
是关键字,是结构体类型的标志。结构体内部的元素,也是组成成分,一般称为成员。
结构体的使用频率不亚于指针,因此需要重视,结构体的定义和使用非常简单,以下是结构体的定义和使用代码:
在上面的示例中,定义了一个名为Student
的结构体,它三个成员组成:name
、age
和height
。可以通过.
运算符来访问结构体的成员。
1.3. 哈希表
哈希表是一种数据结构,它可以用于快速查找和插入数据。哈希表是由若干个键值对组成,每个键值对包含了一个键和一个值。
哈希表的主要特点是可以通过键快速查找对应的值,而不需要遍历整个数据集。哈希表的实现原理是将键通过哈希函数转化为一个索引,然后将值存储在该索引对应的位置上。哈希表的主要优点是查找和插入的时间复杂度都是常数级别,因此可以实现高效的数据操作。
哈希表的实现需要考虑以下几个方面:
- 哈希函数的设计:哈希函数是将键映射到哈希表中的索引的函数。一个好的哈希函数应该尽可能的将键均匀的映射到哈希表中的索引,以避免哈希冲突。
- 哈希表的冲突处理:由于哈希函数的设计不可能完全避免哈希冲突,因此需要考虑如何处理哈希冲突。常见的哈希冲突处理方法包括链地址法,开放地址法等;
- 哈希表的扩容:当哈希表中的元素数量超过了哈希表大小的一定比例时,需要对哈希表进行扩容,以保证哈希表的性能;
下面是哈希表的定义和创建代码:
这个项目使用的哈希表结构,是用数组+链表实现的哈希表拉链法,便于处理哈希冲突,在网上扒拉下一张图片,如下(侵权删)。
1.4. 链表
链表是一种重要的数据结构,它可以动态的分配内存地址,实现高效的数据操作。链表由一系列的节点构成,每个节点包含了数据和指向下一个节点的指针。
链表的特点:
- 解决数组无法存储多种数据类型的问题;
- 解决数组中,元素个数无法改变的限制;
- 数组移动元素的过程中,要对元素进行大范围的移动,很耗费时间,效率也不高;
链表的基本构成是头指针(head),若干个节点,且最后一个节点要指向空。
链表的实现原理是头指针指向链表的第一个节点,然后第一个节点中的指针指向下一个节点,然后一次指到最后一个节点,这样就构成了一条链表。
链表的数据结构定义如下:
1.5. 文件
C语言的文件是一种用于存储数据的抽象概念。文件可以是文本文件或二进制文件。
在C语言中,我们可以通过I/O函数来对文件进行读写操作。相关的操作包括打开、读写、关闭和设置缓冲区。
下面是一个简单的文件读写操作的例子:
fprintf()
和fputs()
函数向文件中写入数据。然后,使用fscanf()
和fgets()
函数从文件中读取数据,并使用printf()
函数将其输出到控制台上。
需要注意的是,在进行文件读写操作之前,需要先打开文件,使用完毕后再关闭文件。
1.6. 函数模块化
模块化编程是一种将程序分割成模块并进行编程的技术。在C语言中,模块是由函数和数据组成的逻辑单元,模块化编程的好处包括代码可读性和可维护性的提高,程序的复杂性降低,便于代码重用等。
在C语言中,可以通过函数来实行模块化编程。一个C语言程序可以由一个主函数和若干其他函数构成。函数的定义和调用可以使程序更加清晰和易于维护。
2. 用户板块设计
2.1. 结构体等一些准备
下面是这个项目关于用户信息的结构体定义。
账户卡号唯一id,是以哈希表存储的键,其与身份证密切相关的。
2.2. 注册
用户注册:用户可以输入自己的姓名、手机号码、身份证号码、密码等信息,创建一个银行账户,并获得一个唯一的账户卡号。
- 首先,注册函数定义了一个字符数组 pw 用于存储用户输入的确认密码,以及一个字符串 oper 用于记录操作类型(这里是 “注册”)。
- 然后,函数动态分配了两个结构体变量的内存空间:data 用于存储 DataType 类型的数据(包括键和值,其中值是 Customer 类型),user 用于存储注册用户的所有信息。
- 函数提示用户输入姓名、手机号码和身份证号码,并将这些信息存储在 user 结构体中。然后,函数将字符串类型的身份证号码转换为数字类型的银行账号,并存储在 user 结构体中。
- 如果哈希映射中已经存在该账号,函数会提示用户该用户已存在并结束注册过程。
- 如果哈希映射中不存在该账号,函数会提示用户注册成功,并显示用户的银行卡号。
- 接下来,函数会提示用户输入银行卡密码(6位数),并要求用户再次输入以确认。如果两次输入的密码不一致,函数会提示用户重新输入。
- 当用户成功输入一致的密码后,函数会将用户的余额设置为0,并获取当前时间作为用户创建时间。
- 然后,函数将用户的银行账号和用户信息存储在 data 结构体中,并将 data 存储在哈希映射和链表中。
- 最后,函数会将用户信息写入文件,并记录用户操作日志。如果两次输入的密码一致,函数会提示用户银行卡注册成功,并释放 user 结构体的内存空间。
void sign(){
char pw[7]; // 用于确认密码
char *oper = "注册";
DataType* data = (DataType*)malloc(sizeof(DataType));
Customer* user = (Customer*)malloc(sizeof(Customer)); // 存储注册用户的所有信息
printf("欢迎来到注册界面\n");
printf("请依次输入您的名字 手机号码 身份证:\n");
scanf("%s %s %s",&user->accountName,&user->mobile,&user->sfz);
user->accountCard = atoi(user->sfz); // 将字符串类型的身份证转化为数字类型的银行账号
if(constainsKey(hashmap,user->accountCard)){
printf("用户已存在,请去登录\n");
return;
}
printf("注册成功,您的银行卡号为:%d\n",user->accountCard);
while(1){
printf("请输入您的银行卡密码(6位数):\n");
scanf("%s",&user->password);
printf("请再次输入你的银行卡密码:\n");
scanf("%s",&pw);
if(strcmp(user->password,pw) != 0){
printf("两次输入密码不一致,请重新输入\n");
continue;
}else{
break;
}
}
user->money = 0.0; // 注册的时候用户的余额为0
currentTime(user->Time); // 获取当前时间存入user中,表示创建用户的时间
data->key = user->accountCard;
data->value = (Customer *) malloc(sizeof(Customer));
memcpy(data->value, user, sizeof(Customer)); // 将注册用户信息内容复制到data->value中
putData(hashmap,data); // 将刚刚注册的用户信息存储到hashmap中
addNode(head,data); // 将刚刚注册的用户信息存储到head中
user_filePut(user);
userLogsRecords(user,oper);
printf("两次输入密码正确,银行卡注册成功!\n");
free(user); // 为什么可以free掉,也忘记了
return;
}
2.3. 用户登录
用户登录:用户可以输入自己的账户卡号和密码,登录到自己的账户,进行后续的操作。
- 用户登录函数首先定义了一个整数 accountCard 用于存储用户输入的账号,以及一个字符数组 password 用于存储用户输入的密码。此外,函数还定义了一个字符串 oper 用于记录操作类型(这里是 “登录”)。
- 函数输出欢迎信息,并进入一个无限循环,提示用户输入银行卡号和密码,知道登录正确的账号就跳出循环。
- 函数使用 getData 函数检查哈希映射中是否存在用户输入的账号。如果存在,函数将获取到的用户信息赋值给 custCurrent。
- 接下来,函数使用 strcmp 函数比较用户输入的密码和 custCurrent 中存储的密码。如果两者相同,函数将用户信息记录到日志文件中,并输出登录成功的信息,然后结束函数。如果两者不同,函数将输出密码输入错误的信息,并重新开始循环。
- 如果哈希映射中不存在用户输入的账号,函数将输出账号和密码输入错误的信息,并重新开始循环。
// 登录
void login(){
int accountCard; // 存储登录用户输入的账号,用于后续做账号比较
char password[7]; // 存储登录用户输入的密码,用于后续做密码比较
char *oper = "登录"; // 登录操作,写入日志文件
printf("欢迎来到登录界面\n");
while(1){
printf("请输入您的银行卡号和密码:\n");
scanf("%d %s",&accountCard,&password);
// 去记录中查找,检查1.是否存在此卡号;2.密码是否正确
if((Customer*)getData(hashmap,accountCard)!=NULL){
// 接下来检查密码是否输入正确
custCurrent = (Customer*)getData(hashmap,accountCard); // 查找成功,就将查找到的信息赋值一份给custCurrent,便于后面一直拿到用户的信息
if(strcmp(password,custCurrent->password)==0){
// 将用户信息记录到日志logs记录文件中
userLogsRecords(custCurrent,oper);
printf("登录成功,请继续接下来的操作\n");
return;
}else{
printf("密码输入错误!请重新操作\n");
}
}else{
printf("银行卡号和密码输入错误,请重新输入:\n");
continue;
}
}
}
2.4. 存款
存款:用户可以输入存款金额,将现金存入自己的账户,账户余额会相应增加。
- saveMoney 函数:
- 这个函数的主要功能是存款。首先,函数定义了一个字符串 oper 用于记录操作类型(这里是 “存款”),以及一个浮点数 money 用于存储用户要存款的金额。
- 然后,函数从哈希映射中获取当前用户的信息,并将其存储在 user 结构体中。
- 函数提示用户输入要存储的金额(只能存储整百元),并将输入的金额存储在 money 中。
- 如果用户输入的金额是整百数,函数会将这个金额添加到用户的余额中,并更新链表和文件中的用户信息。然后,函数会将操作记录到日志文件中,并更新银行的总金额。最后,函数会输出存款成功的信息,并显示用户的银行卡号和当前余额。
- 如果用户输入的金额不是整百数,函数会提示用户重新输入整百的金额。
- saveMoneyLogical 函数:
- 这个函数的主要功能是实现存款的逻辑业务。首先,函数定义了两个整数 i 和 flag,并输出欢迎信息。
- 然后,函数进入一个循环,提示用户选择存款或退出,并将用户的选择存储在 i 中。
- 如果用户选择存款,函数会调用 saveMoney 函数。如果用户选择退出,函数会结束循环。如果用户的输入不是 1 或 2,函数会提示用户输入有误,并重新开始循环。
// 存款
void saveMoney(){
char *oper = "存款";
float money;
Customer *user = (Customer*)getData(hashmap,custCurrent->accountCard);
user = custCurrent;
printf("欢迎来到存款界面\n");
printf("请输入您要存储的钱(只存储整百元)\n");
scanf("%f",&money);
if(((int)money)/100 != 0 && ((int)money)%100 == 0){
custCurrent->money += money;
updateNode(head,custCurrent,user,sizeof(custCurrent));//将更新后的用户信息,也在链表中更新一份
// 修改的信息存入源信息文件时,需要原来的时间,从链表中取出的时间有问题
userUpgrade_filePut(head);
userLogsRecords(custCurrent,oper);
Bank_TotalAmountChange(oper,custCurrent,money);
// 修改的信息应该存入日志文件中
printf("存储成功!\n");
printf("您的银行卡号为:%d,现在的余额为:%f\n",custCurrent->accountCard,custCurrent->money);
}else{
printf("您存储的钱不是整百数,请重新存储整百的钱\n");
}
return;
}
// 存款逻辑业务
void saveMoneyLogical(){
int i=0,flag=1;
printf("欢迎来到存款界面\n");
while(flag){
printf("1 存款\n");
printf("2 退出\n");
scanf("%d",&i);
switch(i){
case 1:saveMoney();break;
case 2:flag=0;break;
default:printf("输入有误,请重新输入!\n");break;
}
}
return;
}
2.5. 取款
取款:用户可以输入取款金额,从自己的账户中取出现金,账户余额会相应减少。
- drawMoney 函数:
- 这个函数的主要功能是取款。首先,函数定义了一个字符串 oper 用于记录操作类型(这里是 “取款”),以及一个整数 money 用于存储用户要取款的金额。
- 然后,函数从哈希映射中获取当前用户的信息,并将其存储在 user 结构体中。
- 函数提示用户输入要取款的金额(只能取款整百元),并将输入的金额存储在 money 中。
- 如果用户输入的金额是整百数,函数会检查用户的余额是否足够。如果足够,函数会从用户的余额中扣除取款金额,并更新链表和文件中的用户信息。然后,函数会将操作记录到日志文件中,并更新银行的总金额。最后,函数会输出取款成功的信息,并显示用户的银行卡号和当前余额。如果余额不足,函数会提示用户余额不足,并要求用户重新操作。
- 如果用户输入的金额不是整百数,函数会提示用户重新输入整百的金额。
- drawMoneyLogical 函数:
- 这个函数的主要功能是实现取款的逻辑业务。首先,函数定义了两个整数 i 和 flag,并输出欢迎信息。
- 然后,函数进入一个循环,提示用户选择取款或退出,并将用户的选择存储在 i 中。
- 如果用户选择取款,函数会调用 drawMoney 函数。如果用户选择退出,函数会结束循环。如果用户的输入不是 1 或 2,函数会提示用户输入有误,并重新开始循环。
// 取款
void drawMoney(){
char *oper = "取款";
int money;
Customer *user = (Customer*)getData(hashmap,custCurrent->accountCard);
user = custCurrent;
printf("请输入您需要取款的金额(只能取款整百元)\n");
scanf("%d",&money);
if(((int)money)/100 != 0 && ((int)money)%100 == 0){
if(money <= custCurrent->money){
printf("您的余额足够,可以为您取出\n");
custCurrent->money -= money;
userUpgrade_filePut(head);
userLogsRecords(custCurrent,oper);
Bank_TotalAmountChange(oper,custCurrent,money);
printf("取款成功!\n");
printf("您的银行卡号为:%d,现在的余额为:%f\n",custCurrent->accountCard,custCurrent->money);
}else{
printf("您的余额不足,无法取出您需要的钱,请重新操作\n");
}
}else{
printf("您取出的钱不是整百,请重新输入\n");
}
}
// 取款逻辑业务
void drawMoneyLogical(){
int i=0,flag=1;
printf("欢迎来到取款界面\n");
while(flag){
printf("1 取款\n");
printf("2 退出\n");
scanf("%d",&i);
switch(i){
case 1:drawMoney();break;
case 2:flag=0;break;
default:printf("输入有误,请重新输入!\n");break;
}
}
return;
}
2.6. 转账
转账:用户可以输入对方的账户卡号和转账金额,将自己的账户中的钱转给对方,自己的账户余额会相应减少,对方的账户余额会相应增加。
- 转账函数首先定义了两个字符串 oper 和 oper1 用于记录操作类型(这里是 “转账”),以及一个 Customer 类型的指针 custTMP_1 用于存储接收转账的用户信息。
- 然后,函数从哈希映射中获取当前用户的信息,并将其存储在 user 结构体中。
- 函数输出欢迎信息,并进入一个无限循环,提示用户输入接收转账的账号和姓名。
- 函数使用 getData 函数检查哈希映射中是否存在用户输入的账号。如果存在,函数将获取到的用户信息赋值给 custTMP_1。
- 接下来,函数使用 strcmp 函数比较用户输入的姓名和 custTMP_1 中存储的姓名。如果两者相同,函数会输出接收转账的用户名,并进入另一个无限循环。
- 在这个循环中,函数提示用户输入转账金额,并要求用户确认转账。如果用户确认转账,函数会检查用户输入的金额是否是整百数,以及用户的余额是否足够。
- 如果用户输入的金额是整百数且余额足够,函数会从用户的余额中扣除转账金额,并将转账金额添加到 custTMP_1 的余额中。然后,函数会更新链表和文件中的用户信息,并将操作记录到日志文件中。最后,函数会输出转账成功的信息,并结束函数。
- 如果用户输入的金额不是整百数或者余额不足,函数会提示用户重新输入,并重新开始循环。
- 如果用户输入的姓名和 custTMP_1 中存储的姓名不一致,函数会提示用户姓名输入错误,并重新开始循环。
- 如果哈希映射中不存在用户输入的账号,函数会提示用户账号不存在,并结束函数。
// 转账--目前只能本行转账本行
void transferMoney(){
char oper[20] = "转账";
char oper1[20] = "转账";
Customer* custTMP_1;
int flag,accountCard;
char accountName[10];
float money=0;
Customer *user = (Customer*)getData(hashmap,custCurrent->accountCard);
user = custCurrent;
printf("欢迎来到转账界面\n");
while(1){
printf("请输入您需要转账的账号 姓名\n");
scanf("%d %s",&accountCard,&accountName);
if((custTMP_1 = (Customer*)getData(hashmap,accountCard)) != NULL){
// 账号存在,开始校验姓名,get函数返回对应卡号的用户信息
if(strcmp(custTMP_1->accountName,accountName) == 0){
printf("你要转账的用户名为:%s\n",custTMP_1->accountName);
while(1){
printf("请输入转账金额:\n");
scanf("%f",&money);
// 在这里确认转账其实也可以做一个界面(优化)
printf("确认转账请输入1,输入其他取消转账\n");
scanf("%d",&flag);
if(flag==1){
// 继续校验自己的账户是否有足够转账的金额
if(((int)money)/100 != 0 && ((int)money)%100 == 0){
if((custCurrent->money - money) >= 0){
custCurrent->money -= money;
custTMP_1->money += money;
userUpgrade_filePut(head);
userLogsRecords(custCurrent,oper);
Bank_TotalAmountChange(strcat(oper,"发起方"),custCurrent,money);
Bank_TotalAmountChange(strcat(oper1,"接收方"),custTMP_1,money);
printf("转账成功!");
return;
}else{
printf("您的账户上余额不足,请重新输入\n");
continue;
}
}else{
printf("请输入整百转账金额\n");
}
}else{
printf("取消转账!\n");
return;
}
}
}else{
printf("您输入银行账号对应的账号姓名不对,请重新输入\n");
continue;
}
}else{
printf("您输入的账号不存在!");
return;
}
}
}
2.7. 查询余额
查询余额:用户可以查看自己的账户余额,以及最近的余额变动记录。
- 查询余额函数首先定义了一个字符串 oper 用于记录操作类型(这里是 “查询余额”)。
- 然后,函数输出欢迎信息,并显示用户的银行卡号和当前余额。
- 最后,函数将操作记录到日志文件中。
// 查询余额
void showMoney(){
char *oper = "查询余额";
printf("欢迎来到查询余额界面\n");
printf("您的银行卡号为:%d\n",custCurrent->accountCard);
printf("您的余额为:%lf\n",custCurrent->money);
userLogsRecords(custCurrent,oper);
return;
}
2.8. 修改密码
修改密码:用户可以输入旧密码和新密码,修改自己的账户密码。
- 函数首先定义了一个字符串 oper 用于记录操作类型(这里是 “修改密码”),以及两个字符数组 passWord 和 passWord_1 用于存储用户输入的原密码和新密码。
- 然后,函数从哈希映射中获取当前用户的信息,并将其存储在 user 结构体中。
- 函数输出欢迎信息,并进入一个无限循环,提示用户输入原密码。
- 函数使用 strcmp 函数比较用户输入的原密码和 user 结构体中存储的密码。如果两者相同,函数会提示用户输入新密码。
- 如果用户输入的新密码和原密码相同,函数会提示用户不能修改为原密码,并重新开始循环。如果用户输入的新密码和原密码不同,函数会结束循环。
- 如果用户输入的原密码和 user 结构体中存储的密码不一致,函数会提示用户原密码输入错误,并重新开始循环。
- 当用户成功输入一个与原密码不同的新密码后,函数会使用 strcpy 函数将新密码复制到 user 结构体中,并更新链表和文件中的用户信息。然后,函数会将操作记录到日志文件中。
- 最后,函数会输出密码修改成功的信息。
// 修改密码
void changePW(){
char *oper = "修改密码";
char passWord[7],passWord_1[7];
Customer *user = (Customer*)getData(hashmap,custCurrent->accountCard);
printf("欢迎来到修改密码界面\n");
while(1){
printf("请输入原密码:\n");
scanf("%s",&passWord);
if(strcmp(passWord,user->password) == 0){
printf("请输入你想要修改的密码:\n");
scanf("%s",&passWord_1);
if(strcmp(passWord,passWord_1)==0){
printf("不能修改为原密码!请重新操作\n");
continue;
}else{
break;
}
}else{
printf("原密码输入错误!请重新操作\n");
continue;
}
}
strcpy(user->password,passWord_1);
custCurrent = user;
userUpgrade_filePut(head);
userLogsRecords(user,oper);
printf("密码修改成功!\n");
}
2.9. 修改个人信息
修改个人信息:用户可以修改自己的姓名、手机号码、身份证号码等信息,但不能修改账户卡号。
- 函数首先定义了一个字符串 oper 用于记录操作类型(这里是 “修改用户信息”),以及两个整数 i 和 flag。
- 然后,函数从哈希映射中获取当前用户的信息,并将其存储在 user 结构体中。
- 函数输出欢迎信息,并显示用户的银行卡号、账户名称、手机号码、身份证号码、密码(以星号表示)和余额。
- 函数进入一个循环,提示用户选择要修改的信息(账户名称、电话号码或密码),并将用户的选择存储在 i 中。
- 如果用户选择修改账户名称,函数会提示用户输入新的账户名称,并将其存储在 user 结构体中。如果用户选择修改电话号码,函数会提示用户输入新的电话号码,并将其存储在 user 结构体中。如果用户选择修改密码,函数会提示用户输入新的密码,并将其存储在 user 结构体中。
- 如果用户选择退出修改个人信息界面,函数会结束循环。如果用户的输入不是 1、2、3 或 0,函数会提示用户输入有误,并重新开始循环。
- 在每次循环结束时,函数都会显示用户的银行卡号、账户名称、手机号码、身份证号码、密码(以星号表示)和余额。
- 当用户完成修改后,函数会更新链表和文件中的用户信息,并将操作记录到日志文件中。
- 最后,函数会输出修改用户信息成功的信息,并暂停程序执行。
// 修改用户信息
void updateInfo(){
char *oper = "修改用户信息";
int i=0;
int flag = 1;
Customer *user = (Customer*)getData(hashmap,custCurrent->accountCard);
printf("欢迎来到修改个人信息界面:\n");
printf("您的信息为:");
printf("银行卡号:%d,账户名称:%s,手机号码:%s,身份证:%s,密码:******,余额:%lf\n\n",user->accountCard,user->accountName,user->mobile,user->sfz,user->money);
while(flag){
printf("请问你要修改什么信息?\n");
printf("1 修改账户名称\n"); // 有些人改名了
printf("2 修改电话号码\n");
printf("3 修改密码\n"); // 认为在修改信息的时候,密码也属于个人信息,应该兼容
printf("0 退出修改个人信息界面\n");
scanf("%d",&i);
// 修改的信息如果是一样的没有校验!
switch(i){
case 1:printf("请输入账户名称:\n");scanf("%s", &user->accountName);break;
case 2:printf("请输入电话号码:\n");scanf("%s", &user->mobile);break;
case 3:printf("请输入要修改的密码:\n");scanf("%s", &user->password);break;
case 0:flag = 0;break;
default:printf("输入有误,请输入对应的值");
}
printf("您的信息为:");
printf("银行卡号:%d,账户名称:%s,手机号码:%s,身份证:%s,密码:******,余额:%lf\n\n",user->accountCard,user->accountName,user->mobile,user->sfz,user->money);
}
custCurrent = user;
userUpgrade_filePut(head);
userLogsRecords(user,oper);
printf("修改用户信息成功! ");
system("pause");
return;
}
2.10. 余额变动记录
余额变动记录:当用户的操作会改变自己的余额时,就会将此次操作记录到日志文件中。
- 函数首先定义了一个 FILE 类型的指针 fp 用于操作文件,一个字符数组 time 用于存储时间,两个浮点数 money 和 BankTotalMoney 用于存储操作的金额和银行总体金额,一个字符数组 oper 用于存储操作类型,以及一个整数 accountCard 用于存储账号。
- 然后,函数使用 fopen 函数以读取模式打开名为 “银行总金额变化文件.txt” 的文件,并将返回的文件指针赋值给 fp。如果文件打开失败,函数会输出错误信息,并暂停程序执行。
- 函数输出提示信息,并进入一个循环,使用 feof 函数检查是否已经到达文件末尾。
- 在循环中,函数使用 fscanf 函数从文件中读取时间、账号、操作类型、操作的金额和银行总体金额,并将这些信息存储在相应的变量中。
- 如果读取到的账号与当前用户的账号相同,函数会输出时间、账号、操作类型和操作的金额。
- 当到达文件末尾时,循环结束。
- 最后,函数使用 fclose 函数关闭文件。
// 从银行总金额变化文件中获取当前用户全部的信息
void getUserBalanceChangeFromFile() {
FILE *fp;
char time[30];
double money = 0.0; // 操作的金额
double BankTotalMoney = 0.0; // 银行总体金额
char oper[20];
int accountCard = 0;
fp = fopen("银行总金额变化文件.txt", "r");
if (fp == NULL) {
perror("打开文件失败啦");
system("pause");
exit(1);
}
printf("您的余额变动记录为:\n");
while (!feof(fp)) {
fscanf(fp,"%s %d %s %lf %lf\n",&time,&accountCard,&oper,&money,&BankTotalMoney);
if(custCurrent->accountCard == accountCard){
printf("%s %d %s %lf\n",time,accountCard,oper,money);
}
}
fclose(fp);
}
2.11. 用户日志记录
用户日志记录:用户的每一次操作都会被记录到一个文件中,包括操作时间、操作类型、操作金额等信息,方便用户查看自己的操作历史。
- 用户日志登录函数首先定义了一个 FILE 类型的指针 fp 用于操作文件,以及一个字符数组 time 用于存储当前时间。
- 然后,函数使用 fopen 函数以追加模式打开名为 “用户logs日志文件.txt” 的文件,并将返回的文件指针赋值给 fp。
- 函数调用 currentTime 函数获取当前时间,并将返回的时间字符串存储在 time 中。
- 接下来,函数使用 fprintf 函数将当前时间、用户的银行卡号、用户的姓名和操作类型写入文件。
- 最后,函数使用 fclose 函数关闭文件。
// 用户的日志logs记录
void userLogsRecords(Customer *custTmp,char *oper){
FILE *fp;
char time[30];
fp = fopen("用户logs日志文件.txt","a");
currentTime(time);
fprintf(fp,"%s %d %s %s\n",time,custTmp->accountCard,custTmp->accountName,oper);
fclose(fp);
return;
}
3. 管理员板块设计
3.1. 管理员登录
管理员登录:管理员暂时只能输入输入默认的账户卡号和密码,登录到管理员的账户,进行后续的操作。
- 管理员登录函数首先定义了两个字符数组
adminName_login
和adminPW_login
,用于存储管理员的账号和密码。 - 然后,函数进入一个无限循环,直到管理员成功登录后才会跳出循环。
- 在循环中,函数首先使用
printf
函数提示管理员输入账号,并使用scanf
函数读取输入的账号,存储到adminName_login
中。 - 接下来,函数同样使用
printf
和scanf
函数提示管理员输入密码,并将输入的密码存储到adminPW_login
中。 - 然后,函数使用
strcmp
函数比较输入的账号和密码是否与预设的管理员账号和密码相同。 - 如果账号和密码都正确,那么函数使用
printf
函数打印出 "管理员登录成功" 的信息,并通过break
语句跳出循环。 - 如果账号或密码有误,那么函数使用
printf
函数打印出 "账号或密码错误" 的信息,并继续下一轮的循环,重新输入账号和密码。
// 管理员的登录
void adminLogin(){
char adminName_login[5];
char adminPW_login[5];
while(1){
printf("请输入管理员账号:\n");
scanf("%s",adminName_login);
printf("请输入管理员密码:\n");
scanf("%s",adminPW_login);
if(strcmp(adminName,adminName_login)==0 && strcmp(adminPW,adminPW_login)==0){
printf("管理员登录成功\n");
break;
}else{
printf("账号或密码错误\n");
}
}
}
3.2. 查看所有用户
查看所有用户:管理员可以查看所有用户的基本信息,如账户卡号、姓名、手机号码、身份证号码、密码、余额等。
- 查看所用用户函数首先定义了一个
LinkNode
类型的指针node
,用于遍历链表中的节点,以及一个Customer
类型的指针user
,用于存储用户的信息。 - 然后,函数定义了一个字符指针
oper
,并将 "查看所有用户信息" 赋值给它,这将作为操作类型写入日志文件。 - 接下来,函数使用
nextNode
函数获取链表头节点的下一个节点,并将返回的节点指针赋值给node
。 - 函数进入一个循环,直到
node
为NULL
时结束循环。 - 在循环中,函数首先为
user
分配内存,然后将node
的数据部分转换为DataType
类型,再将其value
成员转换为Customer
类型,并将结果赋值给user
。 - 然后,函数使用
printf
函数打印出用户的账号开户时间、账户卡号、账户名称、手机号码、身份证、密码和账户金额。 - 最后,函数使用
nextNode
函数获取node
的下一个节点,并将返回的节点指针赋值给node
。 - 循环结束后,函数调用
adminLogsRecords
函数,将管理员的账号、密码和操作类型写入日志文件。
// 查看所有用户
void showAllUser(){
// 直接打印linknode即可
LinkNode *node;
char *oper = "查看所有用户信息"; // 登录操作,写入日志文件
Customer* user;
node = nextNode(head);
printf("所有用户信息:\n");
while(node){
user = (Customer*)malloc(sizeof(Customer));
user = ((Customer*)((DataType*)node->data)->value);
printf("账号开户时间:%s,账户卡号:%d,账户名称:%s,手机号码:%s,身份证:%s,密码:%s,账户金额:%f\n",user->Time,user->accountCard,user->accountName,user->mobile,user->sfz,user->password,user->money);
node = nextNode(node);
}
adminLogsRecords(adminName,adminPW,oper);
}
3.3. 查看单个用户信息
查看单个用户信息:管理员可以输入一个用户的账户卡号,查看该用户的详细信息,以及该用户的操作记录。
- 函数首先定义了一个整型变量
accountCard
,用于存储要查看的用户的银行账号,以及一个字符指针oper
,并将 "查看单个用户信息" 赋值给它,这将作为操作类型写入日志文件。 - 然后,函数为
Customer
类型的指针user
分配内存,用于存储用户的信息。 - 接下来,函数使用
printf
函数提示输入要查看的用户的银行账号,并使用scanf
函数读取输入的账号,存储到accountCard
中。 - 函数进入一个无限循环,直到找到用户或者用户选择退出时才会结束循环。
- 在循环中,函数首先使用
getData
函数尝试从哈希表hashmap
中获取accountCard
对应的用户信息。 - 如果获取到的用户信息不为
NULL
,那么函数将用户信息赋值给user
,并使用printf
函数打印出用户的账户卡号、账户名称、手机号码、身份证、密码和账户金额,然后通过break
语句跳出循环。 - 如果获取到的用户信息为
NULL
,那么函数使用printf
函数打印出 "抱歉,你查找的用户不存在。请继续操作" 的信息,并继续下一轮的循环,重新输入账号。 - 循环结束后,函数调用
adminLogsRecords
函数,将管理员的账号、密码和操作类型写入日志文件。
// 查看单个用户的信息和操作记录
void showSignalUser(){
int accountCard; // 账户卡号
char *oper = "查看单个用户信息";
Customer* user = (Customer*)malloc(sizeof(Customer));
printf("请输入您要查看用户信息的银行账号:\n");
scanf("%d",&accountCard);
while(1){
if((Customer*)getData(hashmap,accountCard) != NULL){
user = (Customer*)getData(hashmap,accountCard);
printf("您需要查看的账户卡号 %d的用户信息是:账户名称:%s,手机号码:%s,身份证:%s,密码:%s,账户金额:%f\n",user->accountCard,user->accountName,user->mobile,user->sfz,user->password,user->money);
break;
}else{
printf("抱歉,你查找的用户不存在。请继续操作\n");
continue;
}
}
adminLogsRecords(adminName,adminPW,oper);
}
3.4. 查看银行总体金额变化
查看银行总体金额变化:管理员可以查看银行的总资产,以及最近的资产变动记录,包括存款、取款、转账等操作的总金额。
- 函数首先定义了一个
FILE
类型的指针fp
,用于操作文件,以及一个字符数组time
,一个双精度浮点数money
和BankTotalMoney
,一个字符数组oper
,和一个整型变量accountCard
。 - 然后,函数使用
fopen
函数以读取模式打开名为 "银行总金额变化文件.txt" 的文件,并将返回的文件指针赋值给fp
。 - 如果文件打开失败,函数将打印出错误信息,并暂停程序,然后退出程序。
- 接下来,函数使用
printf
函数打印出 "银行银行整体金额变化:" 的信息。 - 函数进入一个循环,直到文件结束时结束循环。
- 在循环中,函数使用
fscanf
函数从文件中读取时间、账户卡号、操作类型、操作的金额和银行总体金额,并分别存储到time
、accountCard
、oper
、money
和BankTotalMoney
中。 - 然后,函数使用
printf
函数打印出时间、账户卡号、操作类型、操作的金额和银行总体金额。 - 循环结束后,函数使用
fclose
函数关闭文件。
void showBankMoney() {
FILE *fp;
char time[30];
double money = 0.0; // 操作的金额
double BankTotalMoney = 0.0; // 银行总体金额
char oper[20];
int accountCard;
fp = fopen("银行总金额变化文件.txt", "r");
if (fp == NULL) {
perror("打开文件失败啦");
system("pause");
exit(1);
}
printf("银行银行整体金额变化:\n");
while (!feof(fp)) {
fscanf(fp,"%s %d %s %lf %lf\n",&time,&accountCard,&oper,&money,&BankTotalMoney);
printf("%s %d %s %lf %lf\n",time,accountCard,oper,money,BankTotalMoney);
}
fclose(fp);
}
3.5. 帮助用户修改密码
助用户修改密码:管理员可以输入一个用户的账户卡号,为该用户重置密码,以便于用户忘记密码时找回账户。
- 函数首先定义了一个整型变量
accountCard
,用于存储要修改密码的用户的银行账号,以及三个字符数组originPW
、password
和password_confirm
,分别用于存储原密码、新密码和确认新密码。 - 然后,函数定义了一个字符指针
oper
,并将 "帮助用户修改密码" 赋值给它,这将作为操作类型写入日志文件。 - 接下来,函数为
Customer
类型的指针user
分配内存,用于存储用户的信息。 - 函数进入一个无限循环,直到密码修改成功或者用户选择退出时才会结束循环。
- 在循环中,函数首先使用
printf
和scanf
函数提示输入要修改密码的用户的银行账号和原密码,并分别存储到accountCard
和originPW
中。 - 然后,函数使用
getData
函数从哈希表hashmap
中获取accountCard
对应的用户信息,并将返回的用户信息赋值给user
。 - 如果获取到的用户信息不为
NULL
,那么函数使用strcmp
函数比较user
的密码和originPW
是否相同。 - 如果密码相同,那么函数使用
printf
和scanf
函数提示输入新密码和确认新密码,并分别存储到password
和password_confirm
中。 - 然后,函数使用
strcmp
函数比较user
的密码和password_confirm
是否相同,以及password
和password_confirm
是否相同。 - 如果
user
的密码和password_confirm
不相同,并且password
和password_confirm
相同,那么函数使用printf
函数打印出 "两次输入密码一致" 的信息,并通过break
语句跳出循环。 - 如果
user
的密码和password_confirm
相同,那么函数使用printf
函数打印出 "修改密码不能与原密码一致!" 的信息,并继续下一轮的循环,重新输入新密码和确认新密码。 - 如果
password
和password_confirm
不相同,那么函数使用printf
函数打印出 "两次输入的密码不一致,请继续操作" 的信息,并继续下一轮的循环,重新输入新密码和确认新密码。 - 如果
user
的密码和originPW
不相同,那么函数使用printf
函数打印出 "该用户密码输入错误!" 的信息,并继续下一轮的循环,重新输入银行账号和原密码。 - 如果获取到的用户信息为
NULL
,那么函数使用printf
函数打印出 "抱歉,你查找的用户不存在。请继续操作" 的信息,并继续下一轮的循环,重新输入银行账号。 - 循环结束后,函数使用
strcpy
函数将password_confirm
复制到user
的密码中,然后使用printf
函数打印出 "密码修改成功!" 的信息。 - 最后,函数调用
userUpgrade_filePut
和adminLogsRecords
函数,将用户信息写入文件,并将管理员的账号、密码和操作类型写入日志文件。
void assistUserChangePW(){
int accountCard; // 账户卡号
char originPW[7]; // 账号原密码
char password[7],password_confirm[7]; // 修改密码和修改密码的确认密码
char *oper = "帮助用户修改密码";
Customer* user = (Customer*)malloc(sizeof(Customer));
while(1){
printf("请输入您要修改密码的银行账号:\n");
scanf("%d",&accountCard);
printf("请输入您要修改密码的原密码:\n");
scanf("%s",&originPW);
user = (Customer*)getData(hashmap,accountCard);
if(user != NULL){
if(strcmp(user->password,originPW) == 0){
printf("账号密码匹配成功!\n");
printf("请输入您需要修改的密码\n");
scanf("%s",&password);
printf("请再次输入您需要修改的密码\n");
scanf("%s",&password_confirm);
if(strcmp(user->password,password_confirm) != 0){
if(strcmp(password_confirm,password) == 0){
printf("两次输入密码一致\n");
break;
}else{
printf("两次输入的密码不一致,请继续操作");
continue;
}
}else{
printf("修改密码不能与原密码一致!\n");
continue;
}
}else{
printf("该用户密码输入错误!\n");
continue;
}
}else{
printf("抱歉,你查找的用户不存在。请继续操作\n");
continue;
}
}
strcpy(user->password,password_confirm); // user已经指向了hashmap,为什么不能直接修改
printf("密码修改成功!\n");
userUpgrade_filePut(head);
adminLogsRecords(adminName,adminPW,oper);
return;
}
3.6. 管理员日志记录
管理员日志记录:管理员的每一次操作都会被记录到一个文件中,包括操作时间、操作类型等信息,方便管理员查看自己的操作历史。
- 函数首先定义了一个
FILE
类型的指针fp
,用于操作文件,以及一个字符数组time
,用于存储当前时间。 - 然后,函数使用
fopen
函数以追加模式打开名为 "管理员操作日志文件.txt" 的文件,并将返回的文件指针赋值给fp
。 - 接下来,函数调用
currentTime
函数获取当前时间,并将返回的时间字符串存储在time
中。 - 函数使用
fprintf
函数将当前时间、管理员的账号、密码和操作类型写入文件。 - 最后,函数使用
fclose
函数关闭文件。
// 将管理员操作日志文件存入文件中
static void adminLogsRecords(char *adminName,char *adminPW,char *oper){
FILE *fp;
char time[30];
fp = fopen("管理员操作日志文件.txt","a");
currentTime(time);
fprintf(fp,"%s %s %s %s\n",time,adminName,adminPW,oper);
fclose(fp);
return;
}
4. 后续
代码是我复习C语言时做的一个小的ATM系统,代码在我个人的测试下,还比较健壮,但是肯定还会存在一些隐藏的bug等待修复,代码中还存在一些可以优化的地方,之后有时间会去改进,或者大家有需求也可以提,鞭策我去优化和修bug。
对了,代码放在github,项目地址是:github.com/lpdbz/ATM_s…
觉得有帮助的同学,可以点一个免费的start鼓励一下呀,之后有时间优化ATM代码也会更新的此仓库的。我写的项目代码也都会放在自己的仓库。