武汉理工大学计算机基础与编程综合实验——网吧计费管理系统第一个版本


前言

第一次实验:界面的实现。
第二次实验:用数组(或Vector)实现开卡、查询卡及注销卡的功能。
第三次实验:在第2次实验的基础上,实现卡信息的更改能永久保存。
前面3次实验内容作为一个版本保存下来。
文件 <-> 数组(Vector)<-> 系统的操作

系统需求分析

基本功能结构

在这里插入图片描述

卡管理:新增卡、查询卡、注销卡

(1)新增卡:根据卡号、密码、开卡金额,新增一张新卡。卡号必须唯一。可以增加一些特色功能,如卡号必须都是数字,实际中卡号的长度都是固定的;可以对密码的复杂度做一些规定,如必须是数字和字母的组合等。
(2)查询卡:根据卡号,查询卡信息,包括卡号,卡状态,余额,累计使用,使用次数,最后使用时间。可以增加一些特色功能,如查询某段时间内开的卡。
(3)注销卡:将一张不再使用而且未注销的卡注销,注销后的卡不能在进行任何操作,记载注销的时间。(在实现上,一般是做一个删除标志,注销的卡不能使用,但是可以查询。)

计费标准管理:新增标准、查询标准、删除标准、修改标准。

在实现中,可以将用户进行分类,不同类别的用户使用不同的标准。注意:计费标准可以修改保存的,修改后就意味着应该按新的标准计费,这在技术实现上应该有一个文件专门存储计费标准。

计费管理:上机、下机。

(1)上机:根据输入卡号和密码,进行上机,并开始计费。在实现中要注意,已经上机的卡不能重复上机,卡中余额不够的也不能上机。
(2)下机:根据输入的卡号和密码,判断该卡是否正在上机。并对上机卡进行下机操作,计算消费金额。注意,消费金额要符合实际,如0.1234元就不符合实际;如按秒计费等。

费用管理:充值、退费。

(1)充值:可以对卡进行充值,记载每次充值的时间和金额,充值后余额累加。注意,不能充值负数,如 -10元,也可以控制充值上限。
(2)退费:不再进行消费时,可以根据卡内余额申请退费。注意:退费不能超过余额。

查询统计:查询消费记录、统计总营业额、统计月营业额。

(1)消费记录查询:根据卡号和时间段,查询该卡在该时间段内的消费记录。
(2)统计总营业额:根据时间段统计这段时间内的总营业额。
(3)统计月营业额:统计一年中每个月的营业额。
可以将统计信息显示在显示器上的同时,输出到文本文件中。

权限管理:添加用户、删除用户、设置权限等。

管理使用这个系统的用户。

登录和退出

根据输入的账号和密码,确定用户是否有权限登录系统。


以下是本篇文章正文内容,下面案例可供参考

一、注意事项

VS的版本问题

在这里插入图片描述
VS 2019版不需要加 #include “stdafx.h”

VS开发环境中,使用scanf()函数引发的警告:

在这里插入图片描述

键盘缓冲区问题

过程:键盘上输入的数据会以ASCII形式存储到键盘缓冲区,程序需要接收数据时,从键盘缓冲区读取。

清除键盘缓冲区的方法

C:fflush(stdin);

C++:
cin.clear(); 
cin.sync();

cin.ignore(100, '\n');

丢弃字符直到遇到’\n’(包含’\n’),如果丢弃了100个字符还没遇到字符’\n’,那也停止丢弃字符,所以100是丢弃的最大字符数。

while (getchar() != '\n');

条件编译

作用1:防止头文件的重复include
1)为什么.cpp和.h要分开?
函数只能定义一次,可以声明多次。
2)在一个project中,全局变量、常量一般用单独的头文件统一定义,大家都可以include,为了防止多次include,导致变量重复定义,使用条件编译。
3)条件编译中,为了唯一性,一般用“文件名”做标识符。

作用2:关闭调试语句
在这里插入图片描述
在这个例子中,把a==0 误写成 a=0,在调试中,需要先输出a观察a的值,
cout << "a is : " << a << endl;
但是实际要求中不能有输出a的值,正常我们是调试完后,需要删除这条语句。如果程序很复杂,可能会增加很多这样的语句,这样后续的删除有可能会漏掉一些语句或者错删语句,为了减少这样的错误,可以使用条件编译,它就像一个开关,通过修改DEBUG值为1或0,就可以控制这些语句是否执行。

时间处理

修改时间参数的原因:

