多态之前需要的知识:
在深入本章之前,您应该对指针和类继承有一个正确的理解。如果你不是很清楚下列任何一个表达的意思,你应该复习这部分。
类继承的一个关键特性是指向派生类的指针与指向其基类的指针是类型兼容的。多态性是利用这个简单但强大和多用途特性的艺术。
关于矩形(rectangle)和三角形(triangle)类的示例可以使用指针重写,并考虑到以下特性:
- // 指向基类的指针 || 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;
- }
函数 main 声明了两个指向 Polygon (基类)的指针(命名为 ppoly1和 ppoly2)。它们分别分配 rect 和 trgl 的地址,这两个地址是 Recangle 和 Triangle 类型的对象。这样的分配是有效的,因为矩形和三角形都是从Polygon派生的类。
解引用 ppoly1和 ppoly2(使用 ppoly1-> 和 ppoly2->)是有效的,并允许我们访问它们的指向对象的成员。例如,下面两个语句在前面的示例中是等效的:
ppoly1->set_values (4,5);
rect.set_values (4,5);
但是因为 ppoly1和 ppoly2的类型都是指向 Polygon 的指针(而不是指向矩形或三角形的指针) ,所以只能访问从 Polygon 继承的成员,而不能访问派生类 Recangle 和 Triangle 的成员。这就是为什么上面的程序使用 rect 和 trgl 直接访问这两个对象的面积成员,而不是使用指针; 指向基类的指针不能访问派生类特有的area()。
如果 area() 是 Polygon 的成员而不是它的派生类的成员,那么成员area()可以通过指向 Polygon 的指针来访问,但问题是 Recangle 和 Triangle 实现了不同版本的 area,因此不存在一个可以在基类中实现的通用版本。
虚成员是可以在派生类中重新定义的成员函数,同时通过引用保留其调用属性。函数成为 虚函数的语法是在其声明之前使用 Virtual 关键字:
- // 虚成员 || 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;
- }
virtual,那么对area()的所有三次调用都将返回零,因为在所有情况下,基类的版本都会被调用。virtual关键字实际上是允许从指针适当地调用与基类名相同的派生类的成员,而且更精确地说,当指针的类型是指向派生类对象的基类的指针时,如上例所示。抽象基类与前面示例中的 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的最后一个抽象基类版本不能用来声明如下对象:
Polygon mypolygon; // 如果Polygon是抽象基类则不工作
但是,抽象基类并非完全无用。它可以用来创建指向它的指针,并利用它的所有多态能力。例如,下列指针声明将是有效的:
Polygon * ppoly1;
Polygon * ppoly2;
当指向派生(非抽象)类的对象时,它实际上可以被解引用:
- // 抽象基类 || 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
在本例中,使用唯一类型的指针(Polygon *)引用不同但相关类型的对象(派生),并且每次都调用适当的成员函数,因为它们是虚拟的。这在某些情况下非常有用。例如,抽象基类 Polygon 的成员甚至可以使用特殊指针 this 来访问正确的虚拟成员(基类指针指向不同的派生类对象,就会调用相应对象的重写虚函数的函数,其实this指针就是指向不同的派生对象的虚表(虚表中维持着相应对象的重写的继承基类的虚函数),括号内的内容可以忽视!!!),即使 Polygon 本身没有这个函数的实现:
- // 可以从抽象基类 调用纯虚成员
-
- #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
虚拟成员和抽象类赋予 C + + 多态特性,这对于面向对象的项目非常有用。当然,上面的例子是非常简单的用例,但是这些特性可以应用于对象数组或动态分配的对象:
- // 动态分配 与 多态
- #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