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

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

C语言 & 网吧管理系统

本文章是我(东莞理工学院)对于学校期末程序设计项目的总结,可以借鉴,请勿抄袭~

数据结构:顺序表,链表

知识点:C基础语法,C进阶语法,C文件操作

内容简单易懂,难点主要在于项目代码思想思路设计,此系统可能有所缺陷,希望大家能提供建议!我特别需要~

后附上源码,望大家支持~

本文章可以很好的训练C语言语法,也很好的提高工程思维~

总起. 功能以及代码排版要求

重要前提: 你在下面学习的过程将可能会比较困惑,但是很大可能结合其后面所讲的内容,可以解决你的问题! 耐心耐心耐心


多数存储方式应使用链式存储!

这是学校的要求,不过我认为有些可以用到不同的数据结构来存储,例如适合查询的哈希表~

每个功能都有单独的函数—这也是很重要的习惯~

函数命名我采用小驼峰【就是单词与单词之间,用大写字母分割】

对于每个模块,放在不用的源文件,好做区分,而申明均放在一个头文件中

只需要包含这个头文件,就可以用里面的东西啦~

函数调用的话,会跳转到特定的源文件,所以有一些函数不需要申明,加上static也不错~

这里static有点像Java的private~

我这个思想习惯于Java的类

包括对应的链表的节点数,我是用一个全局变量代替

不能用static修饰(包括函数也不行),因为申明了也不能给别的源文件使用~

即使不申明,也不可以重名~

不像Java面向对象,C语言这里还是很难像Java那样方便


序号 模块 功能 说明

1.1  添加卡 输入卡号、密码、开卡金额等卡信息,将卡信息保存到data文件夹的card.txt文件中

1.2 卡管理 查询卡 根据输入的卡号,查询卡号相同的卡信息,并以表格形式显示在控制台中

1.3  注销卡 根据输入的卡号、密码,将对应卡的状态改为注销,注销卡不能进行上机

2.1  新增计费标准 输入计费标准的信息,将输入的计费标准保存到data文件夹的rate.txt文件中

2.2 计费标准管理 查询计费标准 根据上机时间,查询对应的计费标准

2.3  删除计费标准 从计费标准文件data文件夹的rate.txt文件中,删除一条计费标准

2.4  修改计费标准 修改一条计费标准

3.1 计费管理 上机 根据输入的卡号、密码,判断该卡能否上机,如果能上机,则保存计费信息

3.2  下机 根据输入下机卡的卡号,进行下机结算操作

4.1 费用管理 充值 给一条已经存在的卡进行充值。

4.2  退费 将卡中余额返回给用户

5.1  查询消费记录 查询一张卡在一段时间内的消费记录

5.2 查询统计 统计总营业额 统计一段时间内,总营业额

5.3  统计月营业额 统计一年中,每个月上机次数、总营业额,将统计结果保存到文本文件中,并以列表形式显示在控制台中。

6.1  添加管理员 超级管理员添加一个管理员信息

6.2 权限管理 删除管理员 超级管理员删除一个管理员信息

6.3  配置权限 添加管理员时,配置管理员的权限

7.1 系统 登录 超级管理员和管理员登录系统

7.2  退出 超级管理员和管理员退出系统

特别强调的是:表格的顺序并不是我们实际实现的顺序,要合理分析~

比如,先有什么才有什么,按逻辑才是最重要的~

下面我将细致地一步一步讲解~

我的讲解方式是,先说思路和给源文件全部源码,再细分去一个个分析~

例如,给全部—>源文件—>具体函数—>代码块~,而头文件,只需要需要的时候补充就好了

总分分分分的形式~

所以一旦发现陌生的东西都不要担心哦,后面都会细说的~

并且我们可以假设已经写好所有的函数,定好大致框架~

源码在这里:网吧管理系统 · 游离态

总的思维导图在这里捏:网吧管理系统/data/网络管理系统.xmind · 游离态