struct tm
{
    
    
	int	tm_sec;		/* Seconds: 0-59 (K&R says 0-61?) */
	int	tm_min;		/* Minutes: 0-59 */
	int	tm_hour;		/* Hours since midnight: 0-23 */
	int	tm_mday;	/* Day of the month: 1-31 */
	int	tm_mon;		/* Months *since* january: 0-11 */
	int	tm_year;		/* Years since 1900 */
	int	tm_wday;	/* Days since Sunday (0-6) */
	int	tm_yday;		/* Days since Jan. 1: 0-365 */
	int	tm_isdst;		/* +1 Daylight Savings Time, 0 No DST,
				 * -1 don't know */
};

在这个结构中我们用到了年、月、日等,因为
年份为从1900年开始,所以存储时,需将你输入的年份-1900
月份为0~11,所以存储时,需将你输入的月份-1
如:你输入2020年3月,则:
tm_year -= 1900;
tm1.tm_mon -= 1;
如果涉及到时分秒等,也要做相关的处理。

C++ 中的string

使用C++编程,由于我们需要以结构体或者对象为单元写入到文件中,而string在结构体或者类中存储的是指针,所以在结构体或者类中,不能使用string去定义name和password等字符串数据。应该使用字符数组,将string转换成字符数组。

二、具体实现

menu.cpp

outputMenu()

菜单界面

cout << "--------计费系统菜单--------" << endl << endl;
cout << "1.添加卡" << endl;
cout << "2.查询卡" << endl;
cout << "3.上机" << endl;
cout << "4.下机" << endl;
cout << "5.充值" << endl;
cout << "6.退费" << endl;
cout << "7.查询统计" << endl;
cout << "8.注销卡" << endl;
cout << "0.退出" << endl << endl;
cout << "请选择菜单项编号(0~8):";
selection = 'a'; // 初始化选择的菜单项编号为'a'

inputCardNo()

bool inputCardNo(string& strNo)
{
    
    
while(true)
{
    
    
cout << "请输入卡号(长度为1~17):";
cin >> strNo;

cin.clear();
cin.sync();
if(strNo.length() >= 17) // 卡号长度是否正确
{
    
    
cout << "卡号长度超过最大值!" << endl;
char ch='M';
while(ch != 'N' && ch != 'Y')
{
    
    
cout << "重新输入吗?(y/n)" ;
cin >> ch;
ch = toupper(ch);
cin.clear();
cin.sync();
}
if(ch == 'N')
{
    
    
return false;
}
}
else
{
    
    
break;
}
}
}

inputCardPwd()、inputCardBalance()

参照输入卡号功能的编写过程,实现输入密码函数和输入金额函数,函数形式如下:
bool inputCardPwd(string& strPwd){ …… }
bool inputCardBalance(float& fBalance) { …… }

inputNoPwd()、inputNoPwdBalance()

函数 inputNoPwd(),它调用inputCardNo()和 inputCardPwd()实现输入卡号和密码;函数 inputNoPwdBalance(),它调用 inputCardNo()、inputCardPwd()和 inputCardBalance()实现输入卡号、密码和金额。

bool inputNoPwd(string& strNo, string& strPwd)
{
    
    
 if(!inputCardNo(strNo))
 {
    
    
 return false;
 }
 if(!inputCardPwd(strPwd))
 {
    
    
 return false;
 }
 return true;
}
bool inputNoPwdBalance(string& strNo, string& strPwd, float& fBalance)
{
    
    
 if(!inputCardNo(strNo))
 {
    
    
 return false;
 }
 if(!inputCardPwd(strPwd))
 {
    
    
 return false;
 }
 if(!inputCardBalance(fBalance))
 {
    
    
 return false;
 }
return true;
}

tool.cpp

存放与时间处理相关的函数

#include <time.h> // 包含时间类型头文件
#include <stdio.h> // 包含sscanf()函数头文件
void timeToString(time_t t, char* pBuf)
{
    
    
struct tm * timeinfo;
timeinfo = localtime(&t);
strftime(pBuf, 20, "%Y-%m-%d %H:%M",timeinfo);
}
time_t stringToTime(char* pTime)
{
    
    
struct tm tm1;
 time_t time1;
sscanf(pTime, "%d-%d-%d %d:%d",&tm1.tm_year, &tm1.tm_mon,
 &tm1.tm_mday , &tm1.tm_hour, &tm1.tm_min);
 tm1.tm_year -= 1900; // 年份为从1900年开始
 tm1.tm_mon -= 1; // 月份为0~11
tm1.tm_sec = 0;
tm1.tm_isdst = -1;
 time1 = mktime(&tm1);
 return time1;
}

