项目实战笔记 | C++ 事件驱动模型实现银行排队服务2 代码实现

Oop编程思想,概率编程,驱动的设计,蒙特卡洛方法,CPU资源争夺模型

我们这里使用链表实现队列,这里实现队列的类,使用带头结点的链表实现

这个项目我在最后犯了一个很大的错误,我的程序总是编译不通过,提示expect class-name before ‘class’,查阅了大量资料还是没有找到解决方法,后来发现是#endif 的后面有一个空行,空行里面有几个空格键,所以以后在编程的时候当编译器提示你expect class-name before ‘class’,或者是expect ; before{,检查一下#ifndef #define #endif是否有问题,#endif后面是否有空行,空行里面是否有多余的字符

1)通过这个项目的学习,我学会了几个重要的思想,首先就是对于头文件而言,最好加上保险头,也就是防止头文件重复声明的一个数据结构,其声明方法为

#ifndef A

#define A

#endif

有了这样的数据结构之后,就算头文件被重复引用,也不会发生重复声明的错误

2)然后就是模板类的使用,在自己定义的queue中最好使用temp<typenameT>来定义类型,这样就能够通过queue<T>的调用方法来创建指定类型的队列

3)有关c++语言随机数的使用方法,比如,srand生成0~RAND_MAX大小的随机数,它可以通过double a = rand()/RAND_MAX的方法来生成[0~1)的随机数,要使用srand((unsigned)time(NULL))方法来重置随机数种子

4)还有就是对于事件驱动的程序的设计方法是如何使用的

对于函数而言,可以使用默认行参,比如

Int a(int b=1,int c=2)

那么调用a可以使用

a(),a(1),a(1,1)这三种方法,依次不传入参数,传入一个参数,传入两个参数

 5exit(-1)表示非正常退出,对于操作系统而言,exit(0)是正常退出,而-1是非正常退出,通过返回值的不同我们就能得知是否是正常退出

6)这个项目为了取得平均顾客停留时间和平均单位时间顾客数,使用了十万次模拟的数据总和取平均数,这样的思想能够是的结果更精确,以后在做项目的时候记得参考

7)这个项目的思路就是,这个项目有三个队列,一个事件队列,一个顾客队列,一个窗口队列,首先初始化一个到达事件,调用到达函数,对于每一个到达事件而言,随机生成下一个到达事件,事件列表里面任何时候最多都只有一个到达事件,到达函数的功能是,当发现有空闲窗口的时候,将顾客送入空闲窗口,然后生成一个离开事件否则将顾客送入等待窗口,

当处理离开事件的时候,调用离开函数,离开函数的功能是当顾客等待队列里面有顾客在等待的时候,将等待的顾客送入空闲窗口,并且生成一个离开函数,当等待队列里面没有顾客在等待的时候,将当前窗口设置为空闲状态。

Queue.hpp:

//对于自己写的队列类或者是其他而言,队列元素最好是模板类,类似于T

/*****queue.hpp*****/

//

// Queue.hpp

// QueueSystem

//

 

// 构造一个带头结点的链式队列,节点位于队列尾部

Queue() {

    this->front = new T;

    // 如果内存申请失败,则不应该继续运行程序了

    if (!this->front) {

        exit(-1);//判断某个指针是否申请成功,或者是否为空,可以使用if语句直接判断,exit(-1),对于操作系统而言,0为正常退出,非0为非正常退出

    }

 

    // 初始化节点

    this->front->next = NULL;

    this->rear = this->front;

}

// 销毁一个队列时候需要释放节点中申请的内存

~Queue() {

    // 清空当前队列中的元素

    this->clearQueue();

 

    // 再删除头结点

    delete this->front;

}

T* enqueue(T&node) {

    T *new_node = new T;

    if (!new_node) {

        exit(-1);

    }

    *new_node = node;

    this->rear->next = new_node;

    this->rear = new_node;

    return this->front;

}

// 出队时,从队头元素出队

T* dequeue() {

    // 如果队列空,则返回空指针

    if (!this->front->next) {

        return NULL;

    }

 

    T *temp_node;

    temp_node = this->front->next;

    this->front->next =temp_node->next;

 

    // 如果队列中只有一个节点,那么记得将队尾节点指针置为头结点

    if (this->rear == temp_node) {

        this->rear = this->front;

    }

    return temp_node;

}

 

T*orderEnqueue(Event &event) {

    Event* temp = new Event;

    if (!temp) {

        exit(-1);

    }

    *temp = event;

 

    // 如果这个列表里没有事件, 则把 temp 事件插入

    if (!this->front->next) {

        this->enqueue(*temp);

        return this->front;

    }

 

    // 按时间顺序插入

    Event *temp_event_list = this->front;

 

    // 如果有下一个事件,且下一个事件的发生时间小于要插入的时间的时间,则继续将指针后移

    while (temp_event_list->next &&temp_event_list->next->occur_time < event.occur_time) {

        temp_event_list =temp_event_list->next;

    }

 

    // 将事件插入到队列中

    temp->next = temp_event_list->next;

    temp_event_list->next = temp;

 

    // 返回队列头指针

    return this->front;

}

int  length() {

    T *temp_node;

    temp_node = this->front->next;

    int length = 0;

    while (temp_node) {

        temp_node = temp_node->next;

        ++length;

    }

    return length;

}

voidclearQueue() {

    T *temp_node;

 

    while (this->front->next) {

        temp_node = this->front->next;

        this->front->next =temp_node->next;

        delete temp_node;

    }

 

    this->front->next = NULL;//不知为何这里要再次设为一个NULL,所以以后在销毁某个指针指向的指针的时候记得把前一个指针设为NULL

    this->rear = this->front;

}