后续讲得比较分散,可能会导致哪些东西不知道要放哪,所以你可以尝试看一看全部的源码~

7. 系统

开启系统:


登录

进入系统后登录~

确定身份后登录后进行其他操作,才有登出操作

登出后可以选择继续登录

关闭系统

不选择继续登录,可选择是否再次开启系统~

退出选择:


程序的终结~

真正意义上的完全退出~

7.1 主函数设计~

建立一个源文件test.c,头文件basis.h


源码在这里 : 网吧管理系统/test.c · 游离态 网吧管理系统/basis.h · 游离态

此时在头文件已有信息:


重点说明:不同源文件的信息只需要在头文件申明就好,因为每个源文件都包含了这个basis.h头文件~


并且会在后方填入写在哪个文件,并添加函数是在调用者函数的上方~

#pragma once//防止重复包含头文件

//不要在这个文件开始运行程序~~~会报错!
//并且上方列表如无出现test.c,也会报错
#define _CRT_SECURE_NO_WARNINGS 1
//系统头文件
#include<stdio.h>
#include<stdlib.h> //动态内存分配函数~
void menu();


此时在源文件test.c已有信息:


#include "basis.h"//用双引号:第一步在本目录下查找头文件,第二步,在系统中查找头文件
     //系统头文件一般用< >,一步到位直接在系统中查找头文件~~
int main() {
  int input = 0;
  do {
  menu();
  scanf("%d", &input);
  switch (input) {
    case 1:
    signIn();//选择开启系统进入登录页面~~~
    break;
    case 0:
    printf("退出成功\n");//退出减少简单的退出,程序彻底的结束了~
    break;
    default:
    printf("请重新输入\n");//输入错误了~
    break;
  }
  } while (input);
  return 0;
}

这里用到了常见的多次选择算法~


实现每次从关闭系统之后选择是否继续开启系统~


建立一个源文件Menu.c,存放所有的菜单~


现在已有信息:


void menu() {
  printf("***************\n");
  printf("1. 管理员登录 \n");
  printf("0. 退出\n");
  printf("***************\n");
}


7.2 开启系统后登录

signIn()函数
void signIn() {
  printf("请输入编号,姓名以及六位密码:>");
  int id = 0;
  char name[10] = { 0 };
  char password[7] = { 0 };
  int retscan = scanf("%d%s%s", &id, name, password);
  if (retscan == 0) {
  printf("\n由于输入错误,系统崩坏\n");
  exit(0);
  }
  //错误输入会死循环的原因在于,格式化输入错误
   //----------------------------- 
  FILE* pf = fopen("data\\manager.txt", "rb");
  if (pf == NULL) {
  init();
  pf = fopen("data\\manager.txt", "rb");
  }
  fread(&managerNumber, sizeof(int), 1, pf);
  if (managerNumber == 0) {
  fclose(pf);
  init();
  pf = fopen("data\\manager.txt", "rb");
  }
  Manager* pm = (Manager*)malloc(sizeof(Manager));
  fread(pm, sizeof(Manager), 1, pf);
  Manager* cur = pm;
  head = pm;
  for (int i = 1; i < managerNumber; i++) {
  Manager* newOne = (Manager*)malloc(sizeof(Manager));
  fread(newOne, sizeof(Manager), 1, pf);
  newOne->next = NULL;
  cur->next = newOne;
  cur = cur->next;
  }
  fclose(pf);
   //----------------------------- 
  cur = pm;
  while (cur != NULL) {
  if (id == cur->id
    && strcmp(cur->name, name) == 0
    && strcmp(cur->password, password) == 0) {
    letUsGo(cur);
    return;
  }
  cur = cur->next;
  }
  printf("您暂且不是管理员或者您的卡号密码错误\n");//能到这里必然就是登录失败的~
}



不用担心, 下面是细节分析!


7.2.1 输入

