1、老学长的唠叨
在上一个编的数组版本的简化银行系统的基础上改为结构体存储,为还没有学到结构体的学弟学妹们提供一个迁就的方案,现在将程序改为结构体存储了,也希望学弟学妹们有个c语言学习缓冲的时间,这个代码没有用多文件组织的目的是方便代码的拷贝运行,但有利又有弊,程序的可读性大大降低了,如果学过多文件组织的学弟学妹可也在新建项目的时候进行一个程序多文件组织这种方式。通过这个种方式可以让学弟学妹有能力编写出800行左右的接近工程要求的小项目。此外,还为进一步程序设计学习树立新的目标。
好了,还是得重复啰嗦一下关于这个程序,按照模块化程序设计要求,能够满足存款、取款、销户、挂失……功能。例如实现:
1.能够创建账户
2.能够从磁盘文件输入数据
3.能够将数据保存到磁盘文件中
4.实现提供目前账户个人信息的查询
6.实现对账户组的集中管理
所以各位学弟学妹们可以借这个低阶版的银行系统还是可以进行练手的
结构体数据项成员域如下:
通过结构体数组来存储结多位用户的账户信息,如下:
以此初步体会完整系统开发,综合运用已经学过的知识。以下提示中,给出了多个层次的需要,可以先保证最基本、最简单功能的实现,迭代式地完善,实现“改进意见”中的要求。 按照模块化程序设计要求,充分利用函数实现功能。下面为程序的功能界面
这里需要新建俩文件夹一个存放业务员姓名和密码的Worker.txt文件,一个存放用户的密码、状态、用户姓名、账号、余额的account.dat文件。如图:
Worker.txt文件存放业务员姓名和密码:如图
account.dat文件存放用户的密码、状态、用户姓名、账号、余额信息
2、问题描述
#include <stdio.h>
#include<stdlib.h> //itoa所需要的库函数
#include <string.h>
#include <ctype.h>
#include <conio.h>
#define topNum 2000 //系统最多容纳用户数目
int uActual;//实际人数
struct information
{
char AccName[9];//账户名
int password;//密码
char name[10];//用户姓名
double balance;//账户余额
int state;//状态
};
struct information users[topNum];//用结构体数组存放银行所以用户
int iPWD();//业务员密码验证
void uExpotrData();//导出数据
void uImportData();//写入数据
void Work();//办理
int BankMenu();//银行界面
void Deposit();
void Withdraw();//取款
void ChangePWD();//改密
void Cancellation();//销户
void Sleep();//挂失
void Recovery();//解挂
void TransferAccount();//转账
void CreateAccount();//开户
void Qurey();//查询
void creatAccNum(int );
int createPWd(int );
int seek(char []);//账户判断
int see(char []);//账号查询
int verifyAll();
int verifyPAD(int Num);//密码验证
int inputPWD();
int main()
{
printf("+------------------+\n");
printf("+ 欢迎来到CSDN银行 +\n");
printf("+------------------+\n");
if(iPWD())//业务员密码验证
{
uExpotrData();//从文件导出数据
Work();//业务办理
uImportData();//写入数据到文件
}
return 0;
}
/*
功能:验证用户密码
返回值:密码正确,返回1;
不能通过密码验证,返回0
*/
int iPWD()//密码登录
{
int right = 0;
int iTry = 3;
int i ;
char ch;
char inputPsss[20];//输入账户密码
char inputName[20];//输入业务员登录名称
char sNameFile[20];//文件保存业务员登录名称名
char sPassFile[20];//业务员登录密码
FILE *fp;
if((fp = fopen("Worker.txt","r"))==NULL)
{
printf("Cannot open file!");
exit(0);
}
fscanf(fp,"%s %s",sNameFile,sPassFile);
fclose(fp);
do{
i = 0;
printf("请输入业务员用户名:");
scanf("%s",inputName);
printf("\n请输入登录密码:");
while((ch=getch())!= '\r')
{
inputPsss[i++] = ch;
putchar('*');
}
inputPsss[i] = '\0';
fflush(stdin);
printf("\n");
if(strcmp(inputName,sNameFile)==0 && strcmp(inputPsss,sPassFile)==0)
{
right = 1;
break;
}
else
{
iTry--;
if(iTry > 0)
{
printf("输入密码错误%d次,请重新输入\n",4-iTry);
}
else
{
printf("密码输入已达3次,退出系统\n");
}
}
}while(iTry);
return right;
}
/*
功能:从文件中读取储户数据
*/
void uExpotrData()//导入数据
{
int i=0;
FILE *fp;
if((fp = fopen("account.dat","r")) == NULL)
{
printf("File cannot open!\n");
exit(0);
}
if(feof(fp))
{
uActual = 0;
}
else
{
//密码、状态、用户姓名、银行账号、余额写入
while(fscanf(fp,"%d %d %s %s %lf",&users[i].password,&users[i].state,users[i].name,users[i].AccName,&users[i].balance) != EOF)
i++;
fclose(fp);
uActual = i;
}
}
/*
功能:将用户数据保存到文件中
*/
void uImportData()//写入数据
{
int i=0;
FILE *fp;
if((fp = fopen("account.dat","w")) == NULL)
{
printf("File cannot open!\n");
exit(0);
}
//密码、状态、用户名、账号、余额写入
for(i=0;i<uActual;i++)
fprintf(fp,"%d %d %s %s %lf\n",users[i].password,users[i].state,users[i].name,users[i].AccName,users[i].balance) ;
fclose(fp);
}
/*
关于getch()的一点说明:
所在头文件:conio.h
函数用途:从控制台读取一个字符,但不显示在屏幕上
函数原型:int getch(void)
返回值:读取的字符
在不同平台,输入回车,getch()将返回不同数值,而getchar()统一返回10(即\n)
1)windows平台下ENTER键会产生两个转义字符 \r\n,因此getch返回13(\r)。
2)unix、 linux系统中ENTER键只产生 \n ,因此getch返回10(\n)。
3)MAC OS中ENTER键将产生 \r ,因此getch返回13(\r)。
为避免键盘缓存区中未读出的字符影响程序,用fflush(stdin);清除输入缓存区
*/
void Work()//办理
{
int uChoice;
if(uActual == 0)
{
printf("系统未存储用户数据,需办理开户\n");
printf("是否开户:Y/N\n");
if(tolower(getchar())=='y')//tolower()函数将字母转化为小写,非字母不做处理
{
CreateAccount();//开户
}
else
{
printf("退出系统");
exit(0);
}
}
do{
uChoice = BankMenu();//界面菜单选择
switch(uChoice)
{
case 1:
Deposit();//存款
break;
case 2:
Withdraw();//取款
break;
case 3:
ChangePWD();//改密
break;
case 4:
Cancellation();//销户
break;
case 5:
CreateAccount();//开户
break;
case 6:
Sleep();//挂失
break;
case 7:
Recovery();//解挂
break;
case 8:
TransferAccount();//转账
break;
case 9:
Qurey();//查询
break;
case 0:
printf("欢迎下次光临!\n");//退出
break;
default:
printf("请输入选项中的0~9选项序号:");
break;
}
}while(uChoice);
}
/*
功能:显示菜单并由业务员选择
返回值:用户选择的功能,范围0-9
*/
int BankMenu()//银行界面
{
int Choice;//选择业务办理选项
do
{
printf("\n\n");
printf(" +---------------------------------+\n");
printf(" + 办理业务选项 +\n");
printf(" +---------------------------------+\n");
printf(" + 1.存款 2.取款 3.改密 +\n");
printf(" + +\n");
printf(" + 4.销户 5.开户 6.挂失 +\n");
printf(" + +\n");
printf(" + 7.解挂 8.转账 9.查询 +\n");
printf(" + +\n");
printf(" + 0.退出系统 +\n");
printf(" +---------------------------------+\n");
printf("\n");
printf("输入所需办理的业务序号:");
scanf("%d",&Choice);
fflush(stdin);
if(Choice<=9&&Choice>=0)
break;
else
printf("请输入1~9的选项");
}while(1);
return Choice;
}
/*
功能:存款
说明:需要保证账户存在,且处于正常状态
*/
void Deposit()//存款
{
int site;//账户位置
double uMoney;//存款金额
if((site = verifyAll())!= -1)//身份验证
{
printf("请输入存款金额:");
scanf("%lf",&uMoney);//
users[site].balance +=uMoney;
printf("已存款%0.2lf元\n余额为%0.2lf元\n",uMoney,users[site].balance);
}
}
/*
功能:取款
说明:需要保证账户存在,且处于正常状态,另外,余额要足够取
*/
void Withdraw()//取款
{
int site;
double uMoney;
if((site = verifyAll())!= -1)//身份验证
{
printf("请输入取款金额:");
scanf("%lf",&uMoney);
if(uMoney<users[site].balance)
{
users[site].balance -=uMoney;
printf("已取款%0.2lf元\n余额为%0.2lf元\n",uMoney,users[site].balance);
}
else
printf("余额不足!");
}
}
/*
功能:改密码
*/
void ChangePWD()//改密
{
int site;//查找位置
if((site = verifyAll())!= -1)//账号查询,并返回位置
{
if(createPWd(site))
printf("修改成功!");
else
printf("修改失败");
}
else
{
printf("修改失败\n");
}
}
/*
功能:注销账户
说明:找到账户,并将其状态改为2-注销即可。
注销前应该检查余额,应该先取款再注销
*/
void Cancellation()//销户
{
int site;//查找位置
if((site = verifyAll())!= -1)
{
printf("你的账户余额为%0.2lf元\n",users[site].balance);
printf("是否销户Y/N:");
if(tolower(getchar())=='y')//tolower()函数将字母转化为小写,非字母不做处理
{
users[site].state=2;//销户
printf("销户成功\n");
}
else
{
printf("销户失败\n");
}
}
else
{
printf("\n失败\n");
}
return;
}
/*
功能:开户
说明:在进入系统时,在读入数据过程中,已经记录了用户数为N,在数组中对应下标为0~N-1
开户时要增加一个用户,只要为下标为N的数组元素置值,并在成功之后令N++即可。
这样做顺序增加简单,但遗留的后患是,在查询账户时,不得不用顺序查找,这在效率上是不划算的。
改进的手段(1):开户时,根据账号,将数据插入到数组中,使按账号有序,这样做插入时麻烦,但有利于以后要频繁的查询操作
改进的手段(2):账号由系统自动生成,保证其连续,这样在顺序增加的时候,就保证了其有序。
*/
void CreateAccount()//开户
{
if(uActual == topNum)
{
printf("系统存储已达上线,不能开户");
return;
}
printf("正在开户中……\n");
printf("系统生成账号为:");
creatAccNum(uActual);//生成账号
printf("请输入姓名:");
scanf("%s",users[uActual].name);//存入用户名
if(createPWd(uActual)==1)//创建密码并校验
{
printf("请输入存款金额:");
scanf("%lf",&users[uActual].balance);
printf("您已经存入%lf元",users[uActual].balance);
users[uActual].state=0;//写入状态代号为正常,0表示
uActual++;
}
else
{
printf("返回系统选项\n");
}
}
/*
功能:挂失账户
*/
void Sleep()//挂失
{
int site;
if((site = verifyAll())!= -1)//身份验证
{
if(users[site].state==0)//
{
users[site].state=1;
printf("\n挂失成功\n");
}
else if(users[site].state==1)
printf("\n该账已经挂失\n");
else
printf("\n该账户已经销户\n");
}
}
/*
功能:解除挂失
*/
void Recovery()//解挂
{
char Acc_u[20];//账号
int site;//查找位置
printf("请输入账号:");
gets(Acc_u);//标准输入设备读字符串函数
fflush(stdin);
site = see(Acc_u);//账号查询,并返回位置
if(site!= -1)
{
printf("账户名为:%s\n",users[site].name);
if(verifyPAD(site))
{
if(users[site].state==1)//
{
users[site].state=0;
printf("\n解挂成功\n");
}
else if(users[site].state==0)
printf("\n该账户未挂失\n");
else
printf("\n该账户已经销户\n");
}
}
}
/*
功能:转账
说明:需要保证两个账户都存在,且处于正常状态,另外,转出账户的余额要足够
*/
void TransferAccount()//转账
{
int site;
int Num;
double uMoney;
char str[20];//存储输入账号数字
if((site = verifyAll())!= -1)//身份验证
{
printf("请输入需要转入的账户:");
gets(str);//标准输入设备读字符串函数
fflush(stdin);
if((Num = seek(str))!= -1)//
{
printf("请输入转账金额");
scanf("%lf",&uMoney);
if(uMoney < users[site].balance)
{
users[site].balance -= uMoney;
users[Num].balance += uMoney;
printf("转账成功\n账户余额为%lf",users[site].balance);
}
else
{
printf("余额不足");
}
}
}
return;
}
/*
功能:查询账户
说明:显示账户信息
*/
void Qurey()//查询
{
int site;
if((site = verifyAll( ))!= -1)//身份验证
{
printf("账户余额为%lf",users[site].balance);
}
}
/*
作用:创建以CSDN字母开头的8位数账号,当数字小于4位数时候,数字左边用零位补齐
备注:这里调用了itoa():将整型值转换为字符串;它是一个非标准的C / C++语言功能;
需要的库函数#include<stdlib.h> ,还有用到#include<string.h>函数库里面的strlen统计字符数组包含的长度;
以及strrry()函数将字符串倒置。*/
void creatAccNum(int num)//根据实际人数自动生成账号
{
int n;
char s[20];//将num以字符形式保存
char N[20]="CSDN";
itoa(num,s,10); // itoa(int,char,要转换的十进制数)
strrev(s);//数组倒置
n=strlen(s);//长度
while(n<4)
{
s[n]='0';
n++;
}
s[n]='\0';
strrev(s);
strcat(N,s);//数字连接
printf("%s\n",N);
strcpy(users[num].AccName,N);//拷贝到用户账户里面
return;
}
/*
功能:对新密码进行核对
说明:第一次有输入的密码与第二次输入的密码进行比较,如果密码相同则返回1表示设置成功,否者返回0
*/
int createPWd(int Num)//创建密码并校验
{
int uPass1;//第一次输入要更改的密码
int uPass2;//第二次输入要更改的密码
printf("\n创建密码:");
uPass1 = inputPWD();
printf("\n请重新输入密码:");
//fflush(stdin);
uPass2 = inputPWD();
if(uPass1==uPass2)
{
users[Num].password=uPass1;//写入密码
printf("\n创建密码完成\n");
return 1;
}
else
{
printf("\n密码不一致!\n");
}
return 0;
}
/*
功能:对账户进行身份验证
说明:分别验证账户,密码,当验证成功返回序号下标,账户验证失败返回-1
*/
int verifyAll()
{
char str[20];//存储输入账号数字
printf("请输入账号:");
gets(str);//标准输入设备读字符串函数
fflush(stdin);
int site;//账号在结构体数组中的位置
if((site =seek(str))!= -1)//账户验证
{
if(verifyPAD(site)!= -1)//密码验证
{
printf("\n成功\n");
}
else
site = -1;
}
else
printf("\n失败\n");
return site;
}
/*
功能:对输入账户进行验证
功能:当对输入的
*/
int seek(char str[])//账户判断
{
int judge=-1;
int Num=0;
if((Num = see(str))!= -1)//: 将一个串与另一个比较, 不管大小写,
{
if(users[Num].state==0)//
{
printf("姓名:%s\n",users[Num].name);
judge = Num;//账户序号返回
}
else if(users[Num].state==1)
{
printf("该账户已经挂失\n");
}
else
{
printf("该账户已经销户");
}
}
else
{
printf("该账户不存在");
}
return judge;
}
/*
功能:根据账号查询用户,返回用户的下标
入口参数:要查询用户的账号
返回值:如果该用户存在,返回该用户在数组中的下标,否则,返回一个负数(-1)
*/
int see(char str[])//账号查询
{
int ptr;//存放strnicmp函数返回值
int i;
char N[]="CSDN";//账号前缀
int digit;//长度
int judge=-1;
int Num = 0;//将数字字符转换为数字
digit = strlen(str);//统计长度
ptr = strnicmp(str,N,4);//
if(digit == 8&&ptr == 0)
{
for(i=4;i<8;i++)
if(isdigit(str[i]))//检查其参数是否为十进制数字字符
Num = Num*10+(str[i]-'0');
}
else
{
if(digit != 8)
printf("账号位数出错\n");
if(ptr !=0)
printf("账号请以CSDN开头\n");
}
if(strcmpi(str,users[Num].AccName)==0)//: 将一个串与另一个比较, 不管大小写,
{
printf("查找成功\n");
judge = Num;
}
else
printf("查找失败\n");
return judge;
}
/*
功能:密码进行验证
功能:当输入密码,如果与原设置密码一致则返回数组下标序号,否则返回-1
*/
int verifyPAD(int Num)//密码验证
{
int Pass_u;
int judge=-1;
printf("请输入密码:");
Pass_u = inputPWD();
if(Pass_u == users[Num].password)
judge = Num;//返回存在的序号
else
{
printf("\n密码错误\n");
}
return judge;
}
/*
功能:输入密码
返回值:整型的密码值
技术说明:
(1)此功能在多个模块中都要用到且功能单一,故分离出来,单独作业一个函数
(2)为了便于在输入中只显示*,接受输入时以字符形式输入,而后转为对应的整型数
(3)规定密码由不全为0的6位数字构成(当开头是'0'时,实际不足6位),一旦输入错误将重新输入
附:在实际的系统中,密码通常用字符串描述,即使只允许出现数字字符
首先要明白,从键盘输入的数据会先存到缓冲区,然后在存放到内存中。但是数据存放到内存中之后,
缓冲区的数据并没有释放。如果不给电脑一个清空缓冲区的命令就会造成输入无效的情况。所以需要用
到fflush(stdin)释放缓冲区。具体在C语言中主要表现在getchar()等从键盘单个或逐个数据读取的函数上
*/
int inputPWD()//密码创建
{
int i;
char ch;//接受字符型密码
int uPass=0;//字符型密码转换为数字
do
{
for(i=0;i<6;i++)
{
ch=getch();
printf("*");
if(isdigit(ch))//检查输入是否为十进制数字字符
uPass=uPass*10+(ch - '0');
else
{
uPass=0;
break;
}
}
fflush(stdin);
if(uPass == 0)
{
printf("\n");
printf("密码仅能够为数字\n");
printf("请重新输入密码\n\n");
}
else
{
break;
}
}while(1);
return uPass;
}
输出结果
1.登录
2.开户
3.存款
4……(省略)
心得体会
在编写程序的时候,学弟学妹们可以用来练练手,老学长在敲代码时候可能多多少少有错哦,当然如果你发现有什么错误的地方或者编写的不合理的地方,还得多多指正,一起进步嘛。这个简化版银行系统从精简版本、函数版本、数值版本、到现在的结构体版本、代码量也开始再增加。接下来可能就是最终版本,需要用到链表和MySql,还是希望有同学和我这个老学长一起开心的敲代码,哈哈哈哈哈!