Polymorphism【多态性】
在深入学习本章之前,您应该对指针和类继承有一个正确的理解。如果您不确定以下任何表达的含义,您应该查看指示的部分:
指向基类的指针
类继承的一个关键特征是指向派生类的指针与指向其基类的指针是类型兼容的。多态性是利用这种简单但强大且通用的特性的艺术。
考虑到这个特性,可以使用指针重写关于矩形和三角形类的示例:
// 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;
}
20
10
函数main声明了两个指向Polygon(命名ppoly1和ppoly2)的指针。它们分别被分配了rect和的地址trgl,它们是 和 类型的Rectangle对象Triangle。这样的赋值是有效的,因为Rectangle和Triangle都是派生自Polygon.
解引用ppoly1and ppoly2(with ppoly1->and ppoly2->) 是有效的,并允许我们访问其指向对象的成员。例如,以下两个语句在前面的示例中是等价的:
1.ppoly1->set_values (4,5);
2.rect.set_values (4,5);
但是因为ppoly1and的类型都是ppoly2指向的指针Polygon(既不是指向Rectangle也不是指向 的指针Triangle),所以只能访问继承自的成员Polygon,而不能访问派生类Rectangle和的成员Triangle。这就是为什么上面的程序使用和直接访问area两个对象的成员,而不是指针;指向基类的指针不能访问成员。如果是其派生类的成员而不是其派生类的 成员,则可以使用指向该成员的指针访问成员,但问题在于并实现了不同版本的recttrglarea
areaPolygonareaPolygonRectangleTrianglearea,因此没有一个可以在基类中实现的通用版本。
虚拟会员
虚拟成员是可以在派生类中重新定义的成员函数,同时通过引用保留其调用属性。函数变为虚函数的语法是在其声明之前加上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;
}
RESULT:
20
10
0
在此示例中,所有三个类(Polygon和Rectangle)Triangle都具有相同的成员:width、height和函数set_values以及area。
成员函数area已virtual在基类中声明,因为它稍后在每个派生类中重新定义。非虚拟成员也可以在派生类中重新定义,但是派生类的非虚拟成员不能通过基类的引用来访问:即,如果virtual从area上面示例的声明中删除,则所有三个调用areawould返回零,因为在所有情况下,基类的版本都会被调用。
因此,本质上,什么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没有定义;this 已被 取代=0,这使它成为一个纯虚函数。至少包含一个纯虚函数的类称为抽象基类。
抽象基类不能用于实例化对象。因此,最后一个抽象基类版本Polygon不能用于声明如下对象:
Polygon mypolygon; // not working if Polygon is abstract base class
但抽象基类并非完全无用。它可以用来创建指向它的指针,并利用它的所有多态能力。例如,以下指针声明是有效的:
1 Polygon * ppoly1;
2 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;
}
20
10
在此示例中,使用唯一类型的指针 ( 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;
}
20
10
虚拟成员和抽象类授予 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;
}
20
10
Polygon * ppoly1 = new Rectangle (4,5);
Polygon * ppoly2 = new Triangle (4,5);
被声明为“指向Polygon”的类型,但分配的对象已直接声明为具有派生类类型(Rectangle和Triangle)。
Original Article
Before getting any deeper into this chapter, you should have a proper understanding of pointers and class inheritance. If you are not really sure of the meaning of any of the following expressions, you should review the indicated sections:
Statement: Explained in:
int A::b(int c) { } Classes
a->b Data structures
class A: public B {}; Friendship and inheritance
Pointers to base class
One of the key features of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature.
The example about the rectangle and triangle classes can be rewritten using pointers taking this feature into account:
// pointers to base class
#include
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;
}
20
10
Edit & run on cpp.sh
Function main declares two pointers to Polygon (named ppoly1 and ppoly2). These are assigned the addresses of rect and trgl, respectively, which are objects of type Rectangle and Triangle. Such assignments are valid, since both Rectangle and Triangle are classes derived from Polygon.
Dereferencing ppoly1 and ppoly2 (with ppoly1-> and ppoly2->) is valid and allows us to access the members of their pointed objects. For example, the following two statements would be equivalent in the previous example:
1
2
ppoly1->set_values (4,5);
rect.set_values (4,5);
But because the type of both ppoly1 and ppoly2 is pointer to Polygon (and not pointer to Rectangle nor pointer to Triangle), only the members inherited from Polygon can be accessed, and not those of the derived classes Rectangle and Triangle. That is why the program above accesses the area members of both objects using rect and trgl directly, instead of the pointers; the pointers to the base class cannot access the area members.
Member area could have been accessed with the pointers to Polygon if area were a member of Polygon instead of a member of its derived classes, but the problem is that Rectangle and Triangle implement different versions of area, therefore there is not a single common version that could be implemented in the base class.
Virtual members
A virtual member is a member function that can be redefined in a derived class, while preserving its calling properties through references. The syntax for a function to become virtual is to precede its declaration with the virtual keyword:
// virtual members
#include
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;
}
20
10
0
Edit & run on cpp.sh
In this example, all three classes (Polygon, Rectangle and Triangle) have the same members: width, height, and functions set_values and area.
The member function area has been declared as virtual in the base class because it is later redefined in each of the derived classes. Non-virtual members can also be redefined in derived classes, but non-virtual members of derived classes cannot be accessed through a reference of the base class: i.e., if virtual is removed from the declaration of area in the example above, all three calls to area would return zero, because in all cases, the version of the base class would have been called instead.
Therefore, essentially, what the virtual keyword does is to allow a member of a derived class with the same name as one in the base class to be appropriately called from a pointer, and more precisely when the type of the pointer is a pointer to the base class that is pointing to an object of the derived class, as in the above example.
A class that declares or inherits a virtual function is called a polymorphic class.
Note that despite of the virtuality of one of its members, Polygon was a regular class, of which even an object was instantiated (poly), with its own definition of member area that always returns 0.
Abstract base classes
Abstract base classes are something very similar to the Polygon class in the previous example. They are classes that can only be used as base classes, and thus are allowed to have virtual member functions without definition (known as pure virtual functions). The syntax is to replace their definition by =0 (an equal sign and a zero):
An abstract base Polygon class could look like this:
1
2
3
4
5
6
7
8
9
// 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;
};
Notice that area has no definition; this has been replaced by =0, which makes it a pure virtual function. Classes that contain at least one pure virtual function are known as abstract base classes.
Abstract base classes cannot be used to instantiate objects. Therefore, this last abstract base class version of Polygon could not be used to declare objects like:
1
Polygon mypolygon; // not working if Polygon is abstract base class
But an abstract base class is not totally useless. It can be used to create pointers to it, and take advantage of all its polymorphic abilities. For example, the following pointer declarations would be valid:
1
2
Polygon * ppoly1;
Polygon * ppoly2;
And can actually be dereferenced when pointing to objects of derived (non-abstract) classes. Here is the entire example:
// abstract base class
#include
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;
}
20
10
Edit & run on cpp.sh
In this example, objects of different but related types are referred to using a unique type of pointer (Polygon*) and the proper member function is called every time, just because they are virtual. This can be really useful in some circumstances. For example, it is even possible for a member of the abstract base class Polygon to use the special pointer this to access the proper virtual members, even though Polygon itself has no implementation for this function:
// pure virtual members can be called
// from the abstract base class
#include
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;
}
20
10
Edit & run on cpp.sh
Virtual members and abstract classes grant C++ polymorphic characteristics, most useful for object-oriented projects. Of course, the examples above are very simple use cases, but these features can be applied to arrays of objects or dynamically allocated objects.
Here is an example that combines some of the features in the latest chapters, such as dynamic memory, constructor initializers and polymorphism:
// dynamic allocation and polymorphism
#include
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;
}
20
10
Edit & run on cpp.sh
Notice that the ppoly pointers:
1
2
Polygon * ppoly1 = new Rectangle (4,5);
Polygon * ppoly2 = new Triangle (4,5);
are declared being of type “pointer to Polygon”, but the objects allocated have been declared having the derived class type directly (Rectangle and Triangle).