【C语言】网吧管理系统-链表项目设计(二)

简介: C语言 & 网吧管理系统

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 测试


测试结果正常~


目录
相关文章
|
19天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别
pymalloc 和系统的 malloc 有什么区别
|
12天前
|
C语言 Windows
C语言课设项目之2048游戏源码
C语言课设项目之2048游戏源码,可作为课程设计项目参考,代码有详细的注释,另外编译可运行文件也已经打包,windows电脑双击即可运行效果
25 1
|
15天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别?
pymalloc 和系统的 malloc 有什么区别?
|
21天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
48 4
|
1月前
|
存储 缓存 C语言
C语言:链表和数组有什么区别
C语言中,链表和数组是两种常用的数据结构。数组是一种线性结构,元素在内存中连续存储,通过下标访问,适合随机访问且大小固定的情况。链表由一系列不连续的节点组成,每个节点存储数据和指向下一个节点的指针,适用于频繁插入和删除操作的场景,链表的大小可以动态变化。
|
1月前
|
C语言
无头链表再封装方式实现 (C语言描述)
如何在C语言中实现无头链表的再封装,包括创建节点和链表、插入和删除操作、查找和打印链表以及销毁链表的函数。
27 0
|
1月前
|
C语言
C语言链式结构之有头单链表再封装写法
本文介绍了如何使用C语言对有头单链表进行封装,包括节点的创建、链表的初始化、数据的插入和删除,以及链表的打印等功能。
17 1
|
1月前
|
C语言
C语言结构体链式结构之有头单链表
文章提供了一个C语言实现的有头单链表的完整代码,包括创建链表、插入、删除和打印等基本操作。
23 1
|
20天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
39 0
|
1月前
|
存储 编译器 C语言
【C语言】学生管理系统:完整模拟与实现(一)
【C语言】学生管理系统:完整模拟与实现