c++教程(二十一: Polymorphism)

时间:2022-12-09 01:33:05

在深入这一章之前,你应该对指针和类继承有一个正确的理解。如果您不确定下列表达式中的任何一个含义,则应查看所指示的部分:
c++教程(二十一: Polymorphism)

Pointers to base class

类继承的一个重要特征是指向派生类的指针与指向基类的指针类型兼容。多态性是利用这个简单但功能强大且功能多样的特性。

关于矩形和三角形类的例子可以使用指针重写这个功能:

// pointers to base class
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
};

class Rectangle: public Polygon {
public:
int area()
{ return width*height; }
};

class Triangle: public Polygon {
public:
int area()
{ return width*height/2; }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << '\n';
cout << trgl.area() << '\n';
return 0;
}

Main函数声明两个指针多边形类(命名为ppoly1和ppoly2)。这些都是直接和rect 与trgl分配地址,分别是类型Rectangle 和Triangle的实体。因为矩形和三角形都是来自多边形的类,这样的赋值是有效的。

引用ppoly1和ppoly2( ppoly1 ppoly2)也是有效的,这也可以使我们能够访问他们的指向对象的成员。例如,以下两个语句在前面的示例中是等效的:

ppoly1->set_values (4,5);
rect.set_values (4,5);

但由于ppoly1和ppoly2类型是指针指向多边形Polygon (而不是指针也指向矩形三角形),只有从多边形Polygon 继承的成员可以访问,而不是其他的派生类,矩形和三角形。这就
是为什么上面的程序访问的对象用rect 和trgl直接访问,而不是指针;对基类不能访问area 成员的指针。

如果area 是多Polygon 多边形的成员而不是它的派生类的成员,那么area 成员是可以访问指针成员,但问题是,Rectangle 和Triangle是实现不同area 的版本,因此不存在一个单一的普通版本,可以在基类中实现。

虚拟成员

虚拟成员是可以在派生类中重新定义的成员函数,同时通过引用保留其调用属性。函数成为虚的语法是用虚拟关键字virtual 进行其声明:

// virtual members
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area ()
{ return 0; }
};

class Rectangle: public Polygon {
public:
int area ()
{ return width * height; }
};

class Triangle: public Polygon {
public:
int area ()
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon poly;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
Polygon * ppoly3 = &poly;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly3->set_values (4,5);
cout << ppoly1->area() << '\n';
cout << ppoly2->area() << '\n';
cout << ppoly3->area() << '\n';
return 0;
}

在这个例子中,所有的三类(多边形、矩形和三角形)有相同的成员:width,height,和函数set_values以及area。

成员函数area 在基类中声明为virtual 虚函数,因为它在每个派生类中被重新定义。非虚成员也可以在派生类中重新定义,但派生类的非虚成员不能通过基类的引用访问:即,如果virtual是从上面的area 删除后,后面所有的调用area 就会返回0,因为在所有的情况下,基类的版本将被代替。

因此,从本质上讲,是virtual 的关键词是允许派生类具有相同名称的成员作为一个基类的指针调用,更准确地说,当指针的类型是基类指针,指向派生类对象,如在上面的例子中。

声明或继承虚函数的类称为多态类。

值得注意的是,尽管其成员之一的虚拟性,Polygon 是一个普通类,其中甚至一个对象用自己定义的成员被实例化(poly),area也是总返回0。

抽象基类

抽象基类与前面示例中的多边形Polygon 类非常相似。它们是只能用作基类的类,因此可以不定义虚拟成员函数(称为纯虚函数)。语法是用 = 0(等号和零)替换它们的定义:

抽象基类Polygon 可以表示为:

// abstract class CPolygon
class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area () =0;
};

注意,area没有定义,这已被替换为= 0,这使得它纯粹的虚函数。包含至少一个纯虚函数的类称为抽象基类。

抽象类不能实例化对象。因此,这最后一个抽象的基类版本的多边形不能用来声明对象:

Polygon mypolygon;   // not working if Polygon is abstract base class 

但抽象基类不是完全无用的。它可以用来创建指向它的指针,并利用它的多态能力。例如,下列指针声明将是有效的:

Polygon * ppoly1;
Polygon * ppoly2;

而实际上可以被引用时,对象指向派生类(非抽象)。下面是整个例子:

// abstract base class
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0;
};

class Rectangle: public Polygon {
public:
int area (void)
{ return (width * height); }
};

class Triangle: public Polygon {
public:
int area (void)
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << ppoly1->area() << '\n';
cout << ppoly2->area() << '\n';
return 0;
}

在本例中,使用一个唯一类型的指针(Polygon *)来表示不同类型但相关类型的对象,每次都调用适当的成员函数,只是因为它们是虚的。这在某些情况下真的很有用。例如,即使抽象多边形本身没有此函数的实现,抽象基类多边形的成员也可以使用特殊指针来访问适当的虚拟成员:

// pure virtual members can be called
// from the abstract base class
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area() =0;
void printarea()
{ cout << this->area() << '\n'; }
};

class Rectangle: public Polygon {
public:
int area (void)
{ return (width * height); }
};

class Triangle: public Polygon {
public:
int area (void)
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly1->printarea();
ppoly2->printarea();
return 0;
}

在C++虚拟成员和抽象类多态性的特点,对于面向对象的项目最有用。当然,上面的例子是非常简单的用例,但这些特性可以应用于对象数组或动态分配对象。

这里有一个例子,结合一些功能在最新的章节,如动态内存,构造函数初始化器和多态性:

// dynamic allocation and polymorphism
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
Polygon (int a, int b) : width(a), height(b) {}
virtual int area (void) =0;
void printarea()
{ cout << this->area() << '\n'; }
};

class Rectangle: public Polygon {
public:
Rectangle(int a,int b) : Polygon(a,b) {}
int area()
{ return width*height; }
};

class Triangle: public Polygon {
public:
Triangle(int a,int b) : Polygon(a,b) {}
int area()
{ return width*height/2; }
};

int main () {
Polygon * ppoly1 = new Rectangle (4,5);
Polygon * ppoly2 = new Triangle (4,5);
ppoly1->printarea();
ppoly2->printarea();
delete ppoly1;
delete ppoly2;
return 0;
}

请注意ppoly指针:

Polygon * ppoly1 = new Rectangle (4,5);
Polygon * ppoly2 = new Triangle (4,5);

声明的类型是“指向多边形的指针”,但已分配的对象已被直接声明为派生类类型(矩形和三角形)。