printf("请输入编号,姓名以及六位密码:>");
  int id = 0;//编号
  char name[10] = { 0 };//姓名
  char password[7] = { 0 };//密码
  int retscan = scanf("%d%s%s", &id, name, password);//返回值为正常输入的元素个数~
//如果输入失败,里面退出
  if (retscan == 0) {
  printf("\n由于输入错误,系统崩坏\n");
  exit(0);//库函数,exit(0)代表安全退出~
  }
  //错误输入会死循环的原因在于,格式化输入错误


7.2.2 初始化+导入

引入结构体Manager(basis.h)

//管理员,typedef -- 换名字~
typedef struct Manager {
  int id;//编号
  char name[10];//名字
  char password[7];//密码
  char limits[8];//权限
  struct Manager* next;//后驱
}Manager;


重点讲解权限:有7种权限,1代表有权限,0代表无权限

FILE* pf = fopen("data\\manager.txt", "rb");

if (pf == NULL || managerNumber == 0) {

 init();//初始化函数

 pf = fopen("data\\manager.txt", "rb");//要重写打开哦~

}



fread(&managerNumber, sizeof(int), 1, pf);
  Manager* pm = (Manager*)malloc(sizeof(Manager));
  fread(pm, sizeof(Manager), 1, pf);
  Manager* cur = pm;
  head = pm;
  for (int i = 1; i < managerNumber; i++) {
  Manager* newOne = (Manager*)malloc(sizeof(Manager));
  fread(newOne, sizeof(Manager), 1, pf);
  newOne->next = NULL;
  cur->next = newOne;
  cur = cur->next;
  }
  fclose(pf);
对于读取失败或者管

理员的个数为0的情况下,进行初始化


引入全局变量:managerNumber head


managerNumber(test.c)


计算已有管理员的个数


放在源文件中,然后头文件声明


int managerNumber = 0;//源文件中


extern managerNUmber;


head(basis.h)(必须放在Manager结构体定义之后)


存储已有管理员,作为头结点代表整条链表


方便后续释放空间以及更新管理员信息二进制文件~


放在头文件中(指针应该放在头文件),原因不做解释


Manager* head;


初始化函数(test.c)


重点说明:每一个二进制文件的前四个字节,我默认放的都是元素个数 !


*建立存放manager信息的二进制文件~==》*manager.txt


据学校要求,放置在data文件中,必须提前建立~


malloc库函数~ 申请堆区空间,函数栈帧销毁此~


字符串拷贝用strcpy库函数


可以记录一下管理员表格,防止忘记了~

位置:网吧管理系统/data/备忘录.xlsx · 游离态

void init() {
      FILE* pfr = fopen("data\\manager.txt", "wb");//自动建立二进制文件
        //选取二进制文件是因为读取写入最方便安全~~~
      Manager* pm1 = (Manager*)malloc(sizeof(Manager));
      Manager* pm2 = (Manager*)malloc(sizeof(Manager));
      Manager* pm3 = (Manager*)malloc(sizeof(Manager));
      Manager* pm4 = (Manager*)malloc(sizeof(Manager));
      Manager* pm5 = (Manager*)malloc(sizeof(Manager));
      Manager* pm6 = (Manager*)malloc(sizeof(Manager));
      pm1->id = 0;
      strcpy(pm1->name, "小马");
      strcpy(pm1->password, "123456");
      strcpy(pm1->limits, "1111111");
      pm2->id = -1;
      strcpy(pm2->name, "小张");
      strcpy(pm2->password, "123456");
      strcpy(pm2->limits, "1111111");
      pm3->id = -2;
      strcpy(pm3->name, "老师");
      strcpy(pm3->password, "123456");
      strcpy(pm3->limits, "1111111");
      pm4->id = 1;
      strcpy(pm4->name, "小卡拉");
      strcpy(pm4->password, "123456");
      strcpy(pm4->limits, "1110000");
      pm5->id = 2;
      strcpy(pm5->name, "小空多尼");
      strcpy(pm5->password, "123456");
      strcpy(pm5->limits, "1111110");
      pm6->id = 3;
      strcpy(pm6->name, "小林");
      strcpy(pm6->password, "123456");
      strcpy(pm6->limits, "1111110");
      managerNumber = 6;
      fwrite(&managerNumber, sizeof(int), 1, pfr);//给6个
      fwrite(pm1, sizeof(Manager), 1, pfr);
      fwrite(pm2, sizeof(Manager), 1, pfr);
      fwrite(pm3, sizeof(Manager), 1, pfr);
      fwrite(pm4, sizeof(Manager), 1, pfr);
      fwrite(pm5, sizeof(Manager), 1, pfr);
      fwrite(pm6, sizeof(Manager), 1, pfr);
      fclose(pfr);
}