size_t strftime( char *str, size_t maxsize, const char *fmt, struct tm *time )
函数功能:将时间格式化,或者说格式化一个时间字符串。即将时间 time 格式化为 fmt 样式的字符串。

fmt:
%Y,用 CCYY 表示的年(如:2004)
%m,月份 (1-12)
%d,月中的第几天(1-31)
%H,小时, 24 小时格式 (0-23)
%M,分钟(0-59)

int sscanf (const char *str,const char * format,…) 函数
功能:将字符串 str 根据参数 format 字符串来转换并格式化数据。格式转换形式请参
考 scanf()。转换后的结果存于对应的参数内。

service.cpp

各功能主函数

开卡 addCard()
查询 queryCard()
上机 shangJi()
下机 xiaJi()
充值 addMoney()
退费 refundMoney()
销卡 annul()
统计 displayCard()

addCard()

只有输入正确格式的卡号才能建立新卡,因此我们的程序必须保证卡号格式正确。如果不正确就直接退出回到菜单模块,不用再去做后面的事情。用函数 inputCardNo()实现输入卡号的功能。
inputCardNo()需要将“卡号格式是否正确”和“正确卡号”这两种信息返回给调用它的函数,返回值只能带回一种信息,因此需要通过参数传回另一种信息。
通过参数将数据传回给调用它的函数有两种方式:指针参数和引用参数。本示例中采用引用参数的形式。函数格式如下:
bool inputCardNo(string& strNo)
返回值表示卡号格式是否正确,参数 strNo 存储输入的卡号。

使用容器 vector 存储所有的上机卡,将 vector作为参数传给所有要用到 vector 的函数。由于在一些函数中会对这个 vector 进行修改,为了能将修改的结果返回给调用它的函数。

void addCard(vector<Card> &vec)
{
    
    
string strNo;
 string strPwd;
 float fBalance;
if(inputNoPwdBalance(strNo, strPwd, fBalance))
{
    
    
int nResult;
nResult = addNewCard(strNo, strPwd, fBalance, vec);
if(nResult == FINDCARD)
{
    
    
cout << "此卡号已使用,添加卡失败!" << endl;
}
else if(nResult == SUCCESS)
{
    
    
cout << "卡添加卡成功!" << endl;
}
else
{
    
    
cout << "其它情况" << endl;
}
}
else
{
    
    
cout << "输入的信息格式不正确,添加卡失败!" << endl;
}
}

displayCard()

displayCard()函数去遍历 vector,将每次添加新卡后的结果,即所有卡显示出来。查询卡对应的是 queryCard()函数,因为是查询 vector 类型变量 vec 中的卡,因此需要将变量 vec 作为参数传给 queryCard()函数。再在 queryCard()函数中调用 displayCard()函数。

void displayCard(vector<Card>& vec)
{
    
    
 vector<Card>::iterator it;
cout << "卡号\t状态\t余额\t累计使用\t使用次数\t上次使用时间\n";
 for(it=vec.begin();it!=vec.end();it++)
 {
    
    
 char aLastTime[TIMELENGTH] = {
    
    0};
 timeToString(it->tLast, aLastTime);
cout << it->aName << "\t";
 if(it->nStatus == USING)
 cout << "上机\t";
 else if(it->nStatus == UNUSE)
 cout << "未上机\t";
 else if(it->nStatus == INVALID)
 cout << "注销\t";
 else
 cout << "错误\t";
 cout << it->fBalance << "\t";
 cout << it->fTotalUse << "\t\t";
cout << it->nUseCount << "\t\t";
cout << aLastTime << endl;
 }
}

model.h

上机卡中包含了很多信息,因此我们使用结构体将卡的相关信息组织在一起。

#include <time.h>
// 定义卡信息结构体
struct Card
{
    
    
char aName[18]; // 卡号
char aPwd[8]; // 密码
int nStatus; // 卡状态(UNUSE-未上机;USING-正在上机;INVALID-已注销)
time_t tStart; // 开卡时间,long
float fTotalUse; // 累计金额
time_t tLast; // 最后使用时间,long
int nUseCount; // 使用次数
float fBalance; // 余额
};

VectorCard.cpp

addNewCard()