//注意,清空队列的逻辑应该注意,我们在依次删除完队列中元素的内存时,应该将头和尾节点进行复位,否则会出现很严重的内存泄露问题,因为我们的入队是通过尾指针实现的。

//本项目对于内存使用这样一个理念,谁申请,谁释放

 

接下来是QueueSystem的设计:

//

// QueueSystem.cpp

// QueueSystem

//

/*****QueueSystem.cpp*****/

QueueSystem::QueueSystem(inttotal_service_time, int window_num):

   total_service_time(total_service_time),

   window_num(window_num),

   total_customer_stay_time(0),

   total_customer_num(0) {

   // 创建服务窗口

   this->windows = new ServiceWindow[window_num];

}

QueueSystem::~QueueSystem() {

   delete[] windows;

}

voidQueueSystem::simulate(int simulate_num) {

    double sum = 0;

    for (int i = 0; i != simulate_num; ++i) {

        // 每一遍运行,我们都要增加在这一次模拟中,顾客逗留了多久

//在这里sum得到的是这100000次模拟顾客平均停留时间,这里加了100000

        sum += run();

    }

    // 计算平均逗留时间

avg_stay_time = (double)sum / simulate_num;

//这里的double也是没有必要的,不过这样更规范

    // total_customer_num记录的是这100000次模拟中的顾客到来的总数

    avg_customers = (double)total_customer_num/ (total_service_time*simulate_num);

}

 

voidQueueSystem::init() {

    // 第一个事件肯定是到达事件, 使用默认构造

    Event *event = new Event;

    current_event = event;

}

// 系统开始运行,不断消耗事件表,当消耗完成时结束运行

doubleQueueSystem::run() {

    this->init();

    while (current_event) {

        // 判断当前事件类型

        if (current_event->event_type == -1){

            customerArrived();

        } else {

            customerDeparture();

        }

        delete current_event;

//我觉得当对一个指针重新赋值的时候,直接给这个指针赋值就好了,不需要delete,要不然每一处指针重新赋值都需要delete那么不是非常的麻烦么

        // 获得新的事件

        current_event = event_list.dequeue();

    };

    this->end();

    // 返回顾客的平均逗留时间

    return(double)total_customer_stay_time/total_customer_num;

}

// 系统运行结束,将所有服务窗口置空闲,并清空用户的等待队列和事件列表

voidQueueSystem::end() {

    // 设置所有窗口空闲

    for (int i=0; i != window_num; ++i) {

        windows[i].setIdle();

    }

    // 顾客队列清空

    customer_list.clearQueue();

 

    // 这个时候事件列表已经为空了,写这一句可能是为了保险起见吧,可能是显得更规范吧,不过我觉得这一句是没有什么作用的,清空事件列表

    event_list.clearQueue();

 

}

// 处理用户到达事件

voidQueueSystem::customerArrived() {

    total_customer_num++;

 

    // 生成下一个顾客的到达事件

    int intertime = Random::uniform(100);  // 生成一个[0,100)的随机数,下一个顾客到达的时间间隔,我们假设100分钟内一定会出现一个顾客

    int time = current_event->occur_time +intertime;

    Event temp_event(time);

    // 如果下一个顾客的到达时间小于服务的总时间,就把这个事件插入到事件列表中

    // 同时将这个顾客加入到 customer_list 进行排队

    if (time < total_service_time) {

        event_list.orderEnqueue(temp_event);

    } // 否则不列入事件表,且不加入 cusomer_list

 

 

    // 处理当前事件中到达的顾客

    Customer *customer = newCustomer(current_event->occur_time);

    if (!customer) {

        exit(-1);

    }

    customer_list.enqueue(*customer);

 

    // 如果当前窗口有空闲窗口,那么直接将这个顾客送入服务窗口

    int idleIndex = getIdleServiceWindow();

    if (idleIndex >= 0) {

        customer = customer_list.dequeue();

       windows[idleIndex].serveCustomer(*customer);

        windows[idleIndex].setBusy();

 

        // 顾客到窗口开始服务时,就需要插入这个顾客的一个离开事件到 event_list 中

        // 离开事件的发生时间 = 当前时间事件的发生时间 + 服务时间

        Eventtemp_event(current_event->occur_time + customer->duration, idleIndex);

        event_list.orderEnqueue(temp_event);

    }

delete customer;

//这里还需要注意的一个细节就是当创建一个类或结构体的对象的时候,在程序的最后不需要delete它,比如Event temp_event,但当动态创建一个指针的时候,在程序的最后需要释放它,这里就是这样的

}

voidQueueSystem::customerDeparture() {

    if (current_event->occur_time <total_service_time) {

        total_customer_stay_time += current_event->occur_time- windows[current_event->event_type].getCustomerArriveTime();

   

        if (customer_list.length()) {

            Customer *customer;

            customer = customer_list.dequeue();

           windows[current_event->event_type].serveCustomer(*customer);

//当一个处理离开事件的时候,需要立刻服务下一个顾客,如果顾客的离开时间大于银行的营业时间,那什么事情都不会做,会不断的消耗事件队列里面的时间类,当事件类消耗完毕的时候,也就是程序结束的时候

            Event temp_event(

                current_event->occur_time +customer->duration,

                current_event->event_type

            );

           event_list.orderEnqueue(temp_event);

 

            delete customer;

        } else {

            // 如果顾客等待队列没有人,那么这个银行窗口会被设为空闲状态,等待下一个顾客的到来,这就是事件驱动模型的运行原理

           windows[current_event->event_type].setIdle();

        }

 

    }

}

 

来源: 实验楼

链接: https://www.shiyanlou.com/courses/557

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值