Home C&C++函数库 c++ 语法 程序源码 Linux C库

多态


在深入学习本章之前,您应该对指针和类继承有一个正确的理解。 如果你不是很确定以下任何一个表达的意思,你应该查看指定的部分:

声明 场景
int A::b(int c) { } class
a->b Data structures
class A: public B {}; Friendship and inheritance

☞ 基类指针



类继承的一个关键特性是,指向派生类的指针与指向基类的指针类型兼容。 多态性是利用这个简单但功能强大且通用的特性的艺术。

对于这个特性,关于矩形和三角形类的例子可以使用指针来重写:

// 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 = &rect;
  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派生出来的类。

解引用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 = &rect;
  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

在这个例子中,所有三个类(Polygon, Rectangle和Triangle) 都有相同的成员:width, height,函数set_values和area。

成员函数area 在基类中声明为虚函数,因为它稍后会在每个派生类中重新定义。 非虚成员也可以在派生类中重新定义,但不能通过基类的引用访问派生类的非虚成员: 也就是说,如果在上面的例子中,virtual从area的声明中删除, 那么对area的所有三个调用都将返回0,因为在任何情况下,基类的版本都将被调用。

因此,从本质上讲,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 没有定义;这个被=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 = &rect;
  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 = &rect;
  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

注意ppoly指针:

Polygon * ppoly1 = new Rectangle (4,5);
Polygon * ppoly2 = new Triangle (4,5);

被声明为“指向 Polygon的指针”类型,但是被分配的对象被直接声明为派生类类型(Rectangle 和 Triangle)。

联系我们 免责声明 关于CandCplus 网站地图