初始化默认加入三个超级管理员,三个普通管理员~


超级管理员编号小于等于0~ ,普通管理员编号默认大于0~


超级管理员权限全是1~


存入文件,成员next 是没有价值的,下次读取这个地址也没用~


对于读取成功,进行内容导入~


改变全局变量managerNumber


fread(&managerNumber, sizeof(int), 1, pf);


此时文件不可能没有内容,因为经过初始化了


Manager* pm = (Manager*)malloc(sizeof(Manager));
  fread(pm, sizeof(Manager), 1, pf);
  Manager* cur = pm;
  head = pm;//用全局head -> 记录头结点地址~


读取文件并关闭文件~


一个元素一个元素的读取,以尾插法延伸~

for (int i = 1; i < managerNumber; i++) {
  Manager* newOne = (Manager*)malloc(sizeof(Manager));
  fread(newOne, sizeof(Manager), 1, pf);
  newOne->next = NULL;
  cur->next = newOne;
  cur = cur->next;
  }
  fclose(pf);


7.2.3 遍历链表~

字符串之间比较用strcmp函数~

包含头文件#include<string.h>(basis.h)

cur = pm;//回到链表首~
  while (cur != NULL) {
  if (id == cur->id
    && strcmp(cur->name, name) == 0
    && strcmp(cur->password, password) == 0) {
    letUsGo(cur);//登录成功后的操作~
    return;
  }
  cur = cur->next;//走向下一步~
  }
  printf("您暂且不是管理员或者您的卡号密码错误\n");//能到这里必然就是登录失败的~


7.3 登录后操作

登录系统后预操作函数:letUsGo()(test.c)

void letUsGo(Manager* pm) {
  int input = 0;
    //函数列表~
    //操作相关函数~
    //返回值参数列表都相同~
  void(*opera[7])(Manager*) =
  { 
  exitOut, cardManage, 
  chargeStandard, chargeManage, 
  expenseManage, searchStatistics, 
  limitsManage 
  };
  printf("--------------------------\n");
  time_t t = time(NULL);
  printf("%s\n于%s登录系统\n", pm->name, ctime(&t));
  printf("--------------------------\n");
  do {
  systemMenu(pm);
  scanf("%d", &input);
  if (pm->limits[input] != '1') {
    printf("你并没有权限");
  }
  else {
    opera[input](pm);
    //管理员链表的free,在退出登录时free
  }
  } while (input);
}



7.3.1 ※函数指针数组(C进阶语法)

对于一个函数而言,其函数名实际上就是一个函数指针,并且函数指针的地址还是本身~


函数指针形式: 用(*p)去替换函数名位置


例如void exitOut(Manager* pm) ===》void (*p)(Manager*)

那么函数指针数组就是(*opera[])去替换函数名位置~


即void (*opear[])(Manager*)


这个数组中的内容都是同一类函数的函数名~


参数列表以及返回值是相同的~

用这个p指针可以直接调用这个函数

