加载中...

虚拟成员,抽象,多态


4.4 多态 (Polymorphism)

为了能更好的理解本节内容,你需要清楚的知道怎样使用指针pointers 和类之间的继承 inheritance between classes。建议如果你觉得以下这些表达式比较生疏的的话, 请复习指定的章节:

  1. int a::b(c) {}; // 类Classes (Section 4.1)
  1. a->b // 指针和对象pointers and objects (Section 4.2)
  1. class a: public b; // 类之间的关系Relationships between classes (Section 4.3)

基类的指针(Pointers to base class)

继承的好处之一是一个指向子类(derived class)的指针与一个指向基类(base class)的指针是type-compatible的。 本节就是重点介绍如何利用C++的这一重要特性。例如,我们将结合C++的这个功能,重写前面小节中关于长方形rectangle 和三角形 triangle 的程序:

| // pointers to base class

  1. #include <iostream.h>
  2. class CPolygon {
  3. protected:
  4. int width, height;
  5. public:
  6. void set_values (int a, int b) {
  7. width=a; height=b;
  8. }
  9. };
  10. class CRectangle: public CPolygon {
  11. public:
  12. int area (void) {
  13. return (width * height);
  14. }
  15. };
  16. class CTriangle: public CPolygon {
  17. public:
  18. int area (void) {
  19. return (width * height / 2);
  20. }
  21. };
  22. int main () {
  23. CRectangle rect;
  24. CTriangle trgl;
  25. CPolygon * ppoly1 = &rect;
  26. CPolygon * ppoly2 = &trgl;
  27. ppoly1->set_values (4,5);
  28. ppoly2->set_values (4,5);
  29. cout << rect.area() << endl;
  30. cout << trgl.area() << endl;
  31. return 0;
  32. }

| 20
10 |

在主函数 main 中定义了两个指向class CPolygon的对象的指针,即 ppoly1 和 ppoly2。 它们被赋值为rect 和 trgl的地址,因为rect 和 trgl是CPolygon 的子类的对象,因此这种赋值是有效的。

使用ppoly1 和 ppoly2 取代rect 和trgl 的唯一限制是ppoly1 和 ppoly2 是CPolygon 类型的,因此我们只能够引用CRectangle 和 CTriangle 从基类CPolygon中继承的成员。正是由于这个原因,我们不能够使用ppoly1 和 *ppoly2 来调用成员函数 area(),而只能使用rect 和 trgl来调用这个函数。

要想使CPolygon 的指针承认area()为合法成员函数,必须在基类中声明它,而不能只在子类进行声明(见下一小节)。

虚拟成员(Virtual members)

如果想在基类中定义一个成员留待子类中进行细化,我们必须在它前面加关键字virtual ,以便可以使用指针对指向相应的对象进行操作。

请看一下例子:

| // virtual members

  1. #include <iostream.h>
  2. class CPolygon {
  3. protected:
  4. int width, height;
  5. public:
  6. void set_values (int a, int b) {
  7. width=a;
  8. height=b;
  9. }
  10. virtual int area (void) { return (0); }
  11. };
  12. class CRectangle: public CPolygon {
  13. public:
  14. int area (void) { return (width * height); }
  15. };
  16. class CTriangle: public CPolygon {
  17. public:
  18. int area (void) {
  19. return (width * height / 2);
  20. }
  21. };
  22. int main () {
  23. CRectangle rect;
  24. CTriangle trgl;
  25. CPolygon poly;
  26. CPolygon * ppoly1 = &rect;
  27. CPolygon * ppoly2 = &trgl;
  28. CPolygon * ppoly3 = &poly;
  29. ppoly1->set_values (4,5);
  30. ppoly2->set_values (4,5);
  31. ppoly3->set_values (4,5);
  32. cout << ppoly1->area() << endl;
  33. cout << ppoly2->area() << endl;
  34. cout << ppoly3->area() << endl;
  35. return 0;
  36. }

| 20
10
0 |

现在这三个类(CPolygon, CRectangle 和 CTriangle) 都有同样的成员:width, height, set_values() 和 area()。

area() 被定义为virtual 是因为它后来在子类中被细化了。你可以做一个试验,如果在代码种去掉这个关键字(virtual),然后再执行这个程序,三个多边形的面积计算结果都将是 0 而不是20,10,0。这是因为没有了关键字virtual ,程序执行不再根据实际对象的不用而调用相应area() 函数(即分别为CRectangle::area(), CTriangle::area() 和 CPolygon::area()),取而代之,程序将全部调用CPolygon::area(),因为这些调用是通过CPolygon类型的指针进行的。

