目录

1.项目介绍

1.1项目期望目标与内容

1.2 项目实现的主要过程

1.3分文件操作

1.4项目实现平台

1.5说明

2.项目的具体实现过程

2.1头文件BankCard.h编写

2.1.1结构体类型设计

2.1.2功能函数声明

2.1BankCard.cpp功能函数文件编写

2.3头文件masager.h的编写

2.3.1结构体类型设计

2.3.2相关功能函数声明

2.4masager.h管理员相关函数实现

2.5 start.cpp逻辑功能实现文件编写

2.6 main.cpp测试文件

3.总结


1.项目介绍

1.1项目期望目标与内容

本次C语言项目目标为通过C语言模拟实现自助取款机功能,无图形化界面。通过打印选项与提示,用户通过键盘输入,实现人机交互。人物角色分为两类:管理员与用户。管理员先进行注册,完成后登录,根据用户信息进行卡的注册、挂失、激活和注销等操作;用户通过卡号和密码登录账号,进行存款、取款、转账等操作。

1.2 项目实现的主要过程

1. 设计了六个结构体数据类型:MUser、Sqlist_madsger、User、Sqlist_cards、Card、Record,分别用来定义六种不同变量:管理员、管理员名单、用户、卡库(保存已注册的卡)、卡和卡的流水记录。

2. 确定目标系统的功能:初始化卡库、初始化管理员名单、初始化管理员、管理员注册、管理员登录、初始化用户、初始化卡、注册卡、注销卡、扩容卡库、查找卡、挂失卡、激活卡、登录、存款、取款、转账、查余额、查看流水信息、查看开户时间和流水信息记录等,完成功能函数声明。

3. 对功能函数进行具体实现。

4. 测试。

1.3分文件操作

1)头文件Bank_card.h:包含结构体User、Sqlist_cards、Card、Record的设计与和与用户、卡库、卡、卡的流水记录相关的功能函数的声明。

2)程序文件BankCard.cpp:包含了对头文件Bank_card.h中声明的函数的实现。

3)头文件masager.h:包含结构体MUser、Sqlist_madsger的设计和与和管理员、管理员名单相关的功能函数的声明。

4)程序文件masager.cpp:包含了对头文件masager.h声明的函数的实现。

5)程序文件start.cpp:实现了start函数,start函数调用了自助取款机系统所需的所有功能函数,并且实现了整个系统的逻辑功能。

6)程序文件main.cpp:定义了卡库变量与管理员名单变量,调用start函数,将两个变量传入start函数中,进行整个系统的功能测试。

分文件编写代码可以使单个文件的代码量减少,减少查阅与调试代码的难度。在较大的项目中好处更加明显。在较大项目中,需要多人协作完成开发任务,每个人会有不同部分的开发任务。在分文件开发时,多人同时进行项目开发,增加了开发效率,并且在开发时,每个人都在独立的文件,不会影响到他人的代码(比如修改了别人的代码)。

1.4项目实现平台

Visual Studio 2019

1.5说明

项目实现过程采用软件工程方法学的喷泉模型,对于系统功能的确认、实现和测试是无缝衔接、反复迭代的。在初期,确定了整个系统的可行性后,并没有对系统功能进行完善地确定,就可以进行功能函数实现;在功能函数实现时就可以进行测试。在后面的任务中发现前阶段的任务有错或不足,或者对前阶段未完成的任务有了灵感时,可以返回前阶段任务进行完善。对此反复迭代,不断完善,最终完成系统的实现。

2.项目的具体实现过程

2.1头文件BankCard.h编写

2.1.1结构体类型设计

在头文件Bankcard.h包含结构体类型User、Sqlist_cards、Card、Record的设计。

1.用户数据类型设计,用来保存用户信息 。用户的属性包括姓名、身份证号和电话号。

//用户类型
typedef struct User {char u_name[10];char idcard[20];//身份证char phone[12];
}User;

2.银行卡记录类型设计,用来记录银行卡的流水信息。记录的属性包括操作时间,操作的类型和涉及的金额。

// 记录类型  ->  存放该银行卡对应的流水信息的类型
typedef struct Record {char op_now[20]; //当前操作卡的时间char op_type[10]; //操作类型int money; //涉及金额  :存款类型  100   -100
}Record;

3.银行卡数据类型设计 ,用来保存一张卡的账号信息、存款金额、卡的状态(是否丢失或被锁)、卡的持有人的信息、卡的流水记录、流水记录的有效个数,还有开户信息。

其中,卡的持有人的信息是嵌套的User型结构体变量。流水记录的个数是根据用户对卡进行存款、取款、转账等一系列操作产生的(包括开户),其数量是不定的。所以使用Record型指针保存流水记录,使用动态内存开辟的方法为指针开辟内存保存记录,内存不够时,可以使用realloc()为保存记录的这段内存扩容。比使用数组更加灵活。

