定义一个Box类,表示一个产品。然后用装车类TruckLoad来装,每个产品Box打包到到容器Package中,容器有两大要素:Box,指向下一个Package.
为什么要用容器?因为不要让Box复杂,只让它代表产品。所以有了Package,容器概念是很通用的,可以以相同的方式为任何类型的对象设置窗口对象。
我们明确三点:
一、Box类是,内容,纯粹的内容,不带其它的链接,是一个被操作的东西;
二、Package类,是节点,有两项必备:一是数据(即Box),二是链接(指向下一个Package对象);
三、TruckLoad类,是链表类,用于操作结点,并显示内容的类。
整个过程就是TruckLoad类以中间容器为介质操作Box类。
一、容器(Package)类的定义
按构思最初是这样的:
class Package{
public:
Package(Box* pBox):theBox(*pBox),pNext(0){}//复制一次
Box getBox()const{return theBox;}//显示内容
Package* getNext()const{return pNext;}//返回下个节点
void setNext(Package* pPackage){pNext=pPackage;}//连接下一个节点
private:
Box theBox; //复制一次
Package* pNext;
};
可以看到,要复制两次,一是构造复制,另一个是下一节点的复制。造成多个副本,若有一项改变将不知要修改哪些?
重新设置一下,以指针方式:
class Package{
public:
Package(Box* pNextBox):pBox(pNextBox),pNext(0){}//构造
Box* getBox()const{return pBox;} //返回Box指针
Package* getNext()const{return pNext;}//返回下个节点
void setNext(Package* pPackage){pNext=pPackage;}//连接下一个节点
private:
Box* pBox; //关键处,这里指针提高了效率,导致整个设计高效。
Package* pNext;
};
可以看到容器更的高效了,所以 开发容器类,需要仔细考虑该类的数据成员是否应是对象的副本,有时这很重要,应避免包含副本,除指针外还有引用也是可以来避免的。
二、定义TruckLoad类
它必须提供创建和扩展列表的所有功能,以及Box对象的方式。
这个货车装的链表,必须有链头,链尾,为了显示运送过程发放货物(box)的情况,还应记录当前Box发到什么地方的节点。
class TruckLoad{
public:
TruckLoad(Box* pBox=0,int Count=1);//构造,无数据时空链
Box* getFirstBox(); //返回链头
Box* getNextBox(); //返回下一个Box
void addBox(Box* pBox); //增加一个Box,内部代码把它加入下一个节点的数据中去
private:
Package* pHead; //链头
Package* pCurrent; //当前节点
Package* PTail; //链尾
};
可以看出Package是作为一个中间介质容器来用的。要有TruckLoad来完成所有任务的想法。
TruckLoad类的实现:
TruckLoad::TruckLoad(Box* pBox=0,int Count=0){
pHead=pCurrent=pTail=0;//初始化链表为空,
if((Count>0)&&(pBox!=0)) //创建时不空时
for(int i=0;i<Count;i++) //按数组方式组建
addBox(pBox+1); //指针增1是增加sizeof(Box)大小,一次增加一个节点
}
void addBox(Box* pBox){
Package* pPackage=new Package(pBox);//创建容器并初始化
if(pHeak)
pTail->setNext(pPackage); //非空,链接到链尾上
else
pHead=pPackage; //空链,新创建节点到链头
pTail=pPackage; //移动链尾到新创建的节点上
}
Box* TruckLoad::getFirstBox(){
pCurrent=pHeak; //开始分发,从链头开始,记录当前位置
return pCurrent->getBox();//返回当前节点的盒子
}
Box* TruckLoad::getNextBox(){
if(pCurrent)
pCurrent=pCurrent->getNext();//非空,移至下一节点
else
pCurrnet=pHead; //空,移到链头,节约代码不考虑删除等情况
return pCurrent?pCurrent->getBox():0;
}
三、Box类
这个较简单,就是定义长宽高,计算体积,并进行两个例子体积的比较。类定义:
//box.h definition
#ifndef BOX_H
#define BOX_H
class Box{
public:
Box(double aLength=1.0,double aWidth=1.0,double aHeight=1.0);
double volume()const;
double getLength()const;
double getWidth()const;
double getHeight()const;
int compareVolume(const Box& otherBox)const;
private:
double length;
double width;
double height;
};
#endif
然后是对应 的实现:
//box.cpp implementation
#include"box.h"
Box::Box(double aLength,double aWidth,double aHeight){
length=aLength>0.0?aLength:1.0;
width=aWidth>0.0?aWidth:1.0;
height=aHeight>0.0?aHeight:1.0;
}
double Box::volume()const{return length*width*height;}
double Box::getLength()const{return length;}
double Box::getWidth()const{return width;}
double Box::getHeight()const{return height;}
int Box::compareVolume(const Box& otherBox){
double vol1=volume();
double vol2=other.volume();
return vol1>vol2?1:(vol1<vol2?-1:0);
}
四、链表初步完成
共5个文件,Box两个文件:声明与实现
TruckLoad与Package结合在一起用,共两个文件:声明与实现
主文件一个
初步的文件有三个缺点:1、Package可以被任意文件访问,我们要把它封闭到TruckLoad类中:嵌套类
2、浅拷贝问题,TruckLoad对象复制只是指针的复制,如果一个修改了,另一个并不知道修改没
3、内存泄露:Package没有释放分配的内存,须要手动析构函数。
先看看已经初步得到的程序:
#ifndef BOX_H
#define BOX_H
class Box{
public:
Box(double aLength=1.0,double aWidth=1.0,double aHeight=1.0);
double volume()const;
double getLength()const;
double getWidth()const;
double getHeight()const;
int compareVolume(const Box& otherBox)const;
private:
double length;
double width;
double height;
};
#endif
#ifndef BOX_H
#define BOX_H
class Box{
public:
Box(double aLength=1.0,double aWidth=1.0,double aHeight=1.0);
double volume()const;
double getLength()const;
double getWidth()const;
double getHeight()const;
int compareVolume(const Box& otherBox)const;
private:
double length;
double width;
double height;
};
#endif
//list.h Pakage and TruckLoad
#ifndef LIST_H
#define LIST_H
#include"box.h" //不要忘记
class Package{ //Package类必须在TruckLoad前面
public:
Package(Box* pNewBox);//构造
Box* getBox()const; //返回Box指针
Package* getNext()const;//返回下个节点
void setNext(Package* pPackage);//连接下一个节点
private:
Box* pBox; //关键处,这里指针提高了效率,导致整个设计高效。
Package* pNext;
};
class TruckLoad{
public:
TruckLoad(Box* pBox=0,int Count=1);//构造,无数据时空链
Box* getFirstBox(); //返回链头
Box* getNextBox(); //返回下一个Box
void addBox(Box* pBox); //增加一个Box,内部代码把它加入下一个节点的数据中去
private:
Package* pHead; //链头
Package* pCurrent; //当前节点
Package* pTail; //链尾
};
#endif
//list.cpp Package and TruckLoad
#include"box.h"
#include"list.h"
Package::Package(Box *pNewBox):pBox(pNewBox),pNext(0){}//新结点的下结点始终指针向0
Box* Package::getBox()const{return pBox;}
Package* Package::getNext()const{return pNext;}
void Package::setNext(Package *pPackage){pNext=pPackage;}
TruckLoad::TruckLoad(Box* pBox,int Count){
pHead=pCurrent=pTail=0;//初始化链表为空,
if((Count>0)&&(pBox!=0)) //创建时不空时
for(int i=0;i<Count;i++) //按数组方式组建
addBox(pBox+1); //指针增1是增加sizeof(Box)大小,一次增加一个节点
}
Box* TruckLoad::getFirstBox(){
pCurrent=pHead; //开始分发,从链头开始,记录当前位置
return pCurrent->getBox();//返回当前节点的盒子
}
Box* TruckLoad::getNextBox(){
if(pCurrent)
pCurrent=pCurrent->getNext();//非空,移至下一节点
else
pCurrent=pHead; //空,移到链头,节约代码不考虑删除等情况
return pCurrent?pCurrent->getBox():0;
}
void TruckLoad::addBox(Box* pBox){
Package *pPackage=new Package(pBox);//创建容器并初始化
if(pHead)
pTail->setNext(pPackage); //非空,链接到链尾上
else
pHead=pPackage; //空链,新创建节点到链头
pTail=pPackage; //移动链尾到新创建的节点上
}
#include <iostream>
#include<cstdlib>//random
#include<ctime>
#include"box.h"
#include"list.h"
using std::cout;
using std::endl;
inline int random(int count){
return 1+static_cast<int>(count*static_cast<double>(std::rand())/(RAND_MAX+1.0));
}
int main(int argc, char *argv[])
{
const int dimLimit=100;
std::srand((unsigned)std::time(0));
TruckLoad load1;
for(int i=0;i<10;i++)
load1.addBox(new Box(random(dimLimit),random(dimLimit),random(dimLimit)));
Box* pBox=load1.getFirstBox();
Box* pNextBox;
while(pNextBox=load1.getNextBox()){
if(pBox->compareVolume(*pNextBox)<0) pBox=pNextBox;//选取最大体积
}
cout<<"largest is "<<pBox->volume()<<endl;
const int boxCount=20;
Box boxes[boxCount];
for(int i=0;i<boxCount;i++)
boxes[i]=Box(random(dimLimit),random(dimLimit),random(dimLimit));
TruckLoad load2(boxes,boxCount);
pBox=load2.getFirstBox();
while(pNextBox=load1.getNextBox()){
if(pBox->compareVolume(*pNextBox)<0) pBox=pNextBox;//选取最大体积
}
cout<<"second largest is "<<pBox->volume()<<endl;
pNextBox=load1.getFirstBox();
while(pNextBox){
delete pNextBox;
pNextBox=load1.getNextBox();
}
return 0;
}
五、解决外用,使用嵌套类
把Package纳入到TruckLoad类的私有成员中,可以禁止Package类在外部使用。
class TruckLoad{
public:
TruckLoad(Box* pBox=0,int Count=1);//构造,无数据时空链
Box* getFirstBox(); //返回链头
Box* getNextBox(); //返回下一个Box
void addBox(Box* pBox); //增加一个Box,内部代码把它加入下一个节点的数据中去
private:
class Package{
public:
Box* pBox; //因为两数据是公有的,不再使用两个提取成员函数
Package* pNext;
Package(Box* pNewBox);
void setNext(Package* pPackage);
};//注意这里少了分号,会出意思不到的问题
Package* pHead; //链头
Package* pCurrent; //当前节点
Package* pTail; //链尾
};
相应地更改实现:
//list.cpp Package and TruckLoad
#include"box.h"
#include"list.h"
TruckLoad::Package::Package(Box *pNewBox):pBox(pNewBox),pNext(0){}//新结点的下结点始终指针向0
//Box* Package::getBox()const{return pBox;}
//Package* Package::getNext()const{return pNext;}
void TruckLoad::Package::setNext(Package *pPackage){pNext=pPackage;}
TruckLoad::TruckLoad(Box* pBox,int Count){
pHead=pCurrent=pTail=0;//初始化链表为空,
if((Count>0)&&(pBox!=0)) //创建时不空时
for(int i=0;i<Count;i++) //按数组方式组建
addBox(pBox+1); //指针增1是增加sizeof(Box)大小,一次增加一个节点
}
Box* TruckLoad::getFirstBox(){
pCurrent=pHead; //开始分发,从链头开始,记录当前位置
return pCurrent->pBox;//返回当前节点的盒子
}
Box* TruckLoad::getNextBox(){
if(pCurrent)
pCurrent=pCurrent->pNext;//非空,移至下一节点
else
pCurrent=pHead; //空,移到链头,节约代码不考虑删除等情况
return pCurrent?pCurrent->pBox:0;
}
void TruckLoad::addBox(Box* pBox){
Package *pPackage=new Package(pBox);//创建容器并初始化
if(pHead)
pTail->pNext=pPackage; //非空,链接到链尾上
else
pHead=pPackage; //空链,新创建节点到链头
pTail=pPackage; //移动链尾到新创建的节点上
}