【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

  • 此时在头文件已有信息:

    • 重点说明:不同源文件的信息只需要在头文件申明就好,因为每个源文件都包含了这个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库函数

                  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 是没有价值的,下次读取这个地址也没用~

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

    1. 改变全局变量managerNumber

      	fread(&managerNumber, sizeof(int), 1, pf);
      
    2. 此时文件不可能没有内容,因为经过初始化了

      	Manager* pm = (Manager*)malloc(sizeof(Manager));
      	fread(pm, sizeof(Manager), 1, pf);
      	Manager* cur = pm;
      	head = pm;//用全局head -> 记录头结点地址~
      
    3. 读取文件并关闭文件~

      • 一个元素一个元素的读取,以尾插法延伸~
      	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;
    }
    
  1. 将管理员个数导入文件
  2. 从头开始遍历,将每一个节点导入文件
  3. 同时将每一部分空间进行释放~
    • 不释放,空间泄露可能会很危险,对于一些大工程而言
    • 所以要有这个习惯~
7.4.2 退出操作文件 - 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

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");
    }
    
  1. 无法删除超级管理员~

  2. 输入编号姓名删除管理员

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

    • 找到了删完return

      • 这里的思路是:(删除节点操作)
        1. pm是本人,绝对不是被删除的,所以只需要判断后面的就行
        2. 每次都判断后驱的那个是不是要被删的
        3. 是的话,记录待被删除的节点(要free哦)
          1. Manager* rubbish = cur->next;
          2. cur->next = rubbish->next; 越过节点,完成删除~
          3. free(rubbish);
          4. 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去跑到末尾~
  1. 判断id是否合理(不合理直接退出)
  2. 合理即添加
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;
  1. type => 卡的类型~
  2. state=> 是否有此标准~
  3. 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

这里的 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;
}
  1. 标准的个数一直都是4个,顺序一定1 2 3 4,只不过state可能有所不同,所以我用的是顺序表
  2. 打开文件失败则必然是不存在此文件,则应该进行判断~

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. 卡管理~

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;
  1. 卡号

  2. 密码

  3. 余额

  4. 效应—注销与否

  5. 卡的类型—计费标准

  6. 上机的时间的时间戳~(64位长整形)

  7. 状态—上机了与否

  8. 后继节点—构造链表

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;
}
  1. 文件指针为NULL代表打不开文件,说明文件不存在
    • 则我只需要建立一个,并且将整数0导入文件中~
    • 我的一个习惯:我会讲元素个数放在文件的首位~
      • 刚才的计费标准没有是因为一直都是4个~
  2. 文件打开了,进行标准的读取操作
    • 首先拿到卡数~
    • 然后获得第一张卡~
    • 后续以尾插的形式进行插入~
  3. 重点:要将尾节点的后驱置为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数据库的表格~
  1. 占位符的左右对齐==>负号左对齐~
  2. 小数的话,左侧数字为最终小数所占的位数~(包括小数点在内)
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 测试

在这里插入图片描述

  • 测试结果正常~

3. 计费管理

  • 接下来是比较复杂的两个环节,计费管理以及查询统计~

  • 计费管理操作上机下机,下机的时候计费~

    • 需求:
      • 记录上机记录
      • 记录下机消费记录
    • 这些记录就是为了查询统计而生的~
  • 引入新的源文件:ChargeManage.c

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

在这里插入图片描述

3.1 两个记录性结构体~

3.1.1 Consume 消费记录(basis.h)
typedef struct Consume {
	int id;//卡号
	long long  timeNode;//时间节点
	double money;//下机则为实际收费,合理是相同的
	struct Consume* next;//后继
}Consume;
  1. 卡号
  2. 对应交易时间时间戳
  3. 交易金额(按比例/按单位)
  4. 后继节点~
3.1.2 Puncher 上机记录(basis.h)
//上机记录
typedef struct Puncher {
	int id;//卡号
	long long  timeNode;//时间节点
	struct Puncher* next;//后继
}Puncher;
  1. 卡号
  2. 对应上机时间时间戳
  3. 后继节点~