//银行卡类型
typedef struct BankCard {int id;//卡号int passwd;//密码int money;//账号存款金额bool islocked; //是否被锁 :登录错误3次 锁卡int count;  //记录当前卡 登录错误的次数    bool isloss; //是否被挂失User user; //存储储户信息Record* records; //记录该卡所有的流水明细int record_size; //记录流水信息的有效个数char create_card_date[30]; //开户日期
}BankCard;

  4.卡库数据类型设计 ,用来保存已注册的卡,其属性还包括有效卡的个数、卡库的容量大小。

卡的数量也是由用户数量而决定的(假设一个用户只注册一张卡),所以使用BankCard指针来保存卡。卡是按照注册的顺序一张一张保存进去的,所以也相当于用了一个数组来保存卡,只不过数组在堆区里,并且数组的长度可以根据我们需要而增加。可以以数组的形式访问卡。

//卡库类型
typedef struct SqList_BankCards {BankCard* cards_array;//用指针保存卡int cards_size;  //记录有效卡个数int cards_length; // 容量大小
}SqList_BankCards,*PList;

2.1.2功能函数声明

假设:A.h,B.cpp,C.cpp文件在同一项目中。在A.h头文件中完成结构体数据类型的设计和函数声明后,在B.cpp程序文件中引用A.h文件,并完成其中声明的函数的实现。如果我们想要在C.cpp程序文件中使用A.h文件中的结构体数据类型或使用B.cpp中实现的函数,就可以直接使用#include"A.h"预处理指令,将A.h文件引用后就行了。因为使用同一项目中的其他文件中的函数,要在自己文件中进行声明,告诉系统有这个函数,任何系统会去链接这个函数,进行调用操作。#include"A.h"相对于把A.h文件中的内容拷贝到自己的文件中,所以既有了数据类型,又有了函数声明,就可以随便使用了。

这也是把结构体数据类型设计和功能函数的声明同放在头文件的原因,方便使用。

功能函数的声明(共20个):

//初始化卡库
void init_sqlist_cards(PList list);
//初始化用户
void init_user(User* puser, const char* u_name, const char* u_phone, const char* idcard);
//初始化银行卡
void init_card(BankCard* pcard,int id,int passwd,int money,User* user);//注册卡
bool register_card(PList plist, int id, int passwd, int money, User* user);
bool IsFull(PList list);//判断卡库是否存满
bool Grow(PList list);//为卡库扩容//以卡号在卡库中寻找卡,找到后返回其位置下标
int SearchCard(PList list, int id);
//登录,以卡号、密码
int login_card(PList plist, int id, int passwd);//挂失卡,以身份证
void lost(PList plist, const char* idcard);
//激活卡
void active(PList plist, const char* idcard);//存钱
void savemoney(BankCard* card, int money);
//取钱
bool withdrawmoney(BankCard* card, int money);
//转账
bool transfer(BankCard* card, BankCard* usercard, int money);void getmoney(BankCard* card);//查看余额
void getwatermessage(BankCard* card);//查看流水记录
void get_create_card_date(BankCard* card);//查看开户日期
void localrecord(BankCard* card, const char* type, int money);//记录流水//消除卡的流水记录,因为涉及到动态内存开辟,所以要单独释放其内存
void destoryCardRecord(BankCard* card);
//注销卡
void destoryCard(PList plist,int id);
//程序最后释放所有开辟的内存
void destory(PList plist);

因为在整个ATM功能中,用户模块的功能更多一些,所以在项目开发前期,我们以用户相关功能为主体进行开发、测试,并将注册卡、注销卡、挂失卡和激活卡等管理员的权限功能由用户来执行测试,后期再将管理员功能添加上去。所以我们将大部分的功能函数的声明添加在BankCard.h文件中。

2.1BankCard.cpp功能函数文件编写

实参变量作为形参传入函数时,系统会帮形参开辟相同大小的空间,并将其值拷贝过来。结构体变量通常由多个基本类型变量、指针,甚至嵌套结构体构成,占用内存特别大。所有我们使用指针指向结构体变量,然后将指针作为参数传入函数。(指针大小只有4字节(32位系统)或8字节(64位系统))

1.卡库初始化函数

void init_sqlist_cards(PList list) {list->cards_array = (BankCard*)malloc(sizeof(BankCard) * CARDSIZE);//(1)assert(list->cards_array != NULL);//判断内存开辟成功list->cards_size = 0;//(2)list->cards_length = CARDSIZE;//(3)
}

卡库变量在函数外声明好以后, 将指向整个卡库的指针传入函数,CARDSIZE宏定义为100。

代码(1):malloc()函数为cards_array指针开辟了总大小为100个卡类型大小的内存,可以放下100张卡(详情可见C语言动态内存开辟);

代码(2):将卡的个数初始化为0,每放入一张卡,cards_size就+1(还未+1前,cards_size就是卡在卡库中的顺序下标);

代码(3):将卡库的容量初始化为100。

2.用户初始化函数

void init_user(User* puser, const char* u_name,const char* idcard, const char* u_phone ) {strcpy(puser->idcard,idcard);strcpy(puser->phone, u_phone);strcpy(puser->u_name, u_name);
}

用户变量在函数外声明好以后,将指向用户变量的指针传入函数,通过strcpy()字符串拷贝函数将字符串型用户信息(用户信息由键盘输入,传参进函数)拷贝给用户变量,完成初始化赋值。

