多态(Polymorphism)

重要的事情说三遍:

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

强烈建议按照目录结构中的顺序学习!!!点我查看教程目录结构

在深入学习本章节之前,您应该对指针和类继承有充分的理解。如果您不确定以下表达式的含义,建议您复习对应的章节:

语句:解释章节:
int A::b(int c) { }
a->b数据结构
class A: public B {};友元和继承

指向基类的指针

类继承的一个关键特性是指向派生类的指针与指向其基类的指针在类型上是兼容的。多态性就是利用这个简单但强大和灵活的特性。

关于矩形和三角形类的例子可以重新编写,使用指针来体现这一特性:

// 指向基类的指针
#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;
}

在这个例子中,main 函数声明了两个指向 Polygon 的指针(分别为 ppoly1ppoly2)。这些指针被分别赋值为 recttrgl 的地址,这两个对象分别是 RectangleTriangle 类型。这样的赋值是合法的,因为 RectangleTriangle 都是从 Polygon 派生的类。

解引用 ppoly1ppoly2(使用 ppoly1->ppoly2->)是合法的,并且允许我们访问它们所指向对象的成员。例如,以下两条语句在前面的例子中是等价的:

ppoly1->set_values (4,5);
rect.set_values (4,5);

但是,由于 ppoly1ppoly2 的类型是指向 Polygon 的指针(而不是指向 RectangleTriangle 的指针),所以只能访问从 Polygon 继承的成员,而不能访问派生类 RectangleTriangle 的成员。这就是为什么上面的程序直接使用 recttrgl 来访问它们的 area 成员,而不是通过指针访问;指向基类的指针不能访问 area 成员。

如果 areaPolygon 的成员而不是派生类的成员,那么可以通过指向 Polygon 的指针访问 area,但是问题在于 RectangleTriangle 实现了不同版本的 area,因此在基类中没有一个通用版本可以实现。

虚成员(Virtual members)

虚成员是指可以在派生类中重新定义的成员函数,同时通过引用保留其调用属性。要使函数成为虚函数,需要在其声明前加上 virtual 关键字:

// 虚成员
#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;
}

在这个例子中,所有三个类(PolygonRectangleTriangle)都有相同的成员:widthheight,以及函数 set_valuesarea

成员函数 area 在基类中被声明为虚函数,因为它在每个派生类中都会被重新定义。非虚成员也可以在派生类中重新定义,但通过基类的引用无法访问派生类的非虚成员:即,如果在上述例子中去掉 area 声明中的 virtual 关键字,所有三次对 area 的调用都将返回零,因为在所有情况下,都会调用基类的版本。

因此,virtual 关键字的作用是允许派生类中与基类同名的成员函数在从基类指针调用时能够正确地调用,特别是当指针类型是指向基类的指针并指向派生类的对象时,如上例所示。

声明或继承虚函数的类称为多态类

注意,尽管其成员之一是虚函数,Polygon 仍然是一个常规类,甚至还实例化了一个对象(poly),其成员 area 的定义始终返回 0。

抽象基类(Abstract base classes)

抽象基类与上例中的 Polygon 类非常相似。它们是只能用作基类的类,因此允许有不带定义的虚成员函数(称为纯虚函数)。语法是将它们的定义替换为 =0(等号和零):

抽象基类 Polygon 可能如下所示:

// 抽象类 Polygon
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;

并且当它们指向派生(非抽象)类的对象时,可以实际解引用。以下是完整的例子:

// 抽象基类
#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;
}

在这个例子中,不同但相关的对象类型使用统一的指针类型(Polygon*)进行引用,并且每次都调用正确的成员函数,只是因为它们是虚函数。这在某些情况下非常有用。例如,甚至可以让抽象基类 Polygon 的成员使用特殊指针 this 来访问正确的虚成员,即使 Polygon 本身没有这个函数的实现:

// 纯虚成员可以从抽象基类调用
#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) override
      { return (width * height); }
};

class Triangle: public Polygon {
  public:
    int area (void) override
      { 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;
}

override 关键字确保这些方法正确地覆盖了基类 Polygon 的纯虚函数 area。如果基类的函数签名发生了变化(例如函数名或参数列表变化),编译器会报错,而不是静默地生成一个新的成员函数。

虚成员和抽象类赋予了 C++ 多态特性,这在面向对象项目中非常有用。当然,上述示例是非常简单的用例,但这些特性可以应用于对象数组或动态分配的对象。

这里是一个结合了最近章节中的一些特性,如动态内存、构造函数初始化器和多态性的例子:

// 动态分配和多态性
#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() override
      { return width*height; }
};

class Triangle: public Polygon {
  public:
    Triangle(int a,int b) : Polygon(a,b) {}
    int area() override
      { 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;
}

注意 ppoly 指针:

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

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

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值