C++官方参考链接:Polymorphism - C++ Tutorials (cplusplus.com)
多态
在深入本章之前,您应该对指针和类继承有一个正确的理解。如果你不确定下面任何一个表达的意思,你应该复习指定的部分:
Statement:(语句) | Explained in:(在...里面解释) |
---|---|
int A::b(int c) { } | Classes |
a->b | Data structures |
class A: public B {}; | Friendship and inheritance |
指向基类的指针
类继承的一个关键特性是指向派生类的指针与指向其基类的指针是类型兼容的。多态性是利用这个简单但功能强大且通用的特性的艺术。
关于Rectangle和Triangle类的例子可以使用考虑到这个特性的指针的重写:
// 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 = ▭
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << '\n';
cout << trgl.area() << '\n';
return 0;
}
函数main声明了两个指向Polygon的指针(命名为ppoly1和ppoly2)。它们分别被分配rect和trgl的地址,它们是Rectangle和Triangle类型的对象。这样的赋值是有效的,因为Rectangle和Triangle都是从Polygon派生的类。
解除对ppoly1和ppoly2(包括ppoly1->和ppoly2->)的引用是有效的,允许我们访问它们的指向对象的成员。例如,以下两个语句在前面的例子中是等价的:
ppoly1->set_values (4,5);
rect.set_values (4,5);
但是因为ppoly1和ppoly2的类型都是指向Polygon的指针(而不是指向Rectangle或指向Triangle的指针),所以只能访问从Polygon继承的成员,而不能访问派生类Rectangle和Triangle的成员。这就是为什么上面的程序直接使用rect和trgl来访问两个对象的area成员,而不是使用指针;基类的指针不能访问area成员。
如果area是Polygon的成员而不是其派生类的成员,则可以通过指向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 = ▭
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;
}
在本例中,所有三个类(Polygon、Rectangle和Triangle)都具有相同的成员:width、height以及函数set_values和area。
成员函数area在基类中被声明为virtual,因为它稍后在每个派生类中被重新定义。非虚成员也可以在派生类中重新定义,但是派生类的非虚成员不能通过基类的引用访问:也就是说,如果在上面的例子中从area的声明中删除了virtual,那么对area的所有三个调用都将返回0,因为在所有情况下,基类的版本都将被调用。
因此,本质上,virtual关键字的作用是允许从指针适当地调用派生类中与基类中同名的成员,更准确地说,当指针的类型是指向派生类对象的基类的指针时,如上面的示例所示。
声明或继承虚函数的类称为多态类。
注意,尽管Polygon的一个成员是虚拟的,但它仍然是一个普通类,它甚至实例化了一个对象(poly),它自己定义的成员area总是返回0。
抽象基类
抽象基类与前面示例中的Polygon类非常相似。它们是只能作为基类使用的类,因此允许具有没有定义的虚成员函数(称为纯虚函数)。语法是用=0(一个等号和一个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的最后一个抽象基类版本不能用于声明如下对象:
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 = ▭
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << ppoly1->area() << '\n';
cout << ppoly2->area() << '\n';
return 0;
}
在本例中,使用唯一类型的指针(Polygon*)引用不同但相关类型的对象,并且每次都调用适当的成员函数,因为它们是虚的。这在某些情况下是非常有用的。例如,抽象基类Polygon的成员甚至可以使用特殊指针this来访问适当的虚成员,即使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 = ▭
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);
被声明为“指向Polygon的指针”类型,但是被分配的对象被声明为直接派生类类型(Rectangle和Triangle)。