3.卡初始化函数

void init_card(BankCard* pcard, int id, int passwd, int money, User* user) {init_user(&pcard->user,user->u_name,user->phone,user->idcard);//(1)pcard->id = id;//账号pcard->passwd = passwd;//密码pcard->money = money;//开户金额pcard->islocked = 0;//0表示没被锁pcard->isloss = 0;//0表示没丢pcard->count = 0;//卡登录密码错误的次数初始化为0pcard->records = (Record*)malloc(sizeof(Record) * RECORDSIZE);//(2)assert(pcard->records != NULL);//判断内存开辟成功pcard->record_size = 0;//卡流水记录的条数初始化为0
}

卡变量在函数外声明好以后,将指向卡变量的指针传入函数,使用传入的参数对卡的各类属性进行初始化赋值。

代码(1):调用了用户初始化函数,根据传入的用户信息对卡的用户信息进行初始化;

代码(2):为卡中指针records开辟了总大小能放下100条记录的内存(RECORDSIZE宏定义为100)。

4.卡库判满函数

bool IsFull(PList list) {assert(list!= NULL) ;//传入的卡库要进行过初始化,//要不然卡的有效个数和卡库容量无法比较。return list->cards_size == list->cards_length;
}

将指向卡库的指针传入函数,当卡库中卡的有效个数等于卡库的容量时,卡库就满了,放不下了。返回true,也就是1。

5.卡库扩容函数

bool Grow(PList list) {int len = list->cards_length *2;//卡库长度即容量*2BankCard* p = (BankCard*)realloc(list->cards_array,sizeof(BankCard) * CARDSIZE*len);if (p != NULL) {list->cards_array = p;p = NULL;list->cards_length = len;return 1;}else return 0;
}

将指向卡库的指针传入函数。第二句代码为动态内存扩容代码,realloc()函数将卡库容量扩容为原来的2倍(详情可见C语言动态内存开辟的realloc部分)。先用相同类型的指针p接收开辟好的地址,开辟失败会返回NULL,如果没失败就将这段地址赋值给list->cards_array。再使指针p指向空(可省略,p的存在不影响什么,但是我不想让它指向那片地址)。再修改卡库的长度cards_length。

6. 在卡库中查找卡的函数

int SearchCard(PList list, int id) {//cards_size为卡库中卡的个数,从头到尾遍历一遍for (int i = 0; i < list->cards_size; i++) {if (id == list->cards_array[i].id)return i;}return -1;
}

已卡号在卡库中找卡,如果找到,就返回其下标;没找到就说明卡号不存在,还没注册,返回-1。

7.注册卡函数

