my3Dgui开发过程中我的心得和经验 之一
使用面向对象的方法开发程序:多层链表的遍历
如果现在有一个链表,可以怎样来遍历?
[code:1]
for(obj* now=begin; now!=NULL; now=now->next){
……
}
[/code:1]
那么如果链表之中有子链表呢?比如以下的结构:
[code:1]
●→●→●→●→●→●→●→●→●→●→●→●→●→NULL
↓ ↓ ↓
● ● ●→●→●→●→●→NULL
↓ ↓ ↓ ↓
● NULL ● ●
↓ ↓ ↓
NULL ● ●
↓ ↓
NULL ●→●→●→●→NULL
↓
NULL
[/code:1]
这样一组分层次结构的链表,又该如何遍历?其中有这样几个问题:1.每个节点可能是不同类型的;2.每一层链表的长度都是不同的、不可确定的;3.这一组链表的层数往往是不可确定的。
第一个问题,使用面向对象中的多态特性就可以解决,既保证所有节点所属类都是由同一个基类直接或间接地派生而来的,把循环体中要用的成员函数作为虚函数放进基类中进行声明,节点间使用基类指针来连接。
第二个问题,在使用for循环时,把运行循环体的条件设为“now指针没有指向NULL”,就像本文开头的那个例子。
第三个问题,其中还可分为两个问题:1.如何进入子链表进行遍历;2.如何遍历多层子链表。一种办法是使用运行期类型检测,检测当前对象是否拥有子链表,然后再进行遍历;这样有两个缺点:运行期类型检测据说挺慢;遍历时,得检测每个节点的类型并做判断。如果不检测每个节点的类型,那么遍历时就不知道当前要处理的节点是否还拥有子链表,那么该怎么办呢?
下面我提出我的办法:
先看这样一个类:
[code:1]
class myPoint{
public:
myPoint();
~myPoint();
/** draw this point */
int draw();
public: // Public attributes
/** the pointer points to the next object */
myPoint *next;
};
/** draw this point */
int myPoint::draw(){
……
return next->draw();
}
[/code:1]
请注意draw()函数体里最后一个语句:return next->draw();。设想一下:一个链表,每个节点都是myPoint类的实例,当调用头节点的draw()方法且该函数返回之后,整个链表都被遍历了一次,每个节点都依次被调用了draw()方法。使用这样的类,就避免了使用循环语句来进行遍历。可是,当执行了最后一个节点的draw()方法时,其实执行的就是NULL->draw(),这怎么行?另外,如果每个节点是不同类型的呢?那么就要使用这个类:
[code:1]
class myObj {
public:
myObj();
virtual ~myObj();
/** draw this object
if no problems return 0;
*/
virtual int draw();
};
/** draw this object
if no problems return 0;
*/
int myObj::draw(){
return 0;
}
[/code:1]
原来的myPoint类要改成这样:
[code:1]
class myPoint : public myObj {
public:
myPoint();
virtual ~myPoint();
/** draw this point */
virtual int draw();
public: // Public attributes
/** the pointer points to the next object */
myObj *next;
};
/** draw this point */
int myPoint::draw(){
……
return next->draw();
}
myPoint::~myPoint(){
delete next;
}
[/code:1]
确保每个节点所属的类都是由myObj类派生而来;用于连接相临节点的指针是myObj类型的指针。这样就可以解决各个节点类型不同的问题了。当调用了头节点的draw()方法后,如何让这一“链式反应”在最后一个节点处终止并使函数返回呢?这就需要在链表的末尾再加一个节点,但是这个节点是myObj的实例。这样,当倒数第二个节点的draw()方法被调用后,最后一个节点,就是那个myObj类的实例的draw()方法将被调用,它将直接返回数值0,“链式反应”就结束了。这样,没有使用循环,不需要知道链表长度,也不需要知道每个节点的类型,仍然完成了遍历。那么,如何遍历多层链表呢?看看这个类:
[code:1]
class myShape : public myObj {
public:
myShape();
virtual ~myShape();
/** draw this shape with points */
virtual int draw();
public: // Public attributes
/** a pointer points to the first object (point) of this shape */
myObj *objsBegin;
/** a pointer points to the next object (shape) */
myObj *next;
};
[/code:1]
其中的objsBegin是个指针,指向了由myShape的一个实例所拥有的一个子链表。遍历这个子链表,只需要调用objsBegin->draw()就行了。所以,myShape的draw()方法是这样实现的:
[code:1]
/** draw this shape with points */
int myShape::draw(){
……
objsBegin->draw();
……