第11章 友元与运算符重载
Radius=10 High=10 Cylinder Volume=3141.5 High=10
Length=10 Width=10 Cubiod Volume=1000 Radius=10
High=10
Cylinder Volume=3141.5
High=10
·305·
在主函数中通过调用三个不同对象的成员函数
h.Show(); cu.Show(); cy.Show();
分别输出高、长方体、圆柱体的值。因在编译时,根据对象名,就可确定调用哪一个成员函数,所以这是编译时的多态性。
在主函数中将三个不同类型的对象起始地址依次赋给基类的指针变量p, p=&h; p->Show(); p=&cu; p->Show(); p=&cy; p->Show();
这在C++中是允许的,即可将由基类所派生出来的派生类对象的地址&cu与&cy赋给基类类型的指针变量p。当基类指针变量指向不同的对象时,尽管调用的形式完全相同,均为p->Show(); 但确调用了不同对象中函数。因此输出了不同的结果,所以这是运行时的多态性。
关于虚函数有几点说明如下:
(1)当基类中将成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中虚函数同名同参数同返回类型,如上例中基类与派生类中的虚函数名均为Show,均无参数,返回类型均为void。在定义派生类中的虚函数时,可不加关键词virtual。
(2)实现动态的多态性时,必须使用基类类型的指针变量,使该指针指向不同派生类的对象,并通过调用指针所指向的虚函数才能实现动态的多态性。
(3)虚函数必须是类的一个成员函数,不能是友元函数,也不能是静态的成员函数。 (4)在派生类中没有重新定义虚函数时,与一般的成员函数一样,当调用这种派生类对象的虚函数时,则调用基类中的虚函数。
(5)可将析构函数定义为虚函数,但不能将构造函数定义为虚函数。通常在释放基类中和派生类中动态申请的存储空间时,也要将析构函数定义为虚函数,以便实现撤消对象时的多态性。
(6)虚函数与一般函数相比较,调用时执行速度要慢一些。为了实现多态性。在每一个派生类中均要保持相应虚函数的入口地址表,函数调用机制也是间接实现的。因此除了要编写一些通用的程序并一定要使用虚函数才能完成其功能要求外,通常不必使用虚函数。
第11章 友元与运算符重载 ·306·
11.3.3 纯虚函数 在定义一个基类时,若无法定义基类中虚函数的具体操作,虚函数的具体操作完全取决于其不同的派生类。这时,可将基类中的虚函数定义为纯虚函数。定义纯虚函数的一般格式为:
virtual <类型> <纯虚函数名>(形参表)=0; 由纯虚函数的定义格式可以看出如下几点:
(1)由于纯虚函数无函数体,所以在派生类中没有重新定义纯虚函数之前,是不能调用这种函数的。
(2)将函数名赋值为0的含义是,将指向函数体的指针值赋初值0。
(3)将至少包含一个纯虚函数的类称为抽象类。这种类只能作为派生类的基类,不能用来说明对象。其理由很明显;因为虚函数没有实现部分,所以不能产生对象。但可定义指向抽象类的指针,即指向这种基类的指针。当用这种基类指针指向其派生类的对象时,必须在派生类中重载纯虚函数,否则会产生程序的运行错误。
【例11.15】定义抽象基类High,其数据成员为高H,定义Show()为纯虚函数。然后再由High派生出长方体类Cuboid与圆柱体类Cylinder。并在两个派生类中重新定义虚函数Show()。在主函数中,用基类High定义指针变量p,然后用指针p动态调用派生类中虚函数Show(),显示长方体与圆柱体的体积。
# include
High(float h) {H=h;}
virtual void Show()=0;
//在基类中定义纯虚函数Show()
};
class Cuboid:public High { private:
float Length,Width; public:
Cuboid(float l=0,float w=0,float h=0):High(h) {Length=l; Width=w;} void Show() //在长方体派生类中定义虚函数Show() { cout<<\ cout<<\ cout<<\
cout<<\olume=\ } };
第11章 友元与运算符重载 ·307·
class Cylinder:public High { private: float R; public:
Cylinder(float r=0,float h=0):High(h) { R=r;} void Show() //在圆柱体派生类中定义虚函数Show() { cout<<\ cout<<\
cout<<\olume=\ } };
void main(void) { High *p;
Cuboid cu(10,10,10); Cylinder cy(10,10); p=&cu; p->Show(); p=&cy; p->Show(); }
执行程序后输出:
Length=10 Radius=10
Width=10 High=10
High=10
Cubiod Volume=1000 Cylinder Volume=3141.5
若在主函数中增加说明:
High h;
则因为抽象类High不能产生对象,编译时将给出错误信息。
(4)在以抽象类作为基类的派生类中必须有纯虚函数的实现部分,即必须有重载纯虚函数的函数体。否则,这样的派生类也是不能产生对象的。
综上所述,可将纯虚函数归结为:抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类的成员函数的基础,并实现动态多态性。
下面通过例子来说明抽象类的简单应用。
*【例11.16】建立一个双向链表,要完成插入一个结点、删除一个结点、查找某一个结点操作,并输出链表上各结点值。设结点只有一个整数。
分析:因链表的插入、删除、查找等操作都是相同的,只是结点上的信息随着不同的应用有所不同,所以可将实现链表操作部分设计成通用的程序。一个结点的数据结构用两个类来表示。如图11.6所示。类IntObj的数据成员描述结点信息,成员函数完成两个结点
第11章 友元与运算符重载 ·308·
比较,输出结点数据等。类Node的数据成员中,包括要构成双向链表时,指向后一个结点的后向指针Next,指向前一个结点的前向指针Prev,指向描述结点数据的指针Info。另外定义一个类List,把它作为类Node的友元,它的成员数据包括指向链表的首指针Head,指向链尾的指针Tail,成员函数实现链表的各种操作,如插入一个结点,删除一个结点等。由于类List是类Node的友元,因此它的成员函数可以访问Node的所有成员。
指针 指针 前向指针 前向指针 后向指针 后向指针 图11.6 链表中的数据指针
# include
{ public:
Object(){} virtual int IsEqual(Object &)=0; virtual void Show()=0; virtual ~Object() {} };
class Node
//缺省的构造函数
//实现两个结点数据比较的纯虚函数 //输出一个结点上数据的纯虚函数 //析构函数定义为虚函数 //定义结点类
//指向描述结点的数据域 //用于构成链表前、后指针 //定义缺省的构造函数 //完成拷贝功能的构造函数
描述结点信息 描述结点信息 { private:
Object *Info; Node *Pre, *Next; public: Node()
{ Info=0;Pre=0;Next=0;} Node (Node &node) { Info=node.Info; Pre=node.Pre; Next=node.Next; }
void FillInfo(Object *obj) { Info=obj;} friend class List;
//使Info指向数据域 //定义List为Node的友元类
第11章 友元与运算符重载
};
class List { private:
Node *Head, *Tail; public: List()
//实现双向链表操作的类 //定义链表首和链表尾指针 //置空链表
·309·
{ Head=Tail=0;} ~List()
{ DelList();} void AddNode(Node *); Node * DelNode(Node *); Node * LookUp(Object &); void ShowList (); void DelList();
//释放链表所占的存储空间
//在链表尾增加一个结点的成员函数 //删除链表中指定结点的成员函数 //在链表中查找指定结点的成员函数 //输出整条链表上的数据的成员函数 //删除整条链表的成员函数
};
void List::AddNode(Node * node)
{ if (Head==0) { Head=Tail=node; node->Next=node->Pre=0; }
else { Tail->Next=node;
//链表为空表时
//链表首、尾指针指向结点 //该结点前、后指针置为空 // 链表非空
//将结点加入到链表尾
node->Pre=Tail; Tail=node; node->Next=0; } }
Node * List::DelNode(Node * node) { if (node==Head) if (node==Tail) Head=Tail=0; else
{ Head=node->Next; Head->Pre=0; } else
{ node->Pre->Next=node->Next;
//删除指定结点 //删除链表首结点 //链表只有一个结点
// 删除链表首结点
//删除非首结点
if (node!=Tail) node->Next->Pre=node->Pre; else Tail=node->Pre;