这个程序刚开始学有很多难点,个人认为主要有以下三项:

1.链表的概念

2.如何表示顾客随机到达的过程

3.程序执行时两类之间的关系,即执行逻辑

关于第一点,书上的图解释得比较清楚了,把“空指针”示意为接地很形象。为了理解链表的概念,需要自己把指针的指向变动慢慢推演一遍。大体来说,就是要理清头部的指针、尾部的指针和中间新增的结点的指针,三者是怎么联系在一起的;每当新增一个结点,各个指针应该如何变化;怎么删除节点等。

在程序中,三者是这么个流程。

首先,一个Queue类对象初始化时,其内部的头部和尾部指针都是空指针。(想象成指向大地)

所谓“节点”是个结构体,内部有两个变量,一个是Customer类对象,名字叫“Item”,另一个是个指针,叫“next”。上面的front和rear,以及这个next,都是指向这种结构体的指针。

然后,当一个新节点出现时,可以这么表示:

成员函数内部使用new关键字分配了一个指向这种结构体的指针,名字叫“add”,那么与此同时一个新的Node结构体也出现了。其内部的Item可以用函数的参数去初始化,next指针设为空。

接着,将add指针存的地址赋给front。

那么front就会指向这个新增的节点,如果它是第一个的话,rear也要指向它,于是演变为如下状态:

同样,再增加一个新节点时,应该更改指针指向,使其演变为如下状态:

add是每次new出来的指针,是用于入队的成员函数enqueue()内部的临时变量,所以会不断变化。

再次新增节点时的状况都是类似的,每一个Node结构体就代表了排队的顾客。

有顾客要出队时,定义一个临时Node结构体指针temp,把front存的地址给它,item也用front指向的队首节点初始化。

那么temp就会指向队首节点。然后使front指向下一位节点,原本的队首节点就移出来了。

接着删除temp指针,就模拟了出队的情况。

当队列空无一人时,front和rear会再次置为空指针。

关于第二点,需要学习rand()函数的使用。定义一个时间种子之后,rand()可以生成 [0,RAND_MAX)之间的随机数,然后后面加一定运算就可以自定义范围,这一点我在程序的注释中作了详细解释。

关于第三点,我画了一张模拟过程的流程图:

下面是程序所有代码的详细注释。

Queue类和Customer类的声明:

//Queue.h -- Declaration of class Queue and Customer
#ifndef _QUEUE_H_
#define _QUEUE_H_ #include <cstdlib> //for rand() and srand() class Customer
{
private:
long arrive;
int processtime; public:
Customer(){arrive = processtime = 0;}
void set(long when);
long when() const {return arrive;}
int ptime() const {return processtime;}
}; typedef Customer Item; //为了方便,为Customer类一个别名Item class Queue
{
private:
enum{Q_SIZE = 10};
struct Node
{
Item item;
struct Node * next;
};
Node * front;
Node * rear;
int items;
const int qsize;
Queue(const Queue & q):qsize(0){} //防止类外面用复制构造函数做初始化操作
Queue & operator = (const Queue & q){return *this;} //因为都是私有函数不可直接调用,所以编译时就会报错 public:
Queue(int qs = Q_SIZE);
~Queue();
bool isempty() const;
bool isfull()const;
int queuecount()const;
bool enqueue(const Item & item);
bool dequeue(Item & item);
}; #endif // _QUEUE_H_

对应方法的实现:

//Queue.cpp -- Methods of class Queue and Customer
#include "Queue.h" Queue::Queue(int qs) : qsize(qs) //创建对象时就用qs初始化qsize
{
front = rear = NULL; //队首队尾的指针都设为空
items = 0;
} bool Queue::isempty() const
{
return items == 0;
} bool Queue::isfull() const
{
return items == qsize;
} int Queue::queuecount()const
{
return items;
} bool Queue::enqueue(const Item & item)
{
if(isfull()) //判断队列是否已满
return false;
Node * add = new Node; //新增一个节点(指针)
add->item = item; //节点的初始化
add->next = NULL; //节点的下位指针设为空,为后面新增节点准备
items++; //队列人数+1
if(front == NULL) //判断队列是否为空
front = add; //是,就把add指针存的地址赋给front指针,front和add一样指向新增的节点
else
rear ->next = add; //否,就把add指针存的地址,赋给rear指向的节点里的下位指针,即原本队列最后一个节点里的指针指向了新增的节点
rear = add; //rear和add一样指向新增的节点
return true;
} bool Queue::dequeue(Item & item)
{
if(front == NULL) //判断队列是否为空
return false;
item = front ->item;
items--;
Node * temp = front; //临时指针,用来存储原本front存储的地址(也就是即将出队的节点的地址)
front = front->next; //原本的front指针指向即将出队的节点的下一个节点
delete temp; //删除临时指针,原本分配给该节点的内存不再被使用,即该节点被删除
if(items == 0)
rear = NULL; //如果该节点删除后队列就空了,那么rear谁也不指向
return true;
} Queue::~Queue()
{
Node * temp;
while(front != NULL)
{
temp = front;
front = front->next;
delete temp;
}
} void Customer::set(long when)
{
processtime = std::rand()%3 + 1; //服务时间为[1,3)中一个随机值(分钟)
arrive = when; //记录其到达的时间
}

