《C++primerplus》第12章“队列模拟”程序
这个程序刚开始学有很多难点,个人认为主要有以下三项:
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章“队列模拟”程序的更多相关文章
- 《C++ Primer Plus》12.7 队列模拟 学习笔记
Heather银行打算在Food Heap超市开设一个自动柜员机(ATM).Food Heap超市的管理者担心排队使用ATM的人流会干扰超市的交通,希望限制排队等待的人数.Heather银行希望对顾客 ...
- 《C++primerplus》第12章练习题
做一下倒数两题,都是在队列模拟的程序基础上做点修改测试. 5.找出平均等候时间为1分钟时,每小时到达的客户数为多少(试验时间不少于100小时). 指定队伍最大长度10人,模拟100小时.粗略估计答案在 ...
- Linux就这个范儿 第12章 一个网络一个世界
Linux就这个范儿 第12章 一个网络一个世界 与Linux有缘相识还得从一项开发任务说起.十八年前,我在Nucleus OS上开发无线网桥AP,需要加入STP生成树协议(SpanningTree ...
- 【二代示波器教程】第12章 示波器设计—DAC信号发生器的实现
第12章 示波器设计—DAC信号发生器的实现 本章节为大家讲解二代示波器中信号发生器的实现.这个功能还是比较实用的,方便为二代示波器提供测试信号.实现了正弦波,方波和三角波的频率,幅度以及占 ...
- C++_类和动态内存分配6-复习各种技术及队列模拟
知识点: 队列:是一种抽象的数据类型(Abstract Data Type),可以存储有序的项目序列. 新项目被添加在队尾,并可以删除队首的项目.队列有些像栈.栈是在同一端进行添加和删除.这使得栈是一 ...
- 20191105 《Spring5高级编程》笔记-第12章
第12章 使用Spring远程处理 12.4 在Spring中使用JMS 使用面向消息的中间件(通常成为MQ服务器)是另一种支持应用程序间通信的流行方法.消息队列(MQ)服务器的主要优点在于为应用程序 ...
- 第 12 章 JVM执行引擎
目录 第 12 章 执行引擎 1.执行引擎概述 1.1.执行引擎概述 1.2.执行引擎工作过程 2.Java 代码编译和执行过程 2.1.解释执行和即时编译 2.2.解释器和编译器 3.机器码 指令 ...
- ASM:《X86汇编语言-从实模式到保护模式》第12章:存储器的保护
12章其实是11章的拓展,代码基本不变,就是在保护模式下展开讨论. ★PART1:存储器的保护机制 1. 修改段寄存器的保护 当执行把段选择子传到段寄存器的选择器部分的时候,处理器固件在完成传送之前, ...
- 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则
第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理“胖”接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有“胖”接口.换句话说,类的“胖”接口可以分解成多组 ...
随机推荐
- Python数据可视化之Excel气泡图
最终实现的效果如图: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这三类 ...
- 跟着尚硅谷系统学习Docker-【day07】
day07-20200722 p27.dockerfile案例编写-3 第一步:编写父dockerfile DockerFile_20200722_2 FROM centos RUN yum ...
- Jmeter-添加用户变量
1. 创建用户定义的变量. 2. 3. 4.输出结果:
- SpringMVC-结果跳转方式
结果跳转方式 目录 结果跳转方式 1. ModelAndView 2. ServletAPI 3. SpringMVC实现 1. 无需视图解析器 2. 使用视图解析器 1. ModelAndView ...
- TP6.0中的密码验证逻辑、验证器的使用
目录 1. 场景一:只有一个密码框,并且是可选项,留空不修改密码,不留空则修改密码 2. 场景二:两个密码框,修改密码时有新密码.确认密码,新密码框不为空时,确认密码才验证 1. 场景一:只有一个密码 ...
- 【微信小程序】常用组件及自定义组件
(一) 常用标签 组件你可以理解为传统页面开发时候的各种标签,例如 div span 等等,我这里只说一些常用的,这样就能能搭建出一个基本的页面了,但是如果想要更加美观以及拥有更好的体验,就需要 XS ...
- python之cookie与session
cookie概念 cookie不属于http协议范围,由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生. cookie的工作原理是:由服务器 ...
- Dos拒绝服务Syn-Flood泛洪攻击--Smurf 攻击(一)
Dos拒绝服务利用程序漏洞或一对一资源耗尽的Denial of Service 拒绝服务DDos 分布式拒绝服务 多对一 Syn-Flood泛洪攻击 发送syn包欺骗服务器建立半连接 攻击代码,利用s ...
- three.js学习3_相机相关
Three.Camera Camera是所有相机的抽象基类, 在构建新摄像机时,应始终继承此类. 常见的相机有两种类型: PerspectiveCamera(透视摄像机)或者 Orthographic ...
- Linux实战(13):Centos8 同步时间
前言 以下操作是通过ntpdate命令实现同步 timedatectl set-timezone Asia/Shanghai # 设置时区 rpm -ivh http://mirrors.wlnmp. ...