addNewCard()函数先查找此卡号在 vector 中是否存在。如果存在则不能添加卡;如果不存在,则用卡号,密码,金额和当前时间生成一张卡,再把卡放到容器 vector 中。返回值代表了不同的状态,用于在调用函数 addCard()中显示不同的提示信息。

int addNewCard(string strNo, string strPwd, float fBalance, vector<Card>& vec)
{
    
    
int nCardIndex = 0;
if(cardIsExist(strNo,nCardIndex,vec) != NULL)
return FINDCARD;
 // 构造一张上机卡
 Card card;
 strcpy(card.aName, strNo.c_str());
 strcpy(card.aPwd, strPwd.c_str());
 card.fBalance = fBalance;
 card.fTotalUse = card.fBalance; // 添加新卡时,累计金额等于开卡金额
card.nStatus = UNUSE; // 卡状态
card.nUseCount = 0; // 使用次数
card.tStart = card.tLast = time(NULL); // 开卡时间,最后使用时间
vec.push_back(card);
 return SUCCESS;
}

cardIsExist ()

函数 Card* cardIsExist(string strNo, int &nCardIndex , vector& vec)通
过遍历 vector 来查找卡号 strNo 是否被使用。如果使用,则返回该卡的地址,同时通过参数 nCardIndex 返回是第几张卡,此数据在后面模块要使用;如果没使用,则返回 NULL,表示可以生成新卡。

Card* cardIsExist(string strNo, int &nCardIndex, vector<Card>& vec)
{
    
    
 vector<Card>::iterator it;
 nCardIndex = 0;
 for(it=vec.begin();it!=vec.end();it++)
 {
    
    
 if(strcmp(it->aName,strNo.c_str())==0)
 {
    
    
 return &(*it);
 }
 nCardIndex++;
 }
 return NULL;
}

global.h

卡的状态分为上机,未使用和已销卡等,可以用 3 个数字表示这 3 种状态。

#ifndef GLOBAL_H_INCLUDED
#define GLOBAL_H_INCLUDED
#include <string>
using namespace std;
const string CARDPATH = "card.dat"; // 卡信息记录文件
const string BILLINGPATH = "billing.dat"; // 计费信息记录文件
const string MONEYPATH = "money.dat"; // 充值退费记录文件
const int TIMELENGTH = 20; // 时间字符数组长度
const int SUCCESS = 0; // 操作成功
const int FINDCARD = 1; // 找到卡
const int NOFINDCARD = 2; // 没找到卡
const int NOMATCH = 3; // 卡号密码不匹配
const int ENOUGHMONEY = 4; // 卡余额不足
const int NOFARE = 5; // 没有找到计费信息
const int USING = 6; // 卡状态:正在上机
const int UNUSE = 7; // 卡状态:没有上机
const int INVALID = 8; // 卡状态:已经注销
const int NOSETTLEMENT = 0; // 上机已结算
const int YESSETTLEMENT = 1; // 上机未结算
const int UNIT = 15; // 最小收费单元(分钟)
const float CHARGE = 0.5f; // 每个计费单元收费(元)
#endif // GLOBAL_H_INCLUDED

文件存储管理

saveCard()

每次添加新上机卡时,都是在文件末尾追加这张卡,因此只需在打开文件时使用参数ios::app,即采用追加模式写文件即可。同时为了节省存储空间,我们都使用二进制方式打开和读写文件。

bool saveCard(const Card* pCard, const string pPath)
{
    
    
ofstream ofile(pPath, ios::binary | ios::app);
 ofile.write((char*)pCard, sizeof(Card));
 ofile.close();
return true;
}

CardVectorInit()

在系统启动时,需要从文件中恢复所有上机卡信息到容器 vector 中,因此我们可以编写一个 CardVectorInit()函数实现此功能,在 main()函数中,定义容器 vector 后,调用此函数对 vector 进行初始化,即在 main()函数中定义对象 cardVec 后执行恢复操作。

void CardVectorInit(const string filename, vector<Card>& vec)
{
    
    
ifstream ifile(filename);
 Card card;
 if(!ifile.is_open())
 {
    
    
 return;
 }
 while(1)
 {
    
    
 ifile.read((char*)&card, sizeof(Card));
 if(ifile.eof())
 {
    
    
 break;
 }
 vec.push_back(card);
 }
}

总结

实验内容较多较杂,有不清楚的可参考以下链接。
链接:冲冲冲~
提取码:3jl8
复制这段内容后打开百度网盘手机App,操作更方便哦

猜你喜欢

转载自blog.csdn.net/mo_zhe/article/details/112919033
今日推荐