C++_类和动态内存分配6-复习各种技术及队列模拟
知识点:
队列:是一种抽象的数据类型(Abstract Data Type),可以存储有序的项目序列。
新项目被添加在队尾,并可以删除队首的项目。队列有些像栈。栈是在同一端进行添加和删除。这使得栈是一种后进先出的结构,队列是先进先出的。
问题:Heather银行要在Food Hea超市门口开设一个ATM机。Food Heap需要了解ATM对超市交通可能造成的影响。Heather银行希望对顾客排队等待的时间进行评估,编写一个程序模拟这种情况。
设计:设计一个队列类,队列中的项目是顾客。设计一个表示顾客的类,编写一个程序来模拟顾客和队列之间的交互。
====================================================
队列类
队列的特征:
- 队列能够存储有序的项目序列;
- 队列所能容纳的项目数有一定的限制;
- 应当能够创建空队列;
- 应当能够检查队列是否为空;
- 应当能够检查队列是否是满的;
- 应当能够在队尾添加项目;
- 应当能够在队首删除项目;
- 应当能够确定队列中的项目数;
设计类时应当开发公有接口与私有实现;
1、 Queue类的接口
class Queue
{
enum {Q_SIZE = 10};
private:
// private representation to be developed later
public:
Queue(int qs = Q_SIZE); //create queue with a qs limit
~Queue();
bool isempty() const;
bool isfull() const;
int queuecount() const;
bool enqueuer(); //add item to end
bool dequeuer() //remove item from front
};
Queue line1; //默认构造函数 10-item limit
Queue line2(20); //queue with 20-item limit 显示初始化
2、 Queue类的实现
如何表示队列数据?
链表很好满足队列要求。链表由节点序列构成。每个节点都包含要保存到链表中的信息以及一个指向下一个节点的指针。
struct Node
{
Item item;
struct Node * next;
}
类的私有部分声明如下:
class Queue
{
private:
struct Node {Item item; struct Node * next;};
enum {Q_SIZE = 10};
Node * front;
Node * rear;
int items; //current number
const int qsize; //maxium number
public:
}
嵌套结构和类:
上述声明使用了一个特性,在类中嵌套结构声明或类声明;把Node声明放在Queue类中,可以使其作用域为整个类。也就是是说,Node是一种在类中声明的作用域为整个类的类型。可以用它来声明类成员,也可以将它作为类方法中的类型名称,但是注意这个Node只能在类中使用。而且不用担心Node声明和全局声明发生冲突。
如果声明是私有部分,则只能在类中使用;如果声明是在公有部分进行的,则可以在类的外部通过作用域解析运算符使用被声明的类型。例如可以在外部使用Queue::Node类型的变量。
空指针:有三种表示方式,NULL、0、nullptr(C++11);
3、 类方法
Queue::Queue(int qs)
{
front = rear = NULL;
items =0;
qsize = qs //not acceptable; qsize是const
}
类构造函数,初始化成员的值:
通常调用构造函数时,对象在括号中的代码被执行之前被创建;调用构造函数将导致程序首先给这几个成员分配内存。然后,程序流程进入到括号中,使用常规的赋值方式将值存储到内存中。
因此对于const数据成员,必须在执行到构造函数体之前,即创建对象时进行初始化。 C++提供了一种特殊的语法:成员初始化列表。前面带冒号,然后是有逗号分隔的列表。例如:
Queue::Queue(int qs) : qsize(qs)
{
front = rear =NULL;
item = 0;
}
通常,初值可以是常量或构造函数的参数列表中的参数。这种方法并不限于初始化常量。还可这样写:
Queue::Queue(int qs) : qsize(qs), front(NULL), rear(NULL), items(0)
{
}
注意:只有构造函数才可使用这种语法;对于const类成员,必须使用这种语法。对于被声明为引用的成也必须使用这种语法。
Class Agent
{
Private:
Agency & belong; //must use initializer list to initialize
}
Agent::Agent(Agency & a) : belong(a) {...}
引用与const数据类型相似,只能在被创建时候进行初始化。而且对于本身就是类对象的数据成员来说,使用成员初始化列表效率更高。
//在队尾加入新的节点(入队)
bool Queue::enqueue(const Item & item)
{
if (isfull()) //满了就不能插入
return false;
Node * add = new Node; //Create node
add -> item = item;
add -> next = NULL;
items++ //Current number of items in queue
if (front == NULL) //如果这个队列就是空的,那么新插入的节点就是队首;
front = add;
else
rear->next = add; //把当前队尾节点的next指向新队尾节点
rear =add; //将rear指向新的节点add;
return true;
}
// 删除队首项目节点(出队)
bool Queue::dequeue(Item & item)
{
if(front == NULL) //空了就不能取出
return false;
item = front -> item; //队首的item赋值给形参,相当于取队首item的动作;
items--; //项目数减少1个;
Node * temp = front; //用temp临时指针去指向旧队首;队首指针赋值给temp临时指针的方式
front = front -> next; //修改队首指针的指向,指向下一个item(新的队首);
delete temp; //删除旧队首;
if (items == 0) //如果队列空了,队尾指针被置为空指针;
rear = NULL;
return true;
}
队列到期时,队列不为空:但是没有方法可以清除这些内存;这就需要析构函数来处理;
虽然构造函数中没有用到new,就减少了给类带来的特殊要求;
类需要一个显式的析构函数——该函数删除剩余的所有节点。
Queue::~Queue()
{
Node * temp;
while(front !=NULL)
{
temp = front;
front =front->next;
delete temp;
}
}
使用new的类通常需要包含显式复制构造函数和执行深度复制的赋值运算符;
默认的成员复制是否很是:不合适,复制新对象后,也会发现它们将修改共同的链表;浙江造成非常严重的后果。更糟的是如果执行插入的话,只有副本的尾指针得到更新。这会破坏链表。所以必须要克隆和复制队列。所以必须有复制构造函数和执行深度复制的赋值运算符,尽管目前并不需要它们,如果没有复制队列的话。
还有一种小小的技巧可以避免这些额外的工作,并确保程序不会崩溃:就是定义伪私有方法(暂时不允许风险的操作,这些风险会被提示编译不通过,这就规避了犯错的可能):
Class Queue
{
Private:
Queue( ):qsize(0) { }
Queue & operator = (const Queue & q) {return * this;}
}
这样做有两个好处:一、避免了本来将自动生成的默认方法的定义。(不让用=这个操作,阻止了风险操作);二、因为这些方法是私有的,所以不能被广泛使用。即
Queue snick(nip); //not allowed
tuck =nip; //not allowed
与其将面对无法预料的故障,不如得到一个易于跟踪的
C++11还提供一种禁用方法的方式,delete关键字,在18章中会介绍。
复制构造函数:
用于对象按值传递时;
创建临时对象时;
创建一个对象,并初始化为已有对象;
====================================================
Customer类
设计一个客户类,确定:客户何时进入队列以及客户交易所需的时间;
class Customer
{
private:
long arrive //arrive time for customer
int processtime //processing time for customer
public:
Customer() {arrive = processtime=0;}
void set(long when);
long when() const {return arrive};
int ptime() const {return processtime};
}
====================================================
ATM模拟
程序将使用循环,每次循环代表一分钟。每分钟的循环,程序将完成下列工作:
1 判断是否来了新客人;如果来了,队列未满则将它添加到队列中,否则拒绝客户入队;
2 wait_time是新客人所需的等待时间;
模拟循环(小时为单位);
在该循环内,模拟随机出现客人,但可以保证出现客人的频率是平均每6分钟来一个;
bool newcustomer(double x)
{
Return (std::rand() * x / RAND_MAX <1);
}
====================================================
总结
本章介绍了定义和使用类的许多重要方面。其中一些方面是非常微妙甚至是很难理解的。
在类构造函数中,可以使用new为数据分配内存。然后将内存地址赋给类成员。这样,类便可以处理长度不同的字符串,而不用在类设计时提前固定数组的长度。
在类构造函数中使用new,也可能在对象过期时引发问题。
如果对象包含成员指针,同时它指向的内存是由new分配的。则释放用于保存对象的内存并不会自动释放对象成员指针所指向的内存。因此在构造函数中使用new来分配内存时,应在类析构函数中使用delete来释放分配的内存。这样,当对象过期时,将自动释放其指针成员指向的内存。
如果对象包含指向new分配的内存的指针成员,则将一个对象初始化为另一个对象,或将一个对象赋给另一个对象时,也会出现问题。(浅复制),在默认情况下,C++逐个对成员初始化和赋值,这意味着被初始化或被赋值的对象的成员将与原始对象完全相同。如果原始对象的成员指向一个数据块,则副本成员将指向同一个数据块。当程序最终删除这两个对象时,类的析构函数将试图删除同一个内存数据块两次,这将出错。
解决方法是:定义一个特殊的复制构造函数来重新定义初始化,并重载赋值运算符。在上述任何一种情况下,新的定义都将创建指向数据的副本,并使新的对象指向这些副本。这样,旧对象和新对象都将引用独立的,相同的数据,而不会重叠。由于同样的原因,必须定义赋值运算符。对于每一种情况,最终目的都是执行深度复制,也就是说,复制实际的数据,而不仅仅是复制指向数据的指针。
对象的存储持续性为自动或外部时,在它不再存在时将自动调用其析构函数。如果使用new运算符为对象分配内存,并将其地址赋给一个指针,则当您将delete用于该指针时将自动为该对象调用析构函数。
然而使用定位new运算符(而不是常规new运算符),为对象分配内存,比必须显式地为该对象调用析构函数。方法是使用指向该对象的指针调用析构函数的方法。
C++允许在类中包含结构、类和枚举定义。这些嵌套类型的作用域为整个类,这意味着它们被局限于类中,不会与其他地方定义的同名结构、类和枚举发生冲突。
C++为类构造函数提供了一种可用来初始化数据成员的特殊语法。这种语法包括冒号和有逗号分隔的初始化列表。被放在构造函数参数的右括号后,函数体的左括号前。每个初始化器都有被初始化的成员的名称和包含初始值的括号组成。从概念上讲,这些初始化操作是在对象创建时进行的,此时函数体中的语句还没有被执行。语法如下:
Queue(int qs): qsize(qs), items(0), front(NULL), rear(NULL){ }
如果数据成员是非静态const成员或引用,则必须采用这种格式,但可将C++11新增的类内初始化用于非静态const成员。
C++允许类内初始化,即在类定义中进行初始化:
这与使用成员初始化列表等价。然而,使用成员初始化列表的构造函数将覆盖相应的类内初始化。
Class Queue
{
…
private:
Node * front = NULL;
enum {Q_SIZE = 10};
node * rear = NULL;
int items = 0;
const int qsize =Q_SIZE;
…
};
与简单的C结构相比,需要注意的细节要多得多。作为回报,它们的功能也更强。
C++_类和动态内存分配6-复习各种技术及队列模拟的更多相关文章
- C++_类和动态内存分配3-构造函数中使用new的注意事项
如果在构造函数中使用new来初始化对象的指针成员时必须特别小心. 1 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete. 2 new和delete必须相互兼容.new对应于 ...
- C++_类和动态内存分配1—动态内存和类
静态类成员 num_strings成员声明为静态存储类.静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本.也就是说,类的所有对象共享一个静态成员.num_strings成员可以 ...
- C++_类和动态内存分配5-使用指向对象的指针
再探new和delete new为创建的每一个对象的名称字符串分配存储空间,这是在构造函数中进行的: 析构函数使用delete来释放这些内存. 字符串是一个字符数组,所以析构函数使用的是带中括号的de ...
- C++_类和动态内存分配2-改进后的String类
添加前面介绍过的复制构造函数和赋值运算符,使类能够正确管理类对象使用的内存. 知道对象何时被创建和释放. =================================== 修订后的默认构造函数 ...
- C++_类和动态内存分配4-有关返回对象的说明
返回方式: 返回指向对象的引用: 指向对象的const引用: const对象: =============================================== 返回指向const对象 ...
- 《C++ Primer Plus》读书笔记之十—类和动态内存分配
第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ...
- C++ primer plus读书笔记——第12章 类和动态内存分配
第12章 类和动态内存分配 1. 静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域运算符来指出静态成员所属的类.但如果静态成员是整形或枚举型const,则可以在类声明中初始化 ...
- C++——类和动态内存分配
一.动态内存和类 1.静态类成员 (1)静态类成员的特点 无论创建多少对象,程序都只创建一个静态类变量副本.也就是说,类的所有对象都共享同一个静态成员. (2)初始化静态成员变量 1)不能在类声明中初 ...
- C++拾遗(十)类与动态内存分配(2)
静态成员函数 声明时包含关键字static.注意以下两点: 1.使用静态成员函数时不能通过对象或者this指针来调用,只能使用类名+作用域解析符来调用. 2.静态成员函数只能使用静态成员. new操作 ...
随机推荐
- spring 项目返回406
406 The resource identified by this request is only capable of generating responses with characteris ...
- 模仿慕课网一步步发布一个开源库到 JCenter
H:\common\-common-25.2.2\upload.gradle // Bintray /* Properties properties = new Properties() proper ...
- 2-3 JAVA内存模型
- java代码优化29个点
通过java代码规范来优化程序,优化内存使用情况,防止内存泄露 可供程序利用的资源(内存.CPU时间.网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务.优化通常包含两方面的内容 ...
- Luogu 3646 [APIO2015]巴厘岛的雕塑
初赛成绩出了,和预想的一样,一分都没挂,开心. 大佬的博客 subtask 1 ($n \leq 200$) 因为高位取$0$一定比高位取$1$优,我们考虑按照位从高到低进行检验,设$f_{i, j} ...
- Part8-不用内存怎么行_我从内部看内存lesson1
- 跨库连接报错Server 'myLinkedServer' is not configured for RPC
Solution: Problem is most likely that RPC is not configured for your linked server. That is not a de ...
- JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(7):装配SpringBean·依赖注入装配
一.依赖注入的三种方式 在实际环境中实现IoC容器的方式主要分为两大类,一类是依赖查找,依赖查找是通过资源定位,把对应的资源查找回来.另一类则是依赖注入.一般而言,依赖注入可分为3中方式: ...
- JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(5):责任链模式、观察者模式
一.责任链模式.观察者模式 1.责任链模式:当一个对象在一条链上被多个拦截器处理(烂机器也可以选择不拦截处理它)时,我们把这样的设计模式称为责任链模式,它用于一个对象在多个角色中传递的场景. 2. ...
- repo相关命令
1.repo start <topic_name> 开启一个新的主题,其实就是每个Project都新建一个分支. repo start newbranchname . 创建新的branch ...