多态

在本节开始之前,建议您对指针和类继承有充分的理解。如果以下任何陈述对您来说很奇怪,您应该回顾相应的章节。

陈述在...中解释
int a::b(int c) { }
a->b数据结构
class a: public b { };友元和继承

指向基类的指针

派生类的关键特性之一是,指向派生类的指针与指向其基类的指针是类型兼容的。多态性就是利用这种简单但强大且通用的特性,将面向对象的方法发挥到极致。

我们将从重写上一节关于矩形和三角形的程序开始,并考虑这种指针兼容性属性。

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

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

class CRectangle: public CPolygon {
  public:
    int area ()
      { return (width * height); }
  };

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

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << rect.area() << endl;
  cout << trgl.area() << endl;
  return 0;
}
20
10

在函数main中,我们创建了两个指向类对象CPolygon (ppoly1ppoly2的指针。然后,我们将对recttrgl的引用赋给这些指针,因为它们都是派生自CPolygon的类的对象,所以这两个赋值操作都是有效的。

使用*ppoly1*ppoly2而不是recttrgl的唯一限制是,两者都是*ppoly1*ppoly2jCPolygon*类型的,因此我们只能使用这些指针来引用CRectangleCTriangleCPolygon继承的成员。因此,当我们调用程序末尾的area()成员时,我们必须直接使用对象recttrgl而不是指针。*ppoly1*ppoly2.

为了在指针指向类area()时使用CPolygon,该成员也应该在类CPolygon中声明,而不仅仅是在其派生类中,但问题在于CRectangleCTriangle实现了area的不同版本,因此我们无法在基类中实现它。这时,虚成员就派上用场了。

虚成员

可以在其派生类中重新定义的类成员被称为虚成员。为了将类成员声明为虚成员,我们必须在其声明前加上关键字virtual:

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

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

class CRectangle: public CPolygon {
  public:
    int area ()
      { return (width * height); }
  };

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

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon poly;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  CPolygon * ppoly3 = &poly;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly3->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  cout << ppoly3->area() << endl;
  return 0;
}
20
10
0

现在,这三个类(CPolygon, CRectangleCTriangle)都具有相同的成员。宽度, height, set_values()area().

成员函数area()在基类中被声明为虚函数,因为它后来在每个派生类中被重新定义。如果您想验证一下,可以尝试移除virtual关键字,并运行程序,结果将是area()CPolygon中的声明,而不是0对所有三个多边形,而不是20, 100。这是因为,与其调用每个对象对应的area()函数(分别是CRectangle::area(), CTriangle::area()CPolygon::area()),CPolygon::area()将在所有情况下被调用,因为调用是通过一个类型为CPolygon*.

的指针进行的。virtual因此,

关键字的作用是允许派生类中与基类同名的成员被正确调用,尤其是在指针类型为指向基类的指针但实际上指向派生类对象的情况下,如以上示例所示。

声明或继承了虚函数的类称为*多态类*。CPolygon请注意,尽管它具有虚特性,我们仍然可以声明一个类型为area()的对象,并调用其自身的

函数,该函数始终返回0。

抽象基类CPolygon抽象基类与我们上一示例中的area()类非常相似。唯一的区别在于,在上一示例中,我们为类为CPolygon的对象(如对象poly)定义了一个有效的area()函数,具有最小的功能;而在抽象基类中,我们可以完全不实现该=0成员函数。这可以通过在函数声明后附加

(等于零)来实现。

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

一个抽象基类CPolygon可以这样写:=0注意我们附加了virtual int area ()

而不是指定函数的实现。这种函数称为*纯虚函数*,所有包含至少一个纯虚函数的类都是*抽象基类*。

抽象基类与普通多态类之间的主要区别在于,由于抽象基类中至少有一个成员缺少实现,我们无法创建它的实例(对象)。

1
CPolygon poly;

但是,一个无法实例化对象的类并非完全无用。我们可以创建指向它的指针,并利用它的所有多态能力。因此,像

1
2
CPolygon * ppoly1;
CPolygon * ppoly2;

这样的声明对于我们刚刚声明的抽象基类来说是不合法的,因为它试图实例化一个对象。然而,以下指针

将是完全有效的。CPolygon这是因为

包含一个纯虚函数,因此它是一个抽象基类。但是,指向此抽象基类的指针可以指向派生类对象。

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

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

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

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

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  return 0;
}
20
10

完整的示例如下:CPolygon*如果您仔细查看程序,您会发现我们使用了一种独特的指针类型(CPolygon)来引用不同但相关的类对象。这可能非常有用。例如,现在我们可以创建一个抽象基类area()的成员函数,该函数即使CPolygon本身没有该函数的实现,也能将

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

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

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

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

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = &rect;
  CPolygon * 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 CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
    void printarea (void)
      { cout << this->area() << endl; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

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

int main () {
  CPolygon * ppoly1 = new CRectangle;
  CPolygon * ppoly2 = new CTriangle;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  delete ppoly1;
  delete ppoly2;
  return 0;
}
20
10

让我们用同一个例子结束,但这次使用动态分配的对象。请注意,ppoly

1
2
CPolygon * ppoly1 = new CRectangle;
CPolygon * ppoly2 = new CTriangle;

指针被声明为指向CPolygon的指针类型,但动态分配的对象已声明为直接具有派生类类型。
Index
目录