3.2 主体函数设计~

chargeManage(ChargeManage.c)

  • 引入新的全局变量

    • orderNumber消费单数~
    • workNumber上机记录数据数~
  • 导入卡号,消费记录,上机记录~

  • 根据菜单进行选择~

    • 上机
    • 下机
    • 退出则更新释放三条链表~
void chargeManage(Manager* pm) {
	int input = 0;
	Card* pc = cardCarry();
	Consume* psu = consumeCarry();
	Puncher* ppu = puncherCarry();
	do {
		menu3_1();
		scanf("%d", &input);
		switch (input) {
		case 1 :
			onComputer(pc, ppu);
			break;
		case 2 :
			offComputer(pc, psu);
			break;
		default:
			printf("输入错误\n");
			break;
		case 0:
			exitOutCard(pc);
			exitOutConsume(psu);
			exitOutPuncher(ppu);
			printf("退出成功\n");
			break;
		}
	} while (input);
}
3.2.1 读取文件操作
3.2.1.1 consumeCarry(Carry.c)
  • 读取data文件中的consume.txt二进制文件
Consume* consumeCarry() {
	FILE* pf = fopen("data\\consume.txt", "rb");
	Consume* psu = (Consume*)malloc(sizeof(Consume));//堆区空间,不会被收回
	if (pf == NULL) {
		pf = fopen("data\\consume.txt", "wb");
		fwrite(&orderNumber, sizeof(int), 1, pf);
		fclose(pf);
	}
	else {
		fread(&orderNumber, sizeof(int), 1, pf);
		fread(psu, sizeof(Consume), 1, pf);
		Consume* cur = psu;
		for (int i = 1; i < orderNumber; i++) {
			Consume* newOne = (Consume*)malloc(sizeof(Consume));
			fread(newOne, sizeof(Consume), 1, pf);
			newOne->next = NULL;
			cur->next = newOne;
			cur = cur->next;
		}
	}
	return psu;
}
  1. 文件指针为NULL代表打不开文件,说明文件不存在
    • 则我只需要建立一个,并且将整数0导入文件中~
    • 我的一个习惯:我会讲元素个数放在文件的首位
  2. 文件打开了,进行标准的读取操作
    • 首先拿到消费单数orderNumber~
    • 然后获得第一单~
    • 后续以尾插的形式进行插入~
      • 后续应该判断第一个节点是否是有效数据~
  3. 重点:要将尾节点的后驱置为NULL,否则会导致野指针异常/死循环~
3.2.1.2 puncherCarry(Carry.c)
  • 读取data文件中的puncher.txt二进制文件
Puncher* puncherCarry() {
	FILE* pf = fopen("data\\puncher.txt", "rb");
	Puncher* ppu = (Puncher*)malloc(sizeof(Puncher));//堆区空间,不会被收回
	if (pf == NULL) {
		pf = fopen("data\\puncher.txt", "wb");
		fwrite(&workNumber, sizeof(int), 1, pf);
		fclose(pf);
	}
	else {
		fread(&workNumber, sizeof(int), 1, pf);
		fread(ppu, sizeof(Puncher), 1, pf);
		Puncher* cur = ppu;
		for (int i = 1; i < workNumber; i++) {
			Puncher* newOne = (Puncher*)malloc(sizeof(Puncher));
			fread(newOne, sizeof(Puncher), 1, pf);
			newOne->next = NULL;
			cur->next = newOne;
			cur = cur->next;
		}
	}
	return ppu;
}
  • 与刚才是一样的~
  1. 文件指针为NULL代表打不开文件,说明文件不存在
    • 则我只需要建立一个,并且将整数0导入文件中~
    • 我的一个习惯:我会讲元素个数放在文件的首位
  2. 文件打开了,进行标准的读取操作
    • 首先拿到上机记录数据数workNumber~
    • 然后获得第一份数据~
    • 后续以尾插的形式进行插入~
      • 后续应该判断第一个节点是否是有效数据~
  3. 重点:要将尾节点的后驱置为NULL,否则会导致野指针异常/死循环~

