C++实现银行排队系统
网上看到的设计要求:
基本效果已经实现,希望大家帮忙指点指点。
程序中的一些基本模块
程序处理中的一些流程图
程序运行结果如下图:
程序代码如下:
#include <iostream>
#include <string>
#include <random>
#include <ctime>
#include <cassert>
#include <vector>
#include <queue>
#include <thread>
#include <atomic>
#include <chrono>
#include <sstream>
#include <iomanip>
#include <memory>
#include <mutex>
#define DESTIME 100
#define LOG(x) std::cout << #x << std::endl
int getRandomNum(int lnum, int rnum)
{
// 设定随机数种子,使用当前时间
std::random_device rd;
std::mt19937 gen(rd());
// 定义随机数分布范围,这里使用均匀分布
std::uniform_int_distribution<> dis(lnum, rnum); // 例如,生成1到100之间的随机数
// 生成随机数
int random_number = dis(gen);
return random_number;
}
//HHMM时间添加int时间,返回HHMM格式时间
std::string addMinutesToTime(const std::string& timeStr, int minutesToAdd) {
int hours, minutes;
char delimiter;
//解析输入时间
std::istringstream iss(timeStr);
if (!(iss >> std::setw(2) >> hours >> delimiter >> std::setw(2) >> minutes) || delimiter != '-')
{
throw std::invalid_argument("错误的时间格式,请输入HH-MM格式");
}
//将时间间隔加到分钟上
minutes += minutesToAdd;
hours += minutes / 60;
hours %= 24; //24小时取余
minutes %= 60; //60分钟取余
//格式化输出时间
std::ostringstream oss;
oss << std::setw(2) << std::setfill('0') << hours << "-"
<< std::setw(2) << std::setfill('0') << minutes;
return oss.str();
}
//HHMM 格式时间数据转换为int类型数据
int numToHHMMtime(const std::string strTime)
{
int timeNum = 0;
char delimiter;
int hours, minutes;
std::istringstream iss(strTime);
if (!(iss >> std::setw(2) >> hours >> delimiter >> std::setw(2) >> minutes) || delimiter != '-' || hours >= 24 || hours < 0 || minutes < 0 || minutes >= 60)
{
throw std::invalid_argument("错误的时间,请输入正确的HH-MM格式数据");
}
minutes += (hours * 60);
return minutes;
}
//客户类
class Customer {
public:
//设置VIP
void setVIP(bool vip);
//设置进入时间
void setEnterTime(const std::string time);
//设置离开时间
void setLeftTime(const std::string time);
//设置排队号码
void setQueueNumber(unsigned int num);
//生成评分(传入满分,随机生成区间内分数)
int getScore(int score);
//判断是否是VIP
bool isVIP() {
return VIP;
}
//获取进入时间
std::string getEnterTime() {
return enterTime;
}
//获取离开时间
std::string getLeftTime() {
return leftTime;
}
//获取排队号码
unsigned int getQueueNumber() {
return queueNumber;
}
private:
bool VIP = false; //VIP标志
std::string enterTime = " "; //进入时间
std::string leftTime = " "; //离开时间
unsigned int queueNumber = 0; //排队号码
};
//评分类
class Rating {
public:
//设置分数
void setScore(int _num);
//设置评分时间
void setRatingTime(const std::string time);
//获取分数
int getScore() {
return score;
}
//获取评分时间
std::string getRatingTime() {
return ratingTime;
}
private:
std::string ratingTime = " "; //评分时间
int score = 0; //评分分数
};
//窗口类
class CounterWindow {
public:
CounterWindow(int num) :windowNum(num) {};
void setVIP() {
VIPflag = true;
}
//添加客户
void addCustomer(Customer& customer);
//移除客户(这里不考虑客户不排队了的情况)
void removeCustomer() {
//先移除VIP还是先移除普通用户。查询当前正在服务的VIP标志
if (VipWorkFlag)
{
vipCustomerQue.pop();
}
else {
customerQue.pop();
}
}
//添加打分
void addGrade();
//办理业务
void conductBusiness();
//VIP队列人数
int getVipCustomQuenNum()
{
return vipCustomerQue.size();
}
//客户队列人数
int getCustomeQuenNum()
{
return customerQue.size();
}
//设置处理业务耗时
void setWorkTime(int workTime)
{
windowTime = workTime; //设置窗口时间和工作时间相同
}
//停止业务处理
void closeConduct() { //关闭业务
closeFlag = true;
}
//开始业务处理
void openConduct() { //开启业务
closeFlag = false;
}
private:
//生成业务办理耗时
void businessProcessTime();
private:
bool VIPflag = false; //VIP窗口标志
bool VipWorkFlag = false; //VIP正在工作标识符
bool closeFlag = false; //停止营业标志
int windowNum; //窗口号
std::queue<Customer> customerQue; //客户队列
std::queue<Customer> vipCustomerQue; //VIP客户队列
std::vector<Rating> ratingVec; //分数容器
int windowTime = 0; //窗口时间
};
class sysTime {
public:
sysTime(const sysTime&) = delete;
sysTime& operator = (const sysTime&) = delete;
// 获取类的唯一实例
static sysTime& getInstance() {
static std::mutex mtx; // 用于线程安全的互斥锁
std::lock_guard<std::mutex> lock(mtx); // 自动管理锁的获取和释放
static sysTime instance; // 局部静态变量,线程安全地在第一次调用时初始化
return instance;
}
//获取字符串格式时间
std::string get_HHMM_time();
//获取时间
int getTime() {
return timeNum;
}
private:
sysTime() : workThread(&sysTime::timeLoop, this) {
//检查是否启动成功
if (!workThread.joinable()) {
std::cerr << "线程启动失败" << std::endl;
}
std::cout << "时间线启动成功" << std::endl;
}
~sysTime() {
end();
if (workThread.joinable()) {
workThread.join();
}
}
void start() {
timeflag.store(false);
}
void end() {
timeflag.store(true);
}
//循环计时
void timeLoop();
private:
std::atomic<bool> timeflag;
std::thread workThread; //时间计数工作线程
int timeNum = 0; //时间计数器
};
//银行类
class Bank {
public:
//设置营业开始时间
void setOpenTime(std::string time);
//设置营业结束时间
void setCloseTime(std::string time);
//设置窗口数量
void setNumWindow(int num);
//进入客户
void enterCustomer(Customer& customer);
//打开所有线程
void openAllThread();
//等待关闭所有线程
void joinAllThreads();
private:
CounterWindow* getShortWindow(bool isVIP);
private:
int businessOpenTime; //营业开始时间
int businessCloseTime; //营业结束时间
std::vector<CounterWindow> counterWinVec; //窗口维护容器
std::vector<CounterWindow> counterWinVipVec; //VIP窗口维护器
bool openFlag = false; //营业标志
std::vector<std::shared_ptr<std::thread>> threads; //线程组
};
class Menu {
public:
void start();
private:
};
int main()
{
Menu menu;
menu.start();
return 0;
}
void Customer::setVIP(bool vip)
{
this->VIP = vip;
}
void Customer::setEnterTime(const std::string time)
{
this->enterTime = time;
}
void Customer::setLeftTime(const std::string time)
{
this->leftTime = time;
}
void Customer::setQueueNumber(unsigned int num)
{
this->queueNumber = num;
}
int Customer::getScore(int score)
{
//使用断言判断输入分数是否在区间内
assert(score >= 1);
// 设定随机数种子,使用当前时间
std::random_device rd;
std::mt19937 gen(rd());
// 定义随机数分布范围,这里使用均匀分布
std::uniform_int_distribution<> dis(1, score); // 例如,生成1到100之间的随机数
// 生成随机数
int random_number = dis(gen);
return random_number;
}
void Rating::setScore(int _num)
{
this->score = _num;
}
void Rating::setRatingTime(const std::string time)
{
this->ratingTime = time;
}
//添加客户
void CounterWindow::addCustomer(Customer& customer) {
if (customer.isVIP())
{
vipCustomerQue.push(customer);
}
else {
customerQue.push(customer);
}
}
void CounterWindow::addGrade()
{
Rating rate;
Customer customer;
int scoreNum; //分数
if (VipWorkFlag)
{
customer = vipCustomerQue.front();
}
else {
customer = customerQue.front();
}
scoreNum = customer.getScore(10); //10分满分进行评分
rate.setScore(scoreNum);
rate.setRatingTime(addMinutesToTime(sysTime::getInstance().get_HHMM_time(), windowTime));
ratingVec.push_back(rate);
int personNum = customerQue.size() + vipCustomerQue.size();
std::cout << "评分是" << rate.getScore() << " 评分时间是" << rate.getRatingTime() << " 窗口是" << windowNum << "排队人数是" << personNum <<std::endl;
}
void CounterWindow::conductBusiness()
{
while (!closeFlag)
{
int vipSize = vipCustomerQue.size();
if (vipSize > 0) { //判断当前窗口是否有VIP客户正在排队
VipWorkFlag = true; //标志给VIP客户办理业务
}
else if (customerQue.size() > 0) {
VipWorkFlag = false;
}
else {
//两种条件都不满足,程序跳出
continue;
}
//办理业务,这里使用线程睡眠一段时间模拟
businessProcessTime();
//客户评分,生成客户评分
addGrade();
//移除客户,移除队列里的客户
removeCustomer();
}
}
void CounterWindow::businessProcessTime()
{
int num = getRandomNum(5, 20);
windowTime = num;
setWorkTime(num);
num = num * DESTIME;
std::chrono::milliseconds desTime(num);
std::this_thread::sleep_for(desTime);
}
//获取字符串格式时间
std::string sysTime::get_HHMM_time()
{
int hours, minutes;
//输入时间余多少分钟
minutes = timeNum % 60;
//输入时间换算为多少小时
hours = timeNum / 60;
hours %= 24;
std::ostringstream oss;
oss << std::setw(2) << std::setfill('0') << hours << "-"
<< std::setw(2) << std::setfill('0') << minutes;
return oss.str();
}
void sysTime::timeLoop()
{
start();
while (!timeflag.load())
{
//使用chrono库定义时间间隔,这里使用100ms对应1s
std::chrono::milliseconds desTime(DESTIME);
std::this_thread::sleep_for(desTime);
timeNum++;
}
}
void Menu::start()
{
// 使用单例
sysTime& myTime = sysTime::getInstance();
Bank bank;
bank.setOpenTime("09-00");
bank.setCloseTime("17-00");
bank.setNumWindow(4);
while (1)
{
int num = getRandomNum(1, 10); //1分钟到60分钟内进入一位顾客
num = num * DESTIME; //得到转换后的毫秒数
std::chrono::milliseconds sleepTime(num);
std::this_thread::sleep_for(sleepTime);
if (myTime.getTime() >= 1440) { //一天时间为1440分钟
break;
}
Customer customer;
bank.enterCustomer(customer);
std::string strTime = myTime.get_HHMM_time();
}
bank.joinAllThreads();
}
void Bank::setOpenTime(std::string time)
{
businessOpenTime = numToHHMMtime(time);
}
void Bank::setCloseTime(std::string time)
{
businessCloseTime = numToHHMMtime(time);
}
void Bank::setNumWindow(int num)
{
assert(num >= 2);
//设置一个VIP
CounterWindow cWindow(-1);
cWindow.setVIP();
counterWinVipVec.push_back(cWindow);
for (int i = 0; i < num - 1; i++)
{
CounterWindow window(i);
counterWinVec.push_back(window);
}
}
void Bank::enterCustomer(Customer& customer)
{
int nowTime = sysTime::getInstance().getTime();
if (nowTime < businessOpenTime || nowTime > businessCloseTime)
{
std::cout << "当前不在营业时间当前时间是" << sysTime::getInstance().get_HHMM_time() << std::endl;
openFlag = false;
if (openFlag) //如果之前是营业的,关闭窗口营业线程,这就导致程序启动模拟要从营业前的时间段开始
{
openFlag = false;
//关闭窗口线程
joinAllThreads(); //等待所有的线程关闭
}
return;
}
else {
if (!openFlag) //如果之前是不在营业的,开启窗口营业线程
{
openFlag = true;
//开启窗口线程
openAllThread(); //开启所有的线程
}
}
Customer customers;
customers.setEnterTime(sysTime::getInstance().get_HHMM_time());
//查找排队最短窗口
CounterWindow* win = getShortWindow(customers.isVIP());
win->addCustomer(customers); //添加客户到排队窗口
}
void Bank::joinAllThreads()
{
//关闭普通窗口
for (auto it : counterWinVec)
{
it.closeConduct();
}
//关闭VIP窗口
for (auto it : counterWinVipVec)
{
it.closeConduct();
}
for (auto& threadPtr : threads)
{
if (threadPtr->joinable())
{
threadPtr->join();
}
}
}
CounterWindow* Bank::getShortWindow(bool isVIP)
{
CounterWindow* win, * vipWin; //普通窗口和vip窗口
if (!isVIP) //不是VIP分支
{
//获取第一个队列的长度,然后以这个长度为基准
int num = counterWinVec[0].getCustomeQuenNum();
int counter = 0;
for (int i = 0; i < counterWinVec.size(); i++)
{
int tempNum = counterWinVec[i].getCustomeQuenNum();
if (tempNum <= num)
{
num = tempNum;
counter = i;
}
}
win = &counterWinVec[counter];
return win;
}
else { //是VIP分支
int vipNum = counterWinVipVec[0].getVipCustomQuenNum();
int vipCount = 0;
for (int i = 0; i < counterWinVipVec.size(); i++)
{
int tempNum = counterWinVipVec[i].getVipCustomQuenNum();
if (tempNum <= vipNum)
{
vipNum = tempNum;
vipCount = i;
}
}
vipWin = &counterWinVipVec[vipCount]; //获取VIP下标
//获取第一个队列的长度,然后以这个长度为基准
int num = counterWinVec[0].getCustomeQuenNum();
int counter = 0;
for (int i = 0; i < counterWinVec.size(); i++)
{
int tempNum = counterWinVec[i].getVipCustomQuenNum();
if (tempNum < num)
{
num = tempNum;
counter = i;
}
}
win = &counterWinVec[counter];
if (win->getVipCustomQuenNum() >= vipWin->getVipCustomQuenNum())
{
return vipWin;
}
else {
return win;
}
}
}
void Bank::openAllThread()
{
//启动VIP窗口
for (int i = 0; i < counterWinVipVec.size(); i++)
{
auto threadPtr = std::make_shared<std::thread>([this, i]() {
counterWinVipVec[i].openConduct();
counterWinVipVec[i].conductBusiness(); //启动线程开启业务
});
threads.push_back(threadPtr);
}
//启动窗口
for (int i = 0; i < counterWinVec.size(); i++)
{
auto threadPtr = std::make_shared<std::thread>([this, i]() {
counterWinVec[i].openConduct();
counterWinVec[i].conductBusiness(); //启动线程业务
});
threads.push_back(threadPtr);
}
}
时间系统使用while循环实现的,sysTime类使用了懒汉单例模式,在菜单类调用中初始化。
有一些多线程之类的,智能指针、原子变量、没有上锁,因为没有库里面的读写锁,这里考虑到都是读取时间不会改变时间,就没有加锁。这里通过区间内获取随机数的方式取得一个时间间隔,模模拟顾客进入银行办理业务,Menu类中的start()函数中可以设置客流量的频率。
练手的代码,有些地方没有处理好,比如程序一开始的时间转换函数,可以使用饿汉单例模式实现,(工具类不会占用很大资源吧)。总之,希望评论区多多指点。感谢(抱拳)。