bool register_card(PList list, int id, int passwd, int money, User* user) {if (IsFull(list)) {//进行判满,其返回值直接作为判断条件,满时返回1。if (!Grow(list)) { //进行扩容,扩容失败返回0,再执行printf代码。printf("注册卡数达到上限。\n");return 0;}}int a = SearchCard(list, id);//查找idif (a != -1) {//id存在,a就不是-1printf("卡已注册过,请登录。\n");return 0;}BankCard card;//声明卡变量init_card(&card, id, passwd, money, user);//初始化卡list->cards_array[list->cards_size] = card;//将卡存入卡库printf("注册成功!\n");localrecord(&list->cards_array[list->cards_size],"开户",money);//(1)strcpy(list->cards_array[list->cards_size].create_card_date, list->cards_array[list->cards_size].records[0].op_now);//(2)list->cards_size++;//卡库中卡的有效个数加1return 1;
}

代码(1)是卡的流水记录函数,后面会介绍。

代码(2):此时list->cards_size还未+1,就是这张卡在卡库数组中的顺序下标,list->cards_array[list->cards_size]就是当前刚注册的卡,list->cards_array[list->cards_size].create_card_date就是这张卡的开户时间,代码(1)将这张卡的第一条流水记录,也就是开户,记录了下来,包括开户的时间,list->cards_array[list->cards_size].records[0].op_now就是第0条记录开户的时间(是字符串格式)。所以使用strcpy()函数将时间字符串拷贝给卡的开户时间。

8.流水记录函数

void localrecord(BankCard* card, const char* type, int money) {time_t t;//(1)time(&t);//(2)struct tm* p = localtime(&t);//(3)char buff[20];//声明一个字符数组,长度能放下 年-月-日 时:分:秒 的字符串sprintf(buff, "%d-%d-%d %d:%d:%d", p->tm_year + 1900, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);//(4)strcpy(card->records[card->record_size].op_now, buff);  //将时间字符串拷贝给流水记录的时间strcpy(card->records[card->record_size].op_type, type); //把操作类型拷贝给流水记录的类型card->records[card->record_size].money = money;//把涉及钱数赋值给流水记录card->record_size++;//记录的数量加1
}

代码(1):time_t是被重命名的long long型数据类型。time_t t;等价于long long t;

代码(2):time()是获取从1900年1月1日0点0分到现在的秒数的函数,赋值给t。

代码(3):struct tm是一个专门保存时间的各个部分的结构体类型。通过localtime()函数将获取的t转化为本地时间,保存到指针p中。

代码(4):sprintf()是一个将格式化的数据写入到指定字符串的函数。buff是我们指定用来保存时间格式的字符串,"%d-%d-%d %d:%d:%d"是我们指定的格式, (p->tm_year + 1900, p->tm_mon + 1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec)是我们指定的数据;p->tm_year + 1900是因为获取的时间是从1900到现在的时间差值计算的年份,所以要加上1900;p->tm_mon + 1是因为struct tm 指定保存的月份是从0到11的,所以要加一。

9.登录函数

int login_card(PList plist, int id, int passwd) {int index = SearchCard(plist, id);//先找卡,看卡库里有没有if (index==-1) {//没有printf("请先注册\n");return -1;}//卡库里有这张卡就可以登录,但还要看卡有没有被锁或挂失。if (plist->cards_array[index].islocked || plist->cards_array[index].isloss) {printf("卡已被锁或挂失,请找管理员进行激活\n");return -1;}//密码正确,登录成功if (plist->cards_array[index].passwd == passwd) {printf("登录成功\n");plist->cards_array[index].count = 0;//密码输错次数清零return index;//返回卡在卡库(数组)的下标}else {printf("登录失败\n");plist->cards_array[index].count++;//密码输出次数+1if (plist->cards_array[index].count == FAILTIME) {plist->cards_array[index].islocked = 1;//次数达到3次,锁卡printf("卡已被锁,请找管理员进行激活\n");}return -1;}
}

10.挂失卡函数

void lost(PList plist, const char* idcard) {for (int i = 0; i < plist->cards_size; i++) {
/*1*/    if (strcmp(plist->cards_array[i].user.idcard, idcard) == 0) {if (plist->cards_array[i].isloss == 1) {printf("已挂失过,请勿多次挂失\n");}else {plist->cards_array[i].isloss = 1;printf("挂失成功\n");}
/*2*/       return;}}printf("不存在该用户,挂失失败\n");
}

代码/*1*/:将指向卡库的指针与身份证号传入函数,通过对卡库的遍历,strcmp()比较每张卡的卡主的身份证号与传入的idcard,如果有idcard相同,就是找到了要挂失的卡。如果没挂失过,就挂失,使isloss=1。

代码/*2*/:找到对应用户的卡就 return空;退出函数,不执行最后的printf。

11.激活卡函数

void active(PList plist, const char* idcard) {for (int i = 0; i < plist->cards_size; i++) {if (strcmp(plist->cards_array[i].user.idcard, idcard) == 0) {
/*1*/       if (plist->cards_array[i].isloss == 0 && plist->cards_array[i].islocked == 0) {printf("该卡状态正常,请勿多次激活\n");}else {plist->cards_array[i].isloss = 0;plist->cards_array[i].islocked = 0;printf("激活成功\n");}return;}}printf("不存在该用户,激活失败\n");
}

基本与挂失函数差不多,除了代码/*1*/。

代码/*1*/:如果isloss==0并且islocked==0就意味着卡并没有挂失,也没有被锁,状态正常着,不需要激活。

12.存款函数

void savemoney(BankCard* card, int money) {if (money <= 0) {printf("请勿错误操作\n"); return ;}card->money += money;printf("存款成功\n");localrecord(card, "存款", money);//(1)
}

将指向卡的指针和要存的钱数传入函数。money<=0时退出。否则,卡的钱数加上money的钱数。

代码(1):localrecord()函数记录存款这条流水记录,记录的时间函数自动获取,操作类型为存款,涉及金额为money。

13.取款函数

bool withdrawmoney(BankCard* card, int money) {if (money <= 0) {printf("请勿错误操作\n"); return 0;}if (money > card->money) {printf("余额不足,取款失败\n");return 0;}else {card->money -= money;printf("取款成功\n");localrecord(card, "取款", -money);//涉及钱数为负return 1;}
}

与存款函数相同。

14.转账函数

bool transfer(BankCard* owncard, BankCard* anothercard, int money) {if (money <= 0) {printf("请勿错误操作\n"); return 0;}if (money > owncard->money) {printf("余额不足,转账失败\n");return 0;}else {owncard->money -= money;localrecord(owncard, "转出", -money);anothercard->money += money;localrecord(anothercard, "转入", money);printf("转账成功\n");return 1;}
}

转账函数需要传入两张卡:自己的和要转账到的其他卡。转账成功同时对两张卡进行流水记录。

15.查余额函数

void getmoney(BankCard* card) {printf("账户余额为:%d\n",card->money);
}

将卡的指针传入,直接打印余额。

16.查看流水信息函数

void getwatermessage(BankCard* card) {printf("\t记录\t\t时间\t\t类型\t涉及金额\n" );int count = 0;//打印的记录条数for (int i = 0; i < card->record_size; i++) {count++;printf("\t%d\t%s\t%s\t%d\n",count, card->records[i].op_now, card->records[i].op_type, card->records[i].money);}
}

将指向卡的指针传入,以卡的流水记录条数为停止条件,以 记录条数  时间  类型  涉及金额 的格式,从第一条开始,循环打印所有的流水记录。

17.查看开户时间函数

void get_create_card_date(BankCard* card) {printf("开户时间为:%s\n",card->create_card_date);//直接打印
}

18.销毁一张卡所有流水记录的函数

void destoryCardRecord(BankCard* card) {free(card->records);//释放动态开辟的空间card->records = NULL;//防止野指针产生
}

因为卡的流水记录空间是由动态内存开辟的,所以需要咱自己释放掉。

19.注销卡函数

void destoryCard(PList plist, int index) {destoryCardRecord(&plist->cards_array[index]);for (int i = index; i < plist->cards_size; i++) {plist->cards_array[i] = plist->cards_array[i + 1];}plist->cards_size--;
}

先将流水记录销毁(动态内存),然后把后面的卡依次往前挪一位就行了。(因为这不是链表,链表可以直接删结点,然后把后面的卡续上就行。)(偷懒的话,直接把最后一张卡挪过来把这张卡覆盖掉就行(不需要按顺序的话))。

20.销毁卡库函数

void destory(PList plist) {for (int i = 0; i < plist->cards_size; i++) {free(plist->cards_array[i].records);plist->cards_array[i].records = NULL;}free(plist->cards_array);plist->cards_array = NULL;//防止野指针
}

因为使用了动态内存嘛,其实不这样也行,目前我们自己的程序运行结束,所开辟的动态内存空间系统就给释放掉了。但是如果是在服务器上就不行了,程序是一直在运行的,如果我们不用了就要主动释放内存。不影响其他程序。

所以为了仪式的完整,我们加上,先将所有卡的流水记录进行销毁(因为使用动态内存保存的),再释放卡库指针指向的(数组)的内存(动态内存开辟的)。再使保存卡的指针指向NULL。

在编写完上述两个文件后,就可以在测试文件进行简单的逻辑实现,进行函数功能的检测、调试了。

2.3头文件masager.h的编写

为了缩短单个文件的代码长度,所以我们在一个新文件进行管理员的实现。

2.3.1结构体类型设计

1.管理员结构体类型设计

typedef struct Masager {char u_name[10];//姓名char idcard[20];//身份证号char phone[12];//电话int id;//账号int passwd;//密码
}MUser;

用户登录是通过卡号登录的,所以将用户信息保存在卡里;管理员是通过自己的账号登录的,然后为用户提供服务,所以将卡号,密码保存在管理员变量自身。

2.管理员名单结构体类型设计

typedef struct Sqlist_Masager {MUser *muser;//管理员类型指针int size;//管理员有效个数int length;//管理员名单容量
}sqlist_masager,*MPList;

使用管理员结构体类型指针,为其动态开辟内存,保存管理员变量。sqlist_masager是重命名后的struct Sqlist_Masager类型,MPList是重命名后的struct Sqlist_Masager指针类型。详情见C语言结构体。

2.3.2相关功能函数声明

相关功能函数有8个。

//初始化管理员名单
void init_Sqlist_masager(MPList mlist);
//初始化管理员
void init_masager(MUser* muser, const char* m_name, const char* m_idcard, const char* m_phone, int id, int passwd);//查找管理员是否存在
int SearchM(MPList mlist, int id);
bool IsFull(MPList mlist);//管理员名单判满
bool GrowMNum(MPList mlist);//管理员名单扩容
//注册管理员
bool register_masager(MPList mlist,MUser* muser);
//管理员登录
int login_m(MPList mlist, int id, int passwd);//销毁管理员名单
void DestoryM(MPList mlist);

2.4masager.h管理员相关函数实现

  1.初始化管理员的函数

void init_masager(MUser* muser, const char* m_name, const char* m_idcard, const char* m_phone,int id,int passwd) {strcpy(muser->idcard, m_idcard);strcpy(muser->phone, m_phone);strcpy(muser->u_name, m_name);muser->id = id;muser->passwd = passwd;
}

管理员变量在函数外声明好以后, 将指向管理员变量的指针和相关管理员信息传入函数,管理员信息在函数外由键盘输入。然后·根据数据类型进行合适地赋值。字符串用字符串函数,整型直接赋值。

2.初始化管理员名单的函数

void init_Sqlist_masager(MPList mlist) {mlist->muser = (MUser*)malloc(sizeof(MUser) * MASAGERNUM);assert(mlist->muser != NULL);mlist->size = 0;mlist->length = MASAGERNUM;
}

管理员名单变量在函数外声明好以后,将指向管理员名单的指针传入函数。MASAGERNUM宏定义为5。动态开辟好能放下五个管理员变量的内存空间,用muser指针指向。然后断言开辟成功,否则会退出程序。管理员有效个数初始化为0,名单容量为5。

3.管理员名单判满的函数

bool IsFull(MPList mlist) {assert(mlist != NULL);//名单未初始化时不可以判断(内部属性未初始化赋值)return mlist->length == mlist->size;
}

当有效个数等于名单长度时,名单容量饱和。

4.管理员名单扩容函数

bool GrowMNum(MPList mlist) {int len = mlist->length + MASAGERNUM;MUser* p = (MUser*)realloc(mlist, sizeof(MUser) * len);if (p != NULL) {mlist->muser = p;p = NULL;mlist->length = len;return 1;}else return 0;
}

每次扩容,容量增加5个。其余代码在5.卡库扩容函数已说明。

5.管理员注册函数

bool register_masager(MPList mlist, MUser* muser) {if (IsFull(mlist)) {if (!GrowMNum(mlist)) {printf("管理员人数达到上限\n");return 0;}}mlist->muser = muser;mlist->size++;printf("注册成功\n");return 1;
}

代码与7.注册卡函数 相同。

6.查找管理员函数

int SearchM(MPList mlist,int id) {for (int i = 0; i < mlist->size; i++) {if (id == mlist->muser[i].id)return i;}return -1;
}

以id查找,size为管理员的有效个数。从头到尾遍历名单,找到后返回其对应位置下标。

7.管理员登录函数

int login_m(MPList mlist, int id, int passwd) {int a = SearchM(mlist, id);if (a == -1) {printf("请先注册\n");return -1;}if (mlist->muser[a].passwd == passwd) {printf("登录成功\n");return a;}printf("登录失败,请重试\n");return -1;
}

先以id查找管理员是否存在。不存在就先注册;存在的话,就比对其密码是否正确。

 8.销毁管理员名单函数

void DestoryM(MPList mlist) {free(mlist->muser);mlist->muser = NULL;
}

管理员名单中只有保存管理员变量的内存是动态开辟的,所以需要我们自己释放。与20.销毁卡库函数相同。

2.5 start.cpp逻辑功能实现文件编写

一)先调用自制的两个头文件,因为要使用其中的自定义的结构体数据类型,还有所需的函数的声明。start()函数含有两个参数:指向卡库的指针和指向管理员名单的指针(两变量在调用start函数前已声明好)。

#include"Bankcard.h"
#include"masager.h"
#include<stdio.h>//要用到输入输出
#include<string.h>//要用到字符串函数
int start(PList cards,MPList masagers) {
}

二)start()函数中