exitOut(pm) <=> opera[0](pm) <=> p0(pm)
  //下标对应好哦!               
  void(*opera[7])(Manager*) =
  { 
  exitOut, cardManage, 
  chargeStandard, chargeManage, 
  expenseManage, searchStatistics, 
  limitsManage 
  };


这些是后续的操作的函数名列表~


对于为什么用函数指针,是为了让代码更加简洁,并且提高运行速率(空间换时间);


7.3.2 登录成功显示

包含一个头文件#include<time.h> (basis.h)

这个头文件里面含有时间函数~

有我们所需要的函数:time() 和 ctime()

前者是获得当前时间戳【1970年1月1日 00:00:00到现在的秒数】

后者是将时间戳转化为字符串(年月日时分秒)【英文~】

首先,time(NULL) ==> 获取当前时间戳**(类型为time_t ,实际上是64位长整形)**

其次,将地址传过去

注意,不能写成&(time(NULL)),一个确切的值怎么可以被取地址?只有变量/常量可以

printf("--------------------------\n");

time_t t = time(NULL);

printf("%s\n于%s登录系统\n", pm->name, ctime(&t));

printf("--------------------------\n");


7.3.3 选择操作环节

引入菜单systemMenu(pm)(Menu.c)


void systemMenu(Manager* pm) {
    //通过编号确认管理员类型~
  if (pm->id <= 0) {
  printf("你好,超级管理员,%s!\n", pm->name);
  }
  else {
  printf("你好,普通管理员,%s!\n", pm->name);
  }
    //根据权限判断~~
  printf("******************\n");
  if (pm->limits[0] == '1') {
  printf("0. 退出系统\n");//所有人都有这个功能,要怎么设计呢,随后揭晓~
  }
  if (pm->limits[1] == '1') {
  printf("1. 卡管理\n");
  }
  if (pm->limits[2] == '1') {
  printf("2. 计费标准管理\n");
  }
  if (pm->limits[3] == '1') {
  printf("3. 计费管理\n");
  }
  if (pm->limits[4] == '1') {
  printf("4. 费用管理\n");
  }
  if (pm->limits[5] == '1') {
  printf("5. 查询统计\n");
  }
  if (pm->limits[6] == '1' && pm->id <= 0) {
  printf("6. 权限管理\n");
  }
  printf("******************\n");
}


只有有权限的内容才会显示!


还是老样子~==》多次输入确认算法

int input = 0;
  do {
  systemMenu(pm);
  scanf("%d", &input);
        //确保是否有权限(因为可能菜单没显示,他也选了~这不就是卡bug了吗)
  if (pm->limits[input] != '1') {
    printf("你并没有权限");
  }
  else {
    opera[input](pm);//调用特定下标的函数~~~
    //管理员链表的free,在退出登录时free,不能在退出权限设置的时候free,这样会导致pm被释放,后续无法操作
            //有个误区,就是每次操作的退出,都要对直接影响的链表进行保存释放~权限设置函数确实直接影响了管理员链表
            //但是不能在那个时候释放~
            //这里看不懂无所谓,等权限设置函数讲完之后在看
  }
  } while (input);



选择0调用退出函数并结束循环~


7.4 关闭系统~exitOut

引入void exitOut(Manager* pm)(Exit.c)


即这里的opera[0]~


void exitOut(Manager* pm) {
  time_t t = time(NULL);
  printf("--------------------------\n");
  printf("%s\n于%s退出系统\n", pm->name, ctime(&t));
  printf("--------------------------\n");
  exitOutManager();
    //在这里就可以将管理员链表进行更新了,因为这里代表了此管理员操作的终结,此管理员有可能新增或者删除过管理员~
}


退出的时候使用时间函数,报告时间,模拟实际情况~


在这里退出,是因为这一步结束后,应该是别的管理员进入系统了,所以更新管理员链表,让新添加的管理员可以紧接着进入~


后面会细讲~

7.4.1 更新管理员链表

