2. 计费标准~
也很好理解,有了计费标准,才能有一张卡嘛~
我的思路就是:分为年月日时四种卡,这样就固定了这个计费标准就一直都是四个
时间较长的卡不应该太早下机~即使当天不玩,也可以不下机
新源文件ChargeStandard.c 源码在这里:网吧管理系统/ChargeStandard.c · 游离态
2.1 计费标准结构体
Standard(basis.h)
//计费标准
typedef struct Standard {
int type;//1 2 3 4
int state;//决定此卡是否能办
double price;//标准单价
}Standard;
type => 卡的类型~
state=> 是否有此标准~
price=>标准单价~
规则
有这个标准就不能新增,没有这个标准就不能删除和更改
并且没有这个标准,就没有对应的卡,对应的卡被建立,此标准被删除,则必须补充标准才能正常下机!
修改此标准,该卡将以最终标准进行计费~
2.2 主体函数设计
chargeStandard(ChargeStandard.c ) void chargeStandard(Manager* pm) { int input = 0; Standard* pcs = standardCarry(); void (*func[5])(Standard*) = { exitOutStandard, addStandard, search, delStandard, modify}; do { menu2_1();//菜单~ scanf("%d", &input); if (input <= 4 && input >= 0) { func[input](pcs); } else { printf("请重新输入\n"); } } while (input); }
2.2.1 函数指针数组~
这里由于选项较多,我选择用函数指针数组,用法与刚才一样,要注意下标与菜单与对应选项要相符合哦~
2.2.2 文件读取函数文件-Carry.c
我将所有的结构体(信息)文件读取操作,都放在一个源文件中,方便管理~
源码在这里:网吧管理系统/Carry.c · 游离态
这里的 Standard* pcs = standardCarry();的含义就是从文件中读取~
并且返回值为对应的首元素堆区地址
信息放在rate.txt二进制文件中~
Standard* standardCarry() { FILE* pf = fopen("data\\rate.txt", "rb"); Standard* pcs = (Standard*)malloc(4 * sizeof(Standard)); if (pf == NULL) { pf = fopen("data\\rate.txt", "wb"); fwrite(pcs, sizeof(Standard), 4, pf); fclose(pf); } else { fread(pcs, sizeof(Standard), 4, pf); fclose(pf); } return pcs; }
标准的个数一直都是4个,顺序一定1 2 3 4,只不过state可能有所不同,所以我用的是顺序表
打开文件失败则必然是不存在此文件,则应该进行判断~
2.3 菜单(Menu.c)
增删查改操作菜单:
void menu2_1() { printf("******************\n"); printf("0. 退出\n"); printf("1. 新增计费标准\n"); printf("2. 查询计费标准\n"); printf("3. 删除计费标准\n"); printf("4. 修改计费标准\n"); printf("******************\n"); }
卡的类型清单:
void menu2_2() { printf("******************\n"); printf("1. 年卡\n"); printf("2. 月卡\n"); printf("3. 日卡\n"); printf("4. 时卡\n"); printf("******************\n"); }
2.4 新增计费标准
addStandard(ChargeStandard.c)
选择卡的类型~
不存在此标准则可增加
输入标准信息~
pcs[input - 1].state = 1;
下标访问其实就是解引用操作~
arr[i] == *(arr + i)
所以下标为负在C语言里也不会报错~
state置为1—>已有标准~
void addStandard(Standard* pcs) { int input = 0; menu2_2(); printf("请输入待增加的计费标准的类型:>"); scanf("%d", &input); if (input <= 4 && input >= 1 && pcs[input - 1].state != 1) { printf("请输入你的计费标准单价是多少元:>"); scanf("%lf", &pcs[input - 1].price); pcs[input - 1].state = 1; printf("新增成功\n"); } else { printf("此标准无法加入,“可能”是已有此标准,但可进行修改操作\n"); } }
2.5 查询计费标准
search(ChargeStandard.c)
选择卡的类型~
打印卡的信息~
无此卡则报无~
void search(Standard* pcs) { int input = 0; menu2_2(); printf("请输入待查看的计费标准的类型:>"); scanf("%d", &input); if (input <= 4 && input >= 1 && pcs[input - 1].state == 1) { printf("计费标准为:"); change(input); printf("=》单位时间内收费%.2lf元\n", pcs[input - 1].price); } else { printf("暂无此标准\n"); } }
2.6 删除计费标准
delStandard(ChargeStandard.c)
选择卡的类型
存在此标准,就删除
pcs[input - 1].state = 0;
state 置为0 —> 无标准
不存在就报失败~
void delStandard(Standard* pcs) { int input = 0; menu2_2(); printf("请输入待删除计费标准的类型:>"); scanf("%d", &input); if (input <= 4 && input >= 1 && pcs[input - 1].state == 1) { pcs[input - 1].state = 0; printf("删除成功\n"); } else { printf("删除失败\n"); } }
2.7 修改计费标准
modify(ChargeStandard.c)
选择卡的类型
存在此标准,输入对应信息进行修改~
不存在则报无~
void modify(Standard* pcs) { int input = 0; menu2_2(); printf("请输入待修改计费标准的类型:>"); scanf("%d", &input); if (input <= 4 && input >= 1 && pcs[input - 1].state == 1) { printf("请输入你的调整后的计费标准单价是多少元:>"); scanf("%lf", &pcs[input - 1].price); printf("修改成功\n"); } else { printf("暂无此标准\n"); } }
2.8 退出操作
exitOutStandard(Exit.c) 即func[0] void exitOutStandard(Standard* pcs) { FILE* pf = fopen("data\\rate.txt", "wb"); fwrite(pcs, sizeof(Standard), 4, pf); free(pcs); fclose(pf); }
释放空间,更新信息~
2.9 测试
下面的测试案例,都是事先被我插入数据的~
运行正常~
二进制文件显示正常~
1. 卡管理~
有了计费标准之后,我们基本可以开始构建卡了~
引入新的源文件:CardManage.c
源码在这里:网吧管理系统/CardManage.c · 游离态
1.1 卡这个整体对应的结构体~
Card(basis.h) //卡 typedef struct Card { int id;//卡号 char password[7];//六位密码 double balance;//开卡金额-->余额,卡的种类决定了这个金额 int effect; // 1-->未注销,非1-->已注销 int cardType;//卡的种类---计费方案 long long upTime;//上机间点 int state;//上机与否? struct Card* next;//后继 }Card;
卡号
密码
余额
效应—注销与否
卡的类型—计费标准
上机的时间的时间戳~(64位长整形)
状态—上机了与否
后继节点—构造链表
1.2 主体函数设计
cardManage(CardManage.c)
引入新的全局变量:cardNumber(CardManager.c)卡数~
void cardManage(Manager* pm) { int input = 0; Card* pc = cardCarry(); void (*func[4])(Card*) = { exitOutCard, addCard, searchCard, logOffCard }; do { menu1_1(); scanf("%d", &input); if (input >= 0 && input <= 3) { func[input](pc); } else { printf("输入失败\n"); } } while (input); }
1.2.1 函数指针数组
同样的,选项大于大于3个我就认为有点多了
1.2.2 文件读取函数~
cardCarry(Carry.c)
Card* cardCarry() { FILE* pf = fopen("data\\card.txt", "rb"); Card* pc = (Card*)malloc(sizeof(Card));//堆区空间,不会被收回 if (pf == NULL) { pf = fopen("data\\card.txt", "wb"); fwrite(&cardNumber, sizeof(int), 1, pf); fclose(pf); } else { fread(&cardNumber, sizeof(int), 1, pf); fread(pc, sizeof(Card), 1, pf); Card* cur = pc; for (int i = 1; i < cardNumber; i++) { Card* newOne = (Card*)malloc(sizeof(Card)); fread(newOne, sizeof(Card), 1, pf); newOne->next = NULL; cur->next = newOne; cur = cur->next; } } return pc; }
文件指针为NULL代表打不开文件,说明文件不存在
则我只需要建立一个,并且将整数0导入文件中~
我的一个习惯:我会讲元素个数放在文件的首位~
刚才的计费标准没有是因为一直都是4个~
文件打开了,进行标准的读取操作
首先拿到卡数~
然后获得第一张卡~
后续以尾插的形式进行插入~
重点:要将尾节点的后驱置为NULL,否则会导致野指针异常/死循环~
※注意:如果文件中没有东西,这pc指针中堆区空间仍然是原始的~后续应该通过卡的个数对此情况进行处理!!!
1.3 菜单(Menu.c)
添查销操作菜单
void menu1_1() { printf("******************\n"); printf("1. 添加卡\n"); printf("2. 查询卡\n"); printf("3. 注销卡\n"); printf("0. 退出\n"); printf("******************\n"); }
查询操作选项菜单
void menu1_2() { printf("******************\n"); printf("1. 打印全部卡\n"); printf("2. 查询具体卡\n"); printf("0. 退出\n"); printf("******************\n"); }
选择卡的类型罗列清单
void menu1_3(Standard* pcs) { printf("************************************\n"); for (int i = 1; i <= 4; i++) { printf("%d. ", i); change(i); if (pcs[i - 1].state == 1) { printf("==》单位时间内收费%.2lf元\n", pcs[i - 1].price); } else { printf("==》(计费标准)暂未被开发\n"); } } printf("0. 退出\n"); printf("************************************\n"); }
注销提示菜单
void menu1_4() { printf("******************\n"); printf("1. 注销卡\n"); printf("0. 退出\n"); printf("※ 特别注意:注销后无法恢复!\n※ 必须由管理员重新办卡\n※ 管理员请据情况进行余额转移\n"); printf("******************\n"); }
1.4 添加卡
addCard(CardManage.c)
导入计费标准~
通过菜单选择卡的类型先~(按0就退出)
非0则进入必须添加一张卡才能退出~
newOne–新节点
输入信息~
规则
卡的类型有如下可选(开卡金额不得少于卡的类型对应的单价!)
卡不存在不能建~
记得释放!!!
卡数 + 1
void addCard(Card* pc) { Standard* pcs = standardCarry(); printf("卡的类型有如下可选(开卡金额不得少于卡的类型对应的单价!)\n"); int input = 0; do { menu1_3(pcs); printf("请输入卡的类型:>"); scanf("%d", &input); if (input != 0) { Card* newOne = (Card*)malloc(sizeof(Card)); printf("请依次输入新卡的卡号,密码,开卡金额\n"); scanf("%d%s%lf", &newOne->id, newOne->password, &newOne->balance); newOne->cardType = input; if (pcs[newOne->cardType - 1].state != 1 || newOne->balance < pcs[newOne->cardType].price) { printf("添加失败,“可能”是因为开卡金额不足或者此类卡未被开发\n"); free(newOne); } else { newOne->effect = 1; newOne->next = NULL; if (cardNumber == 0) { memcpy(pc, newOne, sizeof(Card)); } else { Card* cur = pc; while (cur->next != NULL) { cur = cur->next; } cur->next = newOne; } cardNumber++; printf("添加成功\n"); } } } while (input); printf("退出成功\n"); }
1.4.1 对于卡数为0 的情况
使用库里的内存函数memcpy(内存拷贝函数)(string.h)
直接进行内存拷贝转移~
memcpy(pc, newOne, sizeof(Card));
1.5 查询卡
searchCard(CardManage.c)
根据菜单选择操作
打印全部?
打印专门的一个?
退出~
void searchCard(Card* pc) { int input = 0; do { menu1_2(); scanf("%d", &input); switch (input) { case 1: printAll(pc); break; case 2: printOne(pc); break; case 0: printf("退出成功\n"); break; default: printf("输入失败\n"); break; } } while (input); }
1.5.1 打印一个
printOne(CardManage.c)
输入待查询卡号~
遍历链表去找~
找到了按表格打印出来~
各字段根据对应卡号的节点的具体信息进行打印~
这里的表格我借鉴了MySQL数据库的表格~
占位符的左右对齐==>负号左对齐~
小数的话,左侧数字为最终小数所占的位数~(包括小数点在内)
void printOne(Card* pc) { printf("请输入你要查询的卡号:>"); int id = 0; scanf("%d", &id); Card* cur = pc; while (cardNumber != 0 && cur != NULL) { if (cur->id == id) { printf("+------+------+-------------+-------+---------+---------+\n"); printf("|%-6s|%6s|%13s|%7s|%9s|%9s|\n", "卡号", "密码", "余额", "效应", "卡的种类", "状态"); printf("+------+------+-------------+-------+---------+---------+\n"); printf("|%-6d|%6s|%13.2lf|", cur->id, cur->password, cur->balance); //8.2lf代表这小数,总共站位控制在9(不足的时候补齐,足够的时候不用补齐) if (cur->effect == 1) { printf("%7s| ", "未注销"); } else { printf("%7s| ", "已注销"); } change(cur->cardType); if (cur->state == 1) { printf("|%9s|\n", "上机中"); } else { printf("|%9s|\n", "未上机"); } printf("+------+------+-------------+-------+---------+---------+\n"); printf("查找成功\n"); return; } cur = cur->next; } printf("查找失败,“可能”是因为暂无此卡\n"); }
1.5.1.1 通过卡的类型打印对应的名字
change(ChargeStandard.c) void change(int i) { switch (i) { case 1: printf("年卡"); break; case 2: printf("月卡"); break; case 3 : printf("日卡"); break; case 4 : printf("时卡"); break; default: break; } }
1.5.2 打印全部
这个与刚才类似~
直接从头全部遍历打印~
void printAll(Card* cur) { printf("+------+------+-------------+-------+---------+---------+\n"); printf("|%-6s|%6s|%13s|%7s|%9s|%9s|\n", "卡号", "密码", "余额", "效应", "卡的种类", "状态"); printf("+------+------+-------------+-------+---------+---------+\n"); while (cur != NULL) { printf("|%-6d|%6s|%13.2lf|", cur->id, cur->password, cur->balance); //8.2lf代表这小数,总共站位控制在9(不足的时候补齐,足够的时候不用补齐) if (cur->effect == 1) { printf("%7s| ", "未注销"); } else { printf("%7s| ", "已注销"); } change(cur->cardType); if (cur->state == 1) { printf("|%9s|\n", "上机中"); } else { printf("|%9s|\n", "未上机"); } printf("+------+------+-------------+-------+---------+---------+\n"); cur = cur->next; } printf("查找成功\n"); }
1.6 注销卡~
logOffCard(CardManage.c)
根据菜单选择是否继续注销~
void logOffCard(Card* pc) { int input = 0; do { menu1_4(); scanf("%d", &input); if (input) { logOff(pc); } else { printf("退出成功\n"); } } while (input); }
1.6.1 确认注销
logOff(CardManager.c)
找到对应节点~
找到了就直接将effect置为0(effect在一开始被我们自己置为1的)
如果其处在上机状态,则强制下机
这里卖个关子,后续将上下机的时候再详细说说这个操作~
强制下机后费用是按比例计算的,这个也是后续会讲的~
注意:注销之后无法改回来~必须由管理员重新开卡,查询余额据具体操作 ~
void logOff(Card* pc) { Card* cur = pc; int id = 0; char password[7]; printf("请输入待注销卡的卡号以及对应密码:>"); scanf("%d%s", &id, password); while (cur != NULL && cardNumber != 0) { if (id == cur->id && strcmp(password, cur->password) == 0) { cur->effect = 0; printf("注销成功\n"); if (cur->state == 1) { printf("已强制下机\n"); Consume* psu = consumeCarry(); off(cur, psu); exitOutConsume(psu); } return; } cur = cur->next; } printf("注销失败,“可能”是因为暂无此卡\n"); }
1.7 退出操作
exitOutCard(Exit.c)
数据导入data(必须自己建立)文件的card.txt二进制文件中
如果卡数为0,那么只讲0导入文件即可,因为这个节点不为NULL但是不是有效数据!
不为0遍历链表导入数据,并且用相同的方法释放空间~
void exitOutCard(Card* pc) { FILE* pf = fopen("data\\card.txt", "wb"); fwrite(&cardNumber, sizeof(int), 1, pf); if (cardNumber == 0) { return; } while (pc != NULL) { fwrite(pc, sizeof(Card), 1, pf); Card* tmp = pc; pc = pc->next; free(tmp); } fclose(pf); }
1.8 测试
代码运行正常
二进制文件显示正常~
4. 费用管理~
有了卡这个类之后,进行充值退费操作就比较简单了~
这里是我的计费思想:只有在下机/强制下机结算的钱才是纳入我囊中的钱~
引入新的源文件:ExpenseManage.c
源码在这里:网吧管理系统/ExpenseManage.c · 游离态
4.1 主体函数设计
expenseManage(ExpenseManage.c)
导入卡链表~
根据菜单选择操作~
退出的时候更新释放卡链表~
void expenseManage(Manager* pm) { int input = 0; Card* pc = cardCarry(); do { menu4_1(); scanf("%d", &input); switch (input) { case 1: recharge(pc); break; case 2: refunt(pc); break; case 0: exitOutCard(pc); printf("退出成功\n"); break; default: printf("请重新输入\n"); break; } } while (input); }
4.2 充值
recharge(ExpenseManage.c)
输入卡号
找到对应节点
输入充值金额
信息修改~
补充规则:
注销的号是可以充值的,但是不能继续用必须找管理员重新办卡据情况处理
找不到就报无~
void recharge(Card* pc) { printf("请输入待充值卡的卡号:>"); int id = 0; scanf("%d", &id); while (pc != NULL) { if (pc->id == id) { printf("请输入充值金额:>"); double money = 0.0; scanf("%lf", &money); pc->balance += money; printf("充值成功\n"); printf("\n注意:如果是因为欠费导致的账号注销,注销的卡已作废\n" "此次充值若余额恢复非负,管理员请据情况接办理新卡并且余额保留\n"); return; } pc = pc->next; } printf("充值失败,“可能”暂无此卡\n"); }
4.3 退费
refunt(ExpenseManage.c)
输入卡号
找到对应节点~
将一张未注销的卡进行退费~(负数余额必然是注销了的,但是注销了的卡余额可以为正,但无法退费)
余额清空~无法选择退多少
找不到报无~
void refunt(Card* pc) { printf("请输入待退费卡的卡号:>"); int id = 0; scanf("%d", &id); while (pc != NULL) { if (pc->id == id && pc->effect == 1) {//负值时必然注销 pc->balance = 0.0; printf("退费成功\n"); return; } pc = pc->next; } printf("退费失败,“可能”暂无此卡或者此卡已被注销\n"); }
4.4 退出操作
简单的退出~
更新释放卡链表信息~
4.5 测试
测试结果正常~