3.3 菜单

  • 上下机操作菜单

    • 提醒顾客过早下机会导致浪费~

    • void menu3_1() {
      	printf("****************************************\n");
      	printf("※ 请提示顾客:时间较长的卡请勿过早下机\n"
      		"电脑时刻开着呢,不需要再次上机\n");
      	printf("0. 退出\n");
      	printf("1. 上机\n");
      	printf("2. 下机\n");
      	printf("****************************************\n");
      }
      

3.4 上机

conComputer(ChargeMange.c)

  • 输入卡号密码确认是否符合身份
    • 上机中/已注销无法再上机
    • 将上机记录信息记录在ppu中~
    • 上机成功后用时间函数报当时时间信息
  • 找不到即报无~
void onComputer(Card* pc, Puncher* ppu) {
	printf("请输入卡号与密码:>");
	Card* cur = pc;
	int id = 0;
	char password[7];
	scanf("%d%s", &id, password);
	while (cardNumber != 0 && cur != NULL) {
		if (cur->id == id && strcmp(password, cur->password) == 0) {
			if (cur->effect != 1) {
				printf("此卡已被注销,无法上机\n");
				return;
			}
			else {
				if (cur->state == 1) {
					printf("已经是上机状态,本次操作失效\n");
				}
				cur->state = 1;
				time_t t = time(NULL);
				cur->upTime = t;
				//上机记录~~
				clockIn(cur, ppu);
				//上机记录~~
				printf("--------------------------\n");
				printf("上机成功\n");
				printf("时间:%s", ctime(&t));
				printf("--------------------------\n");
			}
			return;
		}
		cur = cur->next;
	}
	printf("上机失败,“可能”原因是此卡暂为开通\n");
}
3.4.1 上机记录函数clockIn(ChargeManage.c)
void clockIn(Card* cur, Puncher* ppu) {
	Puncher* newOne = (Puncher*)malloc(sizeof(Puncher));
	newOne->id = cur->id;
	newOne->next = NULL;
	newOne->timeNode = cur->upTime;
	if (workNumber == 0) {
		memcpy(ppu, newOne, sizeof(Puncher));
	}
	else {
		Puncher* current = ppu;
		while (current->next != NULL) {
			current = current->next;
		}
		current->next = newOne;
	}
	workNumber++;
}
  • 重点要处理workNumber为0时的那个假数据!
    • 用memcpy(库里内存函数,string.h)
  • 新节点newOne记录信息后,尾插到链表尾
  • workNumber上机记录数据数 + 1

3.5 下机

offnComputer(ChargeMange.c)

  • 输入卡号密码确认是否符合身份
    • 未上机/卡的计费标准不存在的情况下,无法下机
    • 将消费记录信息记录在psu中~
      • 对于余额此刻小于0后,强制注销~
    • 下机成功后用时间函数报当时时间信息
  • 找不到即报无~