因此,关键字virtual 的作用就是在当使用基类的指针的时候,使子类中与基类同名的成员在适当的时候被调用,如前面例子中所示。

注意,虽然本身被定义为虚拟类型,我们还是可以声明一个CPolygon 类型的对象并调用它的area() 函数,它将返回0 ,如前面例子结果所示。

抽象基类(Abstract base classes)

基本的抽象类与我们前面例子中的类CPolygon 非常相似,唯一的区别是在我们前面的例子中,我们已经为类CPolygon的对象(例如对象poly)定义了一个有效地area()函数,而在一个抽象类(abstract base class)中,我们可以对它不定义,而简单得在函数声明后面写 =0 (等于0)。

类CPolygon 可以写成这样:

// abstract class CPolygon

  1. class CPolygon {
  2. protected:
  3. int width, height;
  4. public:
  5. void set_values (int a, int b) {
  6. width=a;
  7. height=b;
  8. }
  9. virtual int area (void) =0;
  10. };

注意我们是如何在virtual int area (void)加 =0 来代替函数的具体实现的。这种函数被称为纯虚拟函数(pure virtual function),而所有包含纯虚拟函数的类被称为抽象基类(abstract base classes)。

抽象基类的最大不同是它不能够有实例(对象),但我们可以定义指向它的指针。因此,像这样的声明:

CPolygon poly;

对于前面定义的抽象基类是不合法的。

然而,指针:

CPolygon * ppoly1; CPolygon * ppoly2

是完全合法的。这是因为该类包含的纯虚拟函数(pure virtual function) 是没有被实现的,而又不可能生成一个不包含它的所有成员定义的对象。然而,因为这个函数在其子类中被完整的定义了,所以生成一个指向其子类的对象的指针是完全合法的。

下面是完整的例子:

| // virtual members

  1. #include <iostream.h>
  2. class CPolygon {
  3. protected:
  4. int width, height;
  5. public:
  6. void set_values (int a, int b) {
  7. width=a;
  8. height=b;
  9. }
  10. virtual int area (void) =0;
  11. };
  12. class CRectangle: public CPolygon {
  13. public:
  14. int area (void) { return (width * height); }
  15. };
  16. class CTriangle: public CPolygon {
  17. public:
  18. int area (void) {
  19. return (width * height / 2);
  20. }
  21. };
  22. int main () {
  23. CRectangle rect;
  24. CTriangle trgl;
  25. CPolygon * ppoly1 = &rect;
  26. CPolygon * ppoly2 = &trgl;
  27. ppoly1->set_values (4,5);
  28. ppoly2->set_values (4,5);
  29. cout << ppoly1->area() << endl;
  30. cout << ppoly2->area() << endl;
  31. return 0;
  32. }

| 20
10 |

再看一遍这段程序,你会发现我们可以用同一种类型的指针(CPolygon*)指向不同类的对象,至一点非常有用。 想象一下,现在我们可以写一个CPolygon 的成员函数,使得它可以将函数area()的结果打印到屏幕上,而不必考虑具体是为哪一个子类。

| // virtual members

  1. #include <iostream.h>
  2. class CPolygon {
  3. protected:
  4. int width, height;
  5. public:
  6. void set_values (int a, int b) {
  7. width=a;
  8. height=b;
  9. }
  10. virtual int area (void) =0;
  11. void printarea (void) {
  12. cout << this->area() << endl;
  13. }
  14. };
  15. class CRectangle: public CPolygon {
  16. public:
  17. int area (void) { return (width * height); }
  18. };
  19. class CTriangle: public CPolygon {
  20. public:
  21. int area (void) {
  22. return (width * height / 2);
  23. }
  24. };
  25. int main () {
  26. CRectangle rect;
  27. CTriangle trgl;
  28. CPolygon * ppoly1 = &rect;
  29. CPolygon * ppoly2 = &trgl;
  30. ppoly1->set_values (4,5);
  31. ppoly2->set_values (4,5);
  32. ppoly1->printarea();
  33. ppoly2->printarea();
  34. return 0;
  35. }

| 20
10 |

记住,this 代表代码正在被执行的这一个对象的指针。

抽象类和虚拟成员赋予了C++ 多态(polymorphic)的特征,使得面向对象的编程object-oriented programming成为一个有用的工具。这里只是展示了这些功能最简单的用途。想象一下如果在对象数组或动态分配的对象上使用这些功能,将会节省多少麻烦。


还没有评论.