主程序。加了个大循环来不断测试。

//Bank.cpp -- Using Class

#include "Queue.h"
#include <iostream>
#include <ctime> //for time() const int MIN_PER_HR = 60; bool newcustomer(double x); int main()
{
using std::cin;
using std::cout;
using std::endl;
using std::ios_base; std::srand(std::time(0)); //生成随机数时间种子 int flag = 1; //用于保持循环
while(flag)
{
cout<<"Case Study: Bank of Heather Automatic Teller\n";
cout<<"Enter maximum size of queue: ";
int qs;
cin>>qs; //指定排队的最大人数,不指定默认为10
Queue line(qs); //初始化Queue类对象line cout<<"Enter the number of simulation hours: ";
int hours;
cin>>hours; //指定想要模拟的小时数 long cyclelimit = MIN_PER_HR * hours; //将小时转化为分钟,因为后面每分钟为一个循环周期 cout<<"Enter the average number of customers per hour: ";
double perhour;
cin>>perhour; //指定一小时平均有多少顾客
double min_per_cust;
min_per_cust = MIN_PER_HR / perhour; //换算平均下来每多少分钟到达一位顾客 Item temp; //一个临时顾客对象,用于代表每个循坏周期服务的顾客
long turnaways = 0;
long customers = 0;
long served = 0;
long sum_line = 0;
int wait_time = 0;
long line_wait = 0; //开始模拟
for(int cycle = 0;cycle < cyclelimit;cycle++)
{
if(newcustomer(min_per_cust))
{
if(line.isfull())
turnaways ++; //因为队伍已满而离去的人+1
else
{
customers ++; //到达的顾客数+1
temp.set(cycle); //为这位顾客生成随机的服务时间(1-3分钟),并记录其到达的时间
line.enqueue(temp); //顾客入队,更新内部所有指针
}
} /* wait_time是每位顾客服务时间的计数器,可以这么想象:*/
/* 每有一位顾客到达了队首,就开始掐表倒计时(1-3分钟随机)*/
/* 时间一到0,表示服务完毕,下一个人补上,重新倒计时,如此重复 */
if(wait_time <=0 && !line.isempty()) //上一位服务完毕且队伍里还有人
{
line.dequeue(temp); //下一位出队,开始服务
wait_time = temp.ptime(); //置计数器为该位顾客的服务时间
line_wait += cycle - temp.when(); //用现在的时间减去该顾客的到达时间,所有结果累加(即总等待时间)
served ++; //已服务的人数+1
}
if(wait_time>0)
wait_time--; //上一位服务未完毕,保持当前状态,时间-1
sum_line += line.queuecount(); //数一下现在队伍有多少人,把每一分钟的结果都累加起来
} //报告结果
if(customers > 0)
{
cout<<"customers accepted: "<<customers<<endl; //总到来的顾客数
cout<<" customers served: "<<served<<endl; //总服务的顾客数
cout<<" turnaways: "<<turnaways<<endl; //总离去的顾客数(到来却因队伍满了而离去)
cout<<"average queue size: ";
cout.precision(2); //设定输出的有效数字为两位
cout.setf(ios_base::fixed,ios_base::floatfield); //输出浮点数为定点模式,结合上句的效果就是输出到小数点后两位
cout<<(double)sum_line/cyclelimit<<endl; //平均每分钟的排队人数
cout<<" average wait time: "<<(double)line_wait/served<<" minutes\n"; //平均每个人的等待时间
}
else
cout<<"No customers!\n";
cout<<"Done!\n"; cout<<"Enter 1 to simulate again,0 to quit: ";
cin>>flag; //输入0以终止循环
} return 0;
} /* 判断顾客是否到达的函数 */
/* RAND_MAX是能够生成的最大随机数,rand()会生成[0,RAND_MAX)之间的随机数 */
/* 因此rand()/RAND_MAX会生成[0,1)之间的随机数,再乘以x就是[0,x)之间的随机数 */
/* 加上小于1的判断,生成的数会有1/x的概率小于1,而小于1就表示这一分钟内有顾客到了 */
bool newcustomer(double x)
{
return (std::rand() * x/RAND_MAX < 1);
}