1.要实现每次选完身份进行操作后,循环打印选择身份功能,所以采取whlie循环语句。

while (true) {int choice0 = 0;printf("请选择身份:1.用户  2.管理员 3.退出\n");scanf("%d", &choice0);//下述代码填充位置//代码2}

2. 使用switch分支语句对选项进行分类处理

switch (choice0) {case 1: {int id = 0, passwd = 0;while (true) {printf("(账号、密码同时输入0退出)\n");//避免忘记卡号、密码,卡死在这里printf("请输入卡号:");scanf("%d", &id);printf("请输入密码:");scanf("%d", &passwd);if (id == 0 && passwd == 0) break;int a = login_card(cards, id, passwd);//下述代码填充位置//代码3}break;}case 2: {while (true) {int choice1 = 3;printf("请输入选项数字:1.管理员登录  2.管理员注册  3.退出\n");scanf("%d", &choice1);//下述代码填充位置//代码4if (choice1 == 3) break;}break;}case 3:printf("退出成功\n"); return 0;default:printf("请正确输入\n");//异常处理
}

选择1,进行用户通过卡号、密码登录;选择2,进行管理员身份登录或注册,用户注册卡需通过管理员;选择3,退出程序。输入错误,提示重新选择,while语句循环。

3.如果用户登录成功,就执行用户选择对卡的操作:存款、取款、转账等。使用switch分支语句对选项进行接收,调用各功能函数,进行功能逻辑实现。

if (a != -1) {//a!=-1就是登录成功,a为卡在卡库的下标位置while (true) {printf("请选择数字:1.存款  2.取款  3.转账  4.查余额  5.查看流水信息  6.查看开户日期  7.退出\n");int choice2 = 0;scanf("%d", &choice2);switch (choice2) {case 1: {int money2 = 0;printf("请输入存款金额:");scanf("%d", &money2);savemoney(&cards->cards_array[a], money2); break;}case 2: {int money2 = 0;printf("请输入取款金额:"); scanf("%d", &money2);withdrawmoney(&cards->cards_array[a], money2); break;}case 3: {int money2 = 0, id = 0, a2 = 0;while (true) {printf("请输入转账目标账户账号(请多次检查以免转错):\n(输入0退出)\n");scanf("%d", &id);if (id == 0) break;//避免卡死if (id == cards->cards_array[a].id) {printf("不可自己给自己转账\n");}a2 = SearchCard(cards, id);if (a2 == -1) {printf("账号不存在,请重试\n");}else break;}if (id == 0 || id == cards->cards_array[a].id) break;printf("请输入转账金额:"); scanf("%d", &money2);transfer(&cards->cards_array[a], &cards->cards_array[a2], money2);break;}case 4:getmoney(&cards->cards_array[a]); break;case 5:getwatermessage(&cards->cards_array[a]); break;case 6:get_create_card_date(&cards->cards_array[a]);case 7:break;default:printf("请正确输入\n");//异常处理}if (choice2 == 7)break;}break;
}
else break;

4.如果选择管理员身份,就进行登录、注册或退出的选择。选择1,也就是choice1==1,进行登录;选择2,进行注册;3,退出。

switch (choice1) {case 1: {int id = 0, passwd = 0;while (true) {printf("(账号、密码同时输入0退出)\n");printf("请输入账号:"); scanf("%d", &id);printf("请输入密码:"); scanf("%d", &passwd);if (id == 0 && passwd == 0)break;int c = login_m(masagers, id, passwd);//下述代码填充位置//代码5     }case 2: {MUser muser;int id = 0, passwd = 0;char name[10], idcard[20], phone[12];printf("请依次输入姓名、身份证号、电话\n");scanf("%s %s %s", name, idcard, phone);printf("输入账号:"); scanf("%d", &id);printf("输入密码:"); scanf("%d", &passwd);init_masager(&muser, name, idcard, phone, id, passwd);register_masager(masagers, &muser);break;}case 3:break;default:printf("请正确输入\n");
}

5.如果管理员登录成功,就进行对卡的操作的选项选择。用switch语句接收选项,调用各种功能函数,做功能逻辑实现。

if (c != -1) {while (true) {int choice2 = 5;printf("请输入选项数字:1.注册卡  2.注销卡  3.挂失  4.激活  5.退出\n");scanf("%d", &choice2);switch (choice2) {case 1: {User user;char u_name[10]; char idcard[20]; char phone[12];printf("请依次输入卡主的姓名、身份证号、手机号\n");scanf("%s %s %s", u_name, idcard, phone);init_user(&user, u_name, idcard, phone);int id = 0, passwd = 0, money = 0;printf("请依次输入卡号、密码、开户金额\n");scanf("%d %d %d", &id, &passwd, &money);register_card(cards, id, passwd, money, &user);break;}case 2: {int id = 0; char name[10], idcard[20], phone[12];while (true) {printf("请输入所要注销的卡号(输入0退出)\n");scanf("%d", &id);if (id == 0)break;int b = SearchCard(cards, id);if (b != -1) {while (true) {printf("请输入卡主信息(全输入0退出)\n");printf("姓名:");scanf("%s", name);printf("身份证号:");scanf("%s", idcard);printf("电话号:");scanf("%s", phone);if (*name == '0' && *idcard == '0' && *phone == '0')break;if (strcmp(name, cards->cards_array[b].user.u_name) == 0 &&strcmp(idcard, cards->cards_array[b].user.idcard) == 0 &&strcmp(phone, cards->cards_array[b].user.phone) == 0) {destoryCard(cards, b);printf("注销成功\n"); break;}else printf("信息错误,请重新输入\n");}}else printf("卡号不存在,请重试\n");}break;}case 3: {char idcard[20];printf("请正确输入用户身份证号:"); scanf("%s", &idcard);lost(cards, idcard); break;}case 4: {char idcard[20];printf("请正确输入用户身份证号:"); scanf("%s", &idcard);active(cards, idcard); break;}case 5:break;default:printf("请正确输入\n");}if (choice2 == 5)break;}break;
}
else break;

将上述代码组合起来就是整个系统功能的逻辑实现。

2.6 main.cpp测试文件

在main.cpp文件中声明卡库和管理员名单变量,并进行各自的初始化。再取地址作指针传参进start()函数,start()函数实现系统功能逻辑。最终,释放掉各自开辟的内存。

#include"Bankcard.h"
#include"masager.h"
#include<stdio.h>
#include<string.h>
//声明start()函数,说明项目中有这个函数,让系统去找
int start(PList cards, MPList masagers);int main() {sqlist_bankcards cards;init_sqlist_cards(&cards);sqlist_masager masagers;init_Sqlist_masager(&masagers);start(&cards, &masagers);destory(&cards);DestoryM(&masagers);
}

说明:include""与include<>的区别。

include" "是在程序当前目录下先找指定的文件,再在系统指定的路径去搜索;include<>是直接在系统指定的路径处搜素。比如BankCard.h头文件是我们自己编写的,是在程序所在的文件目录中的,所以要使用include" ",不可以使用include<>,但stdio.h头文件是系统自带的,既可以使用include<>,也可以使用include" "(只不过会多做些无用功)。

3.总结

通过此次项目实现(因为项目太小,还是说实现吧,谦逊一点),主要将以下几部分的知识进行了由知到行的实践:

1、分文件开发,include""与include<>的作用与区别。

2、结构体数据类型的设计,结构体的嵌套,结构体与动态内存的结合。

3、函数相关知识,函数形参是如何传递实参的。

4、动态内存开辟的相关知识。

.        其实,我们只是实现了一个十分简单的自助取款机系统,有些功能逻辑设计的不好。比如在挂失卡和激活卡时,只以用户的身份证号为依据,进行单张卡的挂失与激活,但如果一个用户可以注册多张卡呢。所以,我们考虑将卡号也作为依据传入函数进行更为准确地操作。还有卡密码的找回功能,管理员的注销功能等等,可以自由发挥。

//其实,写代码的时候感觉还好,严格来说是比较简单,但是写成博客就很折磨,所以可能描述地不好,有地方可能有错,不严谨。

//虽然大部分是代码。

C语言项目-ATM自助取款机(无图形化界面)相关推荐

  1. CentOS 7 腾讯云服务器 Linux无图形化界面静默安装oracle数据库.

    服务器基础信息 腾讯云 CentOS7.2 64位 root登录系统查看一下版本信息 CentOS Linux release 7.2.1511 (Core) 在安全组中将ORACLE默认通讯端口 T ...

  2. Linux安装软件无图形化界面,无图形界面安装 Development Tools(centos版)

    相信很多linux老手都知道在安装linux系统的时候需要把Development Tools包选上,以方便将来编译安装自己所需要的软件,又因为Development Tools包在安装linux时并 ...

  3. linux 图形化界面

    系统版本:CentOS-7-x86_64 安装后如无图形化界面,则通过命令安装图形界面. 在命令行中输入yum groupinstall "GNOME Desktop" 后回车(注 ...

  4. 模拟atm取款机 php,C语言模拟ATM自动取款机系统

    C语言实验报告 题目名称:C语言模拟ATM自动取款机系统 一:问题描述: C语言模拟实现ATM自动取款机功能:输入密码,余额查询,取款,存款,转账,修改密码,退出功能: 代码实现的功能: 账号及密码输 ...

  5. ATM自助取款机与CDM自助存款机工作原理

    ATM自助取款机与CDM自助存款机1988年引入我国,给我国传统存取款金融业务带来革命性的变革,ATM自动取款机可以24小时提供优质服务,方便了客户取款,减轻了银行柜台工作压力;CDM自动存款机,则可 ...

  6. 图形化界面扫雷(C语言+easyx实现,多图教学)

    扫雷 前言 准备工作 EasyX的下载 一些准备知识 头文件的引用 图形化界面的创建 图形化界面简介 图片加载与放置图片 鼠标操作 提示框 其它的准备知识 思路分析 代码实现 准备工作 初始化游戏的函 ...

  7. 基于图形化界面创建Vue项目

    记录:267 场景:基于图形化界面创建Vue项目,便于后续查阅. 环境:Node.js v14.17.3 1.打开cmd控制台 运行命令:vue ui 截图: 2.项目仪表盘 截图: 3.Vue 项目 ...

  8. c语言编程游戏界面,震惊!!!一个关于c语言图形化界面编程的小游戏-Go语言中文社区...

    关于C语言的图形化界面编程 第一个小程序<飞翔的小鸟> 效果图 本人也是小白,大家轻点喷!!!! 下面是源码 作者: @追风 #include #include #include #inc ...

  9. [vue build Error] 在vue的图形化界面对项目进行打包时出现“Callback was already called”错误

    错误详情: 运行没有问题: build出现错误: 输出的错误信息: 解决方法: 1.删除项目下的"node_modules"目录: 2.在本文件目录下打开cmd窗口,强制清除缓存 ...

最新文章

  1. 还是畅通工程(1233 并查集+kruskal)
  2. web前端——让人头疼的多列复选框排列解决办法
  3. 【Linux】一步一步学Linux——usermod命令(86)
  4. 移动客户端与服务器端安全通信方案
  5. linux培训课程第六天:ppt以及笔记
  6. 宏锦软件2015年的计划
  7. 核心控制芯片选型建议书(中低端)
  8. oracle 结构化语言查询 DML DDL DCL
  9. 人脸方向学习(六):Face Recognition-Center Loss 解读
  10. 【信号处理】脉搏信号处理系统含Matlab源码
  11. 多目标优化算法:多目标樽海鞘算法MOSSA(提供MATLAB源码)
  12. 关于MSN群 创建MSN群 使用MSN群
  13. mp4格式的视频流传输下实现边下边播
  14. 机器视觉软件能够做什么?-龙熙视觉机器视觉培训李杰
  15. PHP显示了验证码但不能登陆,thinkphp5 登陆后台验证码无法显示
  16. 远程访问计算机硬盘,60秒远程访问路由器硬盘!瞬间变NAS!
  17. 【非原创】Ubuntu14.04+cuda6.5+opencv2.4.9+caffe配置记录
  18. QT QWebEngineView+UEditor富文本编辑器
  19. 英语话题 topic 7:cooking
  20. Ebook管理工具(持续更新)

热门文章

  1. 2021.02.04——用爬虫爬取nga ow区1-10页的帖子并生成词云图
  2. sshpass离线安装+应用场景
  3. iPhone历史上的创新,你都知道吗?
  4. linux top 指定pid,51CTO博客-专业IT技术博客创作平台-技术成就梦想
  5. 未雨绸缪-变化是永恒的
  6. Le vent se lève, il faut tenter de vivre
  7. 干货 | 机器人中的“百达翡丽” — 五指精密仿生灵巧手技术浅析
  8. 【vue warning】avoid mutating a prop directly
  9. 如何从Ubuntu软件存储库外部安装软件
  10. 踩坑记录:消息推送已读未读