exitOutManager(Exit.c)
void exitOutManager() {
  printf("退出成功\n");
  FILE* pf = fopen("data\\manager.txt", "wb");
  fwrite(&managerNumber, sizeof(int), 1, pf);
  //记录管理员名单
  Manager* cur = head;
  while (cur != NULL) {
  fwrite(cur, sizeof(Manager), 1, pf);
  Manager* tmp = cur;//通过寄存器做中介
  cur = cur->next;
  free(tmp);
  }
  //在这里一定要关闭,不然只会到程序结束,内容才会从缓存区传入文件!!!
  fclose(pf);
  pf = NULL;
}



将管理员个数导入文件

从头开始遍历,将每一个节点导入文件

同时将每一部分空间进行释放~

不释放,空间泄露可能会很危险,对于一些大工程而言

所以要有这个习惯~

7.4.2 退出操作文件 - Exit.c

我将所有退出操作放进一个源文件中

方便管理~

源码在这里:网吧管理系统/Exit.c · 游离态

7.5 测试

代码运行正常~


二进制文件显示正常~

因为VS2022中,在内存中,数据存储是小端存储~

小端存储就是,真值小的字节地址小,真值大的字节地址大

大端就是反着来

所以下图的06 00 00 00,8位十六进制数字,在内存中对应的int整形就是6~


6. 权限管理~

不难想到这个基础菜单是因为有权限所以才看得到,这个操作应该要先实现~

6.1 主体函数设计

引出函数limitsManage()(LimitsManage.c)

选项少,用switch( ) + do while

void limitsManage(Manager* pm) {
  int input = 0;
  do {
  menu6_1();//菜单
  scanf("%d", &input);
  switch (input) {
  case 0 : 
    printf("退出成功\n"); //退出就是简简单单的退出~
    break;
  case 1:
    add(pm);//增加函数
    break;
  case 2 : 
    delete(pm);//删除函数
    break;
  default :
    printf("输入错误\n");
    break;
  }
  } while (input);
}


6.2 菜单(Menu.c)

选择增减 菜单~


void menu6_1() {
  printf("******************\n");
  printf("0. 退出此操作\n");
  printf("1. 添加管理员\n");
  printf("2. 删除管理员\n");
  printf("******************\n");
}


权限设置 菜单~


void menu6_2(Manager* pm) {
  printf("******************\n");
  printf("1. 卡管理权限\n");
  printf("2. 计费标准管理权限\n");
  printf("3. 计费管理权限\n");
  printf("4. 费用管理权限\n");
  printf("5. 查询统计权限\n");
  printf("0. 结束本次操作\n");
  printf("******************\n");
}


6.2.1 菜单文件 - Menu.c

我将所有问菜单放在了一个源文件中,方便管理~

源码在这里:/网吧管理系统/Menu.c · 游离态

6.3 删除管理员函数

delect(LimitsManage.c)


由于超级管理员不能新增,并且被我设置在前面

所以拥有此权限的人,必然是超级管理员

得出删除的管理员必然在pm的后面~

只需要用探路指针遍历就好~


void delete(Manager* pm) {
  printf("请输入管理员的编号,姓名:>");
  //由于此操作只能由超级管理员操作并且超级管理员在最前面,
  //所以在这里往后遍历就好,但是不能删除超级管理员
  int id = 0;
  char name[10] = { 0 };
  Manager* cur = pm;
  do {
  scanf("%d%s", &id, name);
  if (id <= 0) {
    printf("无法删除!\n");//这里绝对是不行的!!!因为超级管理员的编号<=0~
  }
  } while (id <= 0);
  while (cur->next != NULL) {
  if(id == cur->next->id && strcmp(name, cur->next->name) == 0){
    Manager* rubbish = cur->next;
    cur->next = rubbish->next;
    free(rubbish);
    printf("删除成功\n");
    managerNumber--;
    return;
  }
  cur = cur->next;
  }
  printf("此人并不是管理员\n");
}