《C++primerplus》第12章“队列模拟”程序的更多相关文章

  1. 《C++ Primer Plus》12.7 队列模拟 学习笔记

    Heather银行打算在Food Heap超市开设一个自动柜员机(ATM).Food Heap超市的管理者担心排队使用ATM的人流会干扰超市的交通,希望限制排队等待的人数.Heather银行希望对顾客 ...

  2. 《C++primerplus》第12章练习题

    做一下倒数两题,都是在队列模拟的程序基础上做点修改测试. 5.找出平均等候时间为1分钟时,每小时到达的客户数为多少(试验时间不少于100小时). 指定队伍最大长度10人,模拟100小时.粗略估计答案在 ...

  3. Linux就这个范儿 第12章 一个网络一个世界

    Linux就这个范儿 第12章 一个网络一个世界 与Linux有缘相识还得从一项开发任务说起.十八年前,我在Nucleus  OS上开发无线网桥AP,需要加入STP生成树协议(SpanningTree ...

  4. 【二代示波器教程】第12章 示波器设计—DAC信号发生器的实现

    第12章      示波器设计—DAC信号发生器的实现 本章节为大家讲解二代示波器中信号发生器的实现.这个功能还是比较实用的,方便为二代示波器提供测试信号.实现了正弦波,方波和三角波的频率,幅度以及占 ...

  5. C++_类和动态内存分配6-复习各种技术及队列模拟

    知识点: 队列:是一种抽象的数据类型(Abstract Data Type),可以存储有序的项目序列. 新项目被添加在队尾,并可以删除队首的项目.队列有些像栈.栈是在同一端进行添加和删除.这使得栈是一 ...

  6. 20191105 《Spring5高级编程》笔记-第12章

    第12章 使用Spring远程处理 12.4 在Spring中使用JMS 使用面向消息的中间件(通常成为MQ服务器)是另一种支持应用程序间通信的流行方法.消息队列(MQ)服务器的主要优点在于为应用程序 ...

  7. 第 12 章 JVM执行引擎

    目录 第 12 章 执行引擎 1.执行引擎概述 1.1.执行引擎概述 1.2.执行引擎工作过程 2.Java 代码编译和执行过程 2.1.解释执行和即时编译 2.2.解释器和编译器 3.机器码 指令 ...

  8. ASM:《X86汇编语言-从实模式到保护模式》第12章:存储器的保护

    12章其实是11章的拓展,代码基本不变,就是在保护模式下展开讨论. ★PART1:存储器的保护机制 1. 修改段寄存器的保护 当执行把段选择子传到段寄存器的选择器部分的时候,处理器固件在完成传送之前, ...

  9. 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则

    第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理“胖”接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有“胖”接口.换句话说,类的“胖”接口可以分解成多组 ...

随机推荐

  1. C# .NET容器的源码

    这里有List<T>的源码http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs

  2. 用IDEA一年了,终于敢说自己会用了

    作为Java老兵,我也是用了很多年的eclipse,为了与时俱进,于是切换到了IDEA.刚开始的时候感觉很不适应,感觉这玩意儿不如eclipse好用,影响工作效率,于是又换回eclipse. 但是很多 ...

  3. 被Spring坑了一把,查看源码终于解决了DataFlow部署K8s应用的问题

    1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! Docker & Kubernetes相关文章:容器技术 基于各种原因,团队的Kubernetes被加了限制,必须在 ...

  4. Leetcode 全排列专题(更新ing)

    总览 涉及到的题目有 题号 名字 难度 Leetcode 60 第k个排列 中等 Leetcode 46 全排列 中等 待更新...... Leetcode 46 全排列 题目 基础题 给定一个 没有 ...

  5. Oracle中树形查询使用方法

    树形查询一般用于上下级场合,使用的特殊sql语法包括level,prior,start with,connect by等,下面将就实例来说明其用法. 表定义: create table tb_hier ...

  6. PHP 安装 扩展时 抛出 /usr/local/Cellar/php@7.1/7.1.25/pecl 异常解决

    liugx@MacBook-Pro  ~/work/php/ext_source/php-xhprof-extension   master  make installmkdir: /usr/ ...

  7. sql如何查询不包含中文

    SELECT * FROM dbo.表名 WHERE 字段名 NOT LIKE '%[吖-座]%'

  8. selenium做UI自动化时,模拟鼠标各种操作的ActionChains的用法

    1.selenium做自动化的时候,需要模拟鼠标进行单击.双击.右键.拖拽等操作,selenium提供了ActionChains类来进行处理. 2.执行原理:当你调用ActionChains的方法时, ...

  9. 漏洞扫描工具acunetix12会遇到的问题

    1.如果安装好之后,打开工具时显示无法访问,首先去看任务管理器当中,acunetix的服务是否启动了 2.如果忘记了账号密码,可以在安装目录下,双击ChangePassword.exe进行重置密码

  10. HTML全局属性(global attribute)有哪些(包含H5)

    1.accesskey:提供了为当前元素生成键盘快捷键的提示.这个属性由空格分隔的字符列表组成.浏览器应该使用在计算机键盘布局上存在的第一个. 2.autocapitalize:控制用户的文本输入是否 ...