void offComputer(Card* pc, Consume* psu) {
	printf("请输入卡号与密码:>");
	Card* cur = pc;
	int id = 0;
	char password[7];
	scanf("%d%s", &id, password);
	Standard* pcs = standardCarry();
	while (cardNumber != 0 && cur != NULL) {
		if (cur->id == id && strcmp(password, cur->password) == 0) {
			if (cur->state == 0) {
				printf("已经是下机状态,本次操作失效\n");
				return;
			}
			if (pcs[cur->cardType - 1].state == 1) {//未上机
				time_t t = time(NULL);
				long long longTime = t - cur->upTime;
				double gap = 1.0 * longTime / transfer(cur->cardType);
				if (gap != (int)gap) {
					gap = (int)gap + 1;
				}
				//记录收益记录~~
				settlement(cur, psu, t, gap, pcs);
				//记录收益记录~~
				cur->state = 0;
				if (cur->balance < 0) {
					printf("此卡已欠费,账号已被注销,请充值缴费\n");
					cur->effect = 0;
				}
				printf("--------------------------\n");
				printf("下机成功, 账户已更新\n");//下机则结算
				printf("时间:%s", ctime(&t));
				printf("--------------------------\n");
			}
			else {
				printf("下机失败~请补充计费标准~\n");
			}
			exitOutStandard(pcs);//释放空间
			return;
		}
		cur = cur->next;
	}
	printf("下机失败,“可能”原因是此卡暂为开通\n");
}
3.5.1 transfer(计费标准转化秒数函数 ChargeManage.c)
//3600------1h
//86400-----1day
//2592000---1month//默认30天
//31536000--1year//默认365天
long long transfer(int i) {
	switch (i) {
		case 1 :	
			return 31536000;
		case 2 :	
			return 2592000;
		case 3 :	
			return 86400;
		case 4 :	
			return 3600;
	}
}
3.5.2 局部变量:gap
  • 例如年卡,一年以内gap为1,一年到两年之间gap为2,恰好为两年gap为2~
  • 表示几个单价~
		time_t t = time(NULL);
		long long longTime = t - cur->upTime;
		double gap = 1.0 * longTime / transfer(cur->cardType);
		if (gap != (int)gap) {
			gap = (int)gap + 1;//不足进1~
		}
3.5.3 下机消费记录函数settlement(ChargeManage.c)
void settlement(Card* cur, Consume* psu, time_t t, double gap, Standard* pcs) {
	
    double benifit = gap * pcs[cur->cardType - 1].price;
	cur->balance -= benifit;
	Consume* newOne = (Consume*)malloc(sizeof(Consume));
	newOne->id = cur->id;
	newOne->money = benifit;
	newOne->next = NULL;
	newOne->timeNode = t;

	if (orderNumber == 0) {
		memcpy(psu, newOne, sizeof(Consume));
	}
	else {
		Consume* current = psu;
		while (current->next != NULL) {
			current = current->next;
		}
		current->next = newOne;
	}
	orderNumber++;
}
  • 根据gap和计费标准pcs确定交易金~
  • 制作newOne新节点
  • 将新节点插入到psu的链表尾
    • 处理orderNumber为0时头结点为假数据的情况~
  • orderNumber 消费单数 + 1

3.6 退出操作

  • 对三链表进行释放更新处理~
  • 卡链表的退出已在上文书写
3.6.1 消费单表的更新

exitOutConsume(Exit.c)

  • 数据导入data(必须自己建立)文件的consume.txt二进制文件中

  • 如果卡数为0,那么只讲0导入文件即可,因为这个节点不为NULL但是不是有效数据!

  • 不为0遍历链表导入数据,并且用相同的方法释放空间~

void exitOutConsume(Consume* psu) {
	FILE* pf = fopen("data\\consume.txt", "wb");
	fwrite(&orderNumber, sizeof(int), 1, pf);
	if (orderNumber == 0) {
		return;
	}
	while (psu != NULL) {
		fwrite(psu, sizeof(Consume), 1, pf);
		Consume* tmp = psu;
		psu = psu->next;
		free(tmp);
	}
	fclose(pf);
}
3.6.2 上机记录数据表

exitOutPunche(Exit.c)

  • 数据导入data(必须自己建立)文件的puncher.txt二进制文件中

  • 如果卡数为0,那么只讲0导入文件即可,因为这个节点不为NULL但是不是有效数据!

  • 不为0遍历链表导入数据,并且用相同的方法释放空间~

void exitOutPuncher(Puncher* ppu) {
	FILE* pf = fopen("data\\puncher.txt", "wb");
	fwrite(&workNumber, sizeof(int), 1, pf);
	if (workNumber == 0) {
		return;
	}
	while (ppu != NULL) {
		fwrite(ppu, sizeof(Puncher), 1, pf);
		Puncher* tmp = ppu;
		ppu = ppu->next;
		free(tmp);
	}
	fclose(pf);
}