无法删除超级管理员~


输入编号姓名删除管理员


遍历链表查找(由于编号唯一,理当只删一个,不应该在添加管理员时使用相同编号,但是我这个项目不会查重


找到了删完return


这里的思路是:(删除节点操作)

pm是本人,绝对不是被删除的,所以只需要判断后面的就行

每次都判断后驱的那个是不是要被删的

是的话,记录待被删除的节点(要free哦)

Manager* rubbish = cur->next;

cur->next = rubbish->next; 越过节点,完成删除~

free(rubbish);

managerNumber--; 管理员数量减一


找不到打印找不到的信息~


6.4 增加管理员 + 权限设置

函数add (LimitsManage.c)
void add(Manager* pm) {
  printf("请输入新增管理员的编号,姓名,六位密码:>");
  Manager* cur = pm;
  while (cur->next != NULL) {
  cur = cur->next;
  }
  int id = 0;
  char name[10];
  char password[7];
  scanf("%d%s%s", &id, name, password);
  if (id <= 0) {
  printf("普通管理员的id理应大于0!\n");
  return;
  }
  else {
  cur->next = (Manager*)malloc(sizeof(Manager));
  cur = cur->next;
  cur->id = id;
  strcpy(cur->name, name);
  strcpy(cur->password, password);
        strcpy(cur->limits, "0000000");
  cur->next = NULL;
  }
  int input = 0;
  menu6_2(pm);
  printf("请输入要为其设置的权限:>");
  do {
  scanf("%d", &input);
  if (input <= 5 && input >= 0) {
    cur->limits[input] = '1';
  }
  else {
    printf("输入失败\n");
  }
  } while (input);
  printf("添加成功\n");
  managerNumber++;
}



6.4.1 找到尾巴节点进行尾插~~

找到尾巴并设置基础属性,编号、姓名、六位密码~

printf("请输入新增管理员的编号,姓名,六位密码:>");
  Manager* cur = pm;
  while (cur->next != NULL) {
  cur = cur->next;
  }
  int id = 0;
  char name[10];
  char password[7];
  scanf("%d%s%s", &id, name, password);
  if (id <= 0) {
  printf("普通管理员的id理应大于0!\n");
  return;
  }
  else {
  cur->next = (Manager*)malloc(sizeof(Manager));
  cur = cur->next;
  cur->id = id;
  strcpy(cur->name, name);
  strcpy(cur->password, password);
  strcpy(cur->limits, "0000000");//默认全为0~
        cur->next = NULL;
  }



探路指针cur代替pm去跑到末尾~

判断id是否合理(不合理直接退出)

合理即添加

6.4.2 设置权限

此时的cur指向的就是新增节点~


通过多次输入选项,对对应的权限进行设置


按0的时候,会直接将0下标的那个权限设置为1,顺带因此退出了本次操作


不能赋予增减管理员设置的权限~


managerNumber++;管理员数+1

int input = 0;
  menu6_2(pm);//菜单~~~
  printf("请输入要为其设置的权限:>");
  do {
  scanf("%d", &input);
  if (input <= 5 && input >= 0) {
    cur->limits[input] = '1';
  }
  else {
    printf("输入失败\n");
  }
  } while (input);
  printf("添加成功\n");
  managerNumber++;


6.5 退出操作

简简单单的退出~

※注意:管理员链表的更新应该在这个管理员退出系统的时候才能更新,否则此管理员被释放了,无法继续后面的操作!并且新增的管理员才能进入系统~


6.5 测试



测试结果正常~

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

下面的测试案例,都是事先被我插入数据的~

运行正常~


二进制文件显示正常~

目录
相关文章
|
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
|
21天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
39 0
|
1月前
|
存储 编译器 C语言
【C语言】学生管理系统:完整模拟与实现(一)
【C语言】学生管理系统:完整模拟与实现