3.7 补充:注销导致的强制下机计费

  • 前面下机导致的注销,信息已记录
  • 但是注销导致的下机,尚未解决(下面操作出现在卡管理的注销操作中~
    • 在这里插入图片描述
  1. 导入消费单表

  2. 记录强制下机消费记录

  3. 释放更新消费单consume.txt~

3.7.1 强制下机消费记录函数 off (CardManage.c)
void off(Card* pc, Consume* psu) {
	printf("由于此次下机非主动下机,所以此次消费以按比例计算\n");
	Standard* pcs = standardCarry();
	time_t t = time(NULL);
	long long longTime = t - pc->upTime;
	double gap = 1.0 * longTime / transfer(pc->cardType);
	settlement(pc, psu, t, gap, pcs);
	pc->state = 0;
}
  • 为什么gap的类型我要定位double,原因就是我还要再次使用settlement函数
  • 这里我规则是:注销导致强制下机,交易金是以时间占比计算的~
    • 以此计算gap~
  • 调用settlement下机消费记录函数~
  • 并将卡的上机状态改为0 =>下机~

3.8 测试

  • 卡的情况

在这里插入图片描述

  • 测试结果正常~
    • 至于消费记录以及上机记录,再查询统计的时候一起测试~

在这里插入图片描述

  • 二进制文件显示正常~

在这里插入图片描述


5. 查询统计

  • 来到最后一个环节啦啦啦~ ==> 查询统计
      1. 查询单一卡一段时间的消费
      2. 查询全部卡一段时间额总消费
      3. 查询近一年来每个月的营销额以及上机次数~
  • 引出一个新的源文件:SearchStatistics.c
  • 源码在这:网吧管理系统/SearchStatistics.c · 游离态
    在这里插入图片描述

5.1 主体函数设计

searchStatistics(SearchStatistics.c)

  • 导入卡链表
  • 根据菜单选择对应操作~
void searchStatistics(Manager* pm) {
	int input = 0;
	Card* pc = cardCarry();
	void (*func[4])(Card * pc) = 
	{ exitOutStatistics, searchConsume, statisticsTime, statisticsMonths };
	do {
		menu5_1();
		scanf("%d", &input);
		if (input <= 3 && input >= 0) {
			func[input](pc);
		}
		else {
			printf("输入失败\n");
		}

	} while (input);
}
5.1.1 函数指针数组
  • 这里由于选项较多,我选择用函数指针数组,用法与刚才一样,要注意下标与菜单与对应选项要相符合哦~

5.2 菜单

  • 统计菜单
void menu5_1() {
	printf("******************\n");
	printf("0. 退出\n");
	printf("1. 查询消费记录\n");
	printf("2. 统计总营业额\n");
	printf("3. 统计月营业额\n");
	printf("******************\n");
}
  • 选择时间段菜单
void menu5_2() {
	printf("******************\n");
	printf("1. 最近一年\n");
	printf("2. 最近一月\n");
	printf("3. 最近一天\n");
	printf("******************\n");
}

5.3 查询单一卡一段时间的消费~

searchConsume(SearchStatistics.c)

  • 输入卡号,探路指针找到对应卡~

    • 找到后通过菜单选择一个时间段
    • 调用consumeCarry去导入消费记录链表
    • 通过消费记录链表节点的时间属性去判断是否属于该时间段
      • 若属于且是对应卡,则以表格打印出来~
        • 该行用箭头指向右侧的结算时间~
      • 打印思路:(打印的同时用中间寄存器释放空间)
        • 如何对齐请看上文~
        • 在这里插入图片描述
  • 找不到报无~

void searchConsume(Card* pc) {
    //三个选择,最近一天,最近一月,最近一年
	printf("请输入一张卡的卡号:>");
	int id = 0;
	scanf("%d", &id);
	Card* cur1 = pc;
	while (cardNumber != 0 && cur1 != NULL) {
		if (id == cur1->id) {
			menu5_2();
			int number = 0;
			printf("请选择一个时间段(距现在):>");
			scanf("%d", &number);
			long long gapTime = transfer(number);
			Consume* psu = consumeCarry();
			Consume* cur2 = psu;
			printf("+------+-------------+\n");
			printf("|%-6s|%13s|\n", "卡号", "消费/元");
			printf("+------+-------------+\n");
			while (cur2 != NULL) {
				Consume* tmp = cur2;
				if (cur2->id == id && time(NULL) - cur2->timeNode <= gapTime) {
					printf("|%-6d|%13.2lf| 结算时间===>%s", cur2->id, cur2->money, ctime(&cur2->timeNode));
					printf("+------+-------------+\n");
				}
				cur2 = cur2->next;
				free(tmp);
			}
			return;
		}
		cur1 = cur1->next;
	}
	printf("查询失败,暂无此卡\n");
}
5.3.1 细讲判断时间段方法~

在这里插入图片描述

  • 调用transfer,将选择的数字转化为对应时间戳(一个单位时间
  • 如果满足要求且是对应的那张卡,打印下来~

5.4 查询所有卡一段时间的总消费

statisticsTime(SearchStatistics.c)

  • 通过菜单选择一个时间段
  • 调用consumeCarry去导入消费记录链表
  • 引入新的局部变量sum,计算总营业额~
  • 通过消费记录链表节点的时间属性去判断是否属于该时间段
    • 若属于,则以表格打印出来~
      • 该行用箭头指向右侧的结算时间~
    • 打印思路:(打印的同时用中间寄存器释放空间)
      • 如何对齐请看上文~

      • 在这里插入图片描述

      • 并在最后,打印该时间段的总营业额~

void statisticsTime(Card* pc) {
	menu5_2();
	int number = 0;
	printf("请选择一个时间段统计总营业额(距现在):>");
	scanf("%d", &number);
	Consume* psu = consumeCarry();
	Consume* cur = psu;
	printf("+------+-------------+\n");
	printf("|%-6s|%13s|\n", "卡号", "消费/元");
	printf("+------+-------------+\n");
	double sum = 0;
	long long gapTime = transfer(number);
	while (cur != NULL) {
		Consume* tmp = cur;
		if (time(NULL) - cur->timeNode <= gapTime) {
			printf("|%-6d|%13.2lf| 结算时间===>%s", cur->id, cur->money, ctime(&cur->timeNode));
			printf("+------+-------------+\n");
			sum += cur->money;
		}
		cur = cur->next;
		free(tmp);
	}
	printf("|%-6s|%13.2lf|\n", "共计", sum);
	printf("+------+-------------+\n");
}

在这里插入图片描述

5.5 查询统计近一年来每个月的营销额以及上机次数~

statisticsMonths(SearchStatistics.c)

  • 别看代码多,其实模块化很明确~
    • 后面细腻分析~
  • 大致思路是
    1. 通过计算获取现在是第几年第几个月
    2. 然后推导出近12个月
    3. 通过十二个月每一个特定的时间戳范围查询统计划分~
    4. 导入消费记录和上机数据记录~
    5. 遍历多次出打印结果~
void statisticsMonths(Card* pc) {
	Date date = judgeMonth(time(NULL));
	int year = date.year;
	int month = date.month;
	Date dates[12];
	dates[11] = date;
	for (int i = 10; i >= 0; i--) {
		month--;
		if (month == 0) {
			year--;
			month = 12;
		}
		dates[i].timestamp = dates[i + 1].timestamp - months[judgeLeapYear(year)][month] * 86400;
		dates[i].month = month;
		dates[i].year = year;
	}
	FILE* pf = fopen("data\\statisticsMonths.txt", "w");
	Consume* psu = consumeCarry();
	Puncher* ppu = puncherCarry();
	Consume* cur = psu;
	Puncher* current = ppu;
	//做成哈希,还有这样做,构建的过程都要O(N^2)

	//这里是下机计费,所以很有可能上机次数多但是收益少
	fprintf(pf, "+-------+---------------+---------------+\n");
	fprintf(pf, "|%-7s|%15s|%15s|\n", "Month", "Hands-on times", "Total turnover");
	fprintf(pf, "+-------+---------------+---------------+\n");
	printf("+-------+--------+-------------+\n");
	printf("|%-7s|%8s|%13s|\n", "年月份", "上机次数", "月总营销额/元");
	printf("+-------+--------+-------------+\n");


	for (int i = 0; i < 12; i++) {
		cur = psu;
		current = ppu;
		Date d = dates[i];
		long long min = d.timestamp;
		long long max = min + months[judgeLeapYear(d.year)][d.month] * 86400;
		double sum = 0.0;
		int count = 0; //上机次数

		//消费与上机次数统计
		while (orderNumber != 0 && cur != NULL &&
			workNumber != 0 && current != NULL) {
			if (cur->timeNode < max && cur->timeNode >= min) {
				sum += cur->money;
			}
			if (current->timeNode < max && current->timeNode >= min) {
				count++;
			}
			cur = cur->next;
			current = current->next;
		}
		while (orderNumber != 0 && cur != NULL) {
			if (cur->timeNode < max && cur->timeNode >= min) {
				sum += cur->money;
			}
			cur = cur->next;
		}
		while (workNumber != 0 && current != NULL) {
			if (current->timeNode < max && current->timeNode >= min) {
				count++;
			}
			current = current->next;
		}


		fprintf(pf, "|%4d.%2d|%15d|%15.2lf|\n", d.year, d.month, count, sum);
		fprintf(pf, "+-------+---------------+---------------+\n");
		printf("|%4d.%2d|%8d|%13.2lf|\n", d.year, d.month, count, sum);
		printf("+-------+--------+-------------+\n");

	}
	fclose(pf);
	//free掉
	exitOutConsume(psu);
	exitOutPuncher(ppu);
}
5.5.1 引入 Date 年月结构体(basis.h)
//日期年月
typedef struct {
	int year;
	int month;
	long long timestamp;//首时间戳
}Date;
  1. 此年此月的00:00:00时的时间戳~
5.5.2 引入全局变量:润平年月份日数对应数组~

months(SearchStatistics.c)

int months[2][13] = { { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
		{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} };
  1. 平年 – 2月 28天~
  2. 闰年 – 2月 29天~
5.5.3 判断闰年函数以及判断年月函数~
  1. judgeLeapYear(Searchstatistics.c)

  2. judgeMonth(SearchStatistics.c)

int judgeLeapYear(int year) {
	return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
}
Date judgeMonth(long long timeStamp) {
	long long times = 1672502400;
	int year = 2023;
	int month = 1;
	int i = 0;
	while ((times += months[judgeLeapYear(year)][month] * 86400) <= timeStamp) {
		i++;
		month = i % 12 + 1;
		if (month == 1) {
			year++;
		}
	}
	long long timestamp = times - months[judgeLeapYear(year)][month] * 86400;
	Date date = { year, month, timestamp};
	return date;
}
  • 我这里是从2023年开始算起,因为现在不可能是2023年之前~

    • 通过计算,2023-1-1 00:00:00 的时候的时间戳为1672502400~
  • 这里用到返回结构体变量的技巧去返回两个值~
    在这里插入图片描述

5.5.4 推导近12个月
  • 建立一个12大的dates数组
  • 最后一个元素便是judgeMonth(time(NULL))的返回值~
    • 记录year,month
    • 逆推出近12个月~
      • 结合年份月份天数数组,计算对应月份的“首时间戳”~
      • dates[i].timestamp = dates[i + 1].timestamp - months[judgeLeapYear(year)][month] * 86400;
    • 但month为0时,说明是翻到了前年~
      • 应做出处理~
	Date date = judgeMonth(time(NULL));
	int year = date.year;
	int month = date.month;
	Date dates[12];
	dates[11] = date;
	for (int i = 10; i >= 0; i--) {
		month--;
		if (month == 0) {
			year--;
			month = 12;
		}
		dates[i].timestamp = dates[i + 1].timestamp - months[judgeLeapYear(year)][month] * 86400;
		dates[i].month = month;
		dates[i].year = year;
	}
5.5.5 建立文本文档记录 + 打印到屏幕~
  • 引入新文件 "data\\statisticsMonths.txt",建立文本文档~,用“w”
    • 学校要求~
  • 引入两个探路指针~
  • 列表头写入文件中并且打印在屏幕上~
  • 显示的跟记录的不一样的原因是:
    • C语言导出的文本文档中文格式不好看,不同文本编辑器打开都有可能不一样,可能乱码,得把文本编辑器编码方式改为ANSI~
    • 并且很难对齐~
	FILE* pf = fopen("data\\statisticsMonths.txt", "w");
	Consume* psu = consumeCarry();
	Puncher* ppu = puncherCarry();
	Consume* cur = psu;
	Puncher* current = ppu;
	//做成哈希,还有这样做,构建的过程都要O(N^2)

	//这里是下机计费,所以很有可能上机次数多但是收益少
	fprintf(pf, "+-------+---------------+---------------+\n");
	fprintf(pf, "|%-7s|%15s|%15s|\n", "Month", "Hands-on times", "Total turnover");
	fprintf(pf, "+-------+---------------+---------------+\n");
	printf("+-------+--------+-------------+\n");
	printf("|%-7s|%8s|%13s|\n", "年月份", "上机次数", "月总营销额/元");
	printf("+-------+--------+-------------+\n");
  • 后续打印方式还是这样:
    • 在这里插入图片描述
5.5.6 遍历12次打印表格~
for (int i = 0; i < 12; i++) {
	cur = psu; //回到一开始
	current = ppu; //回到一开始
	Date d = dates[i];
	long long min = d.timestamp;
	long long max = min + months[judgeLeapYear(d.year)][d.month] * 86400;
	double sum = 0.0;
	int count = 0; //上机次数

	//消费与上机次数统计
	while (orderNumber != 0 && cur != NULL &&
		workNumber != 0 && current != NULL) {
		if (cur->timeNode < max && cur->timeNode >= min) {
			sum += cur->money;
		}
		if (current->timeNode < max && current->timeNode >= min) {
			count++;
		}
		cur = cur->next;
		current = current->next;
	}
    
	while (orderNumber != 0 && cur != NULL) {
		if (cur->timeNode < max && cur->timeNode >= min) {
			sum += cur->money;
		}
		cur = cur->next;
	}

	while (workNumber != 0 && current != NULL) {
		if (current->timeNode < max && current->timeNode >= min) {
			count++;
		}
		current = current->next;
	}
	fprintf(pf, "|%4d.%2d|%15d|%15.2lf|\n", d.year, d.month, count, sum);
	fprintf(pf, "+-------+---------------+---------------+\n");
	printf("|%4d.%2d|%8d|%13.2lf|\n", d.year, d.month, count, sum);
	printf("+-------+--------+-------------+\n");

}
fclose(pf);
//free掉
exitOutConsume(psu);
exitOutPuncher(ppu);
  • 引入两个局部变量,规定时间区间
    • min为此年月对应的首时间戳
    • max为下个月对应的首时间戳
    • 时间区间为 [min, max) (左闭右开)
  • 引入两个局部变量,记录
    • sum记录月营销额~
    • count记录月上机次数~
  • 开始遍历,一开始两个链表一起遍历,当然是有一个先停下来的可能的
    • 停下来后另一个继续跑
  • 然后,将数据按对应格式写入到文件中 并且打印在屏幕上~

总共遍历个12次

  • 最后,关闭文件(如果不关闭,那么数据就只会在程序的终结才能从缓存区导入文件!)

    • //释放空间
      exitOutConsume(psu);
      exitOutPuncher(ppu);
      

5.6 退出操作

  • 超级简单地退出~
void exitOutStatistics(Card* pc) {
	printf("退出成功\n");
}

5.7 测试~

  • 程序运行正常~

在这里插入图片描述

在这里插入图片描述


文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆

这是我的代码仓库!(在马拉圈的23.3里)马拉圈2023年三月: 大学生代码仓库

全部源码具体位置:网吧管理系统 . 游离态

邮箱:2040484356@qq.com

  • 15
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

s:103

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值