c++的多态是什么?

转自https://www.cnblogs.com/findumars/p/9845429.html

1、在C++中接口的多种不同的实现方式就是多态,是面向对象的核心,分为类的多态性和函数的多态性。

2、虚函数表:存在虚函数的类都存在一个一维的虚函数表叫做虚表,类的对象有一个指向虚表的虚指针,虚表和类是对应的,虚指针是和对象对应的。

单继承时虚函数表:

(1)无需函数覆盖

class Base
{
public:
	virtual void x() { cout << "Base::x()" << endl; }
	virtual void y() { cout << "Base::y()" << endl; }
	virtual void z() { cout << "Base::z()" << endl; }
};

class Derive : public Base
{
public:
	virtual void x1() { cout << "Derive::x1()" << endl; }
	virtual void y1() { cout << "Derive::y1()" << endl; }
	virtual void z1() { cout << "Derive::z1()" << endl; }
};

在这个单继承的关系中,子类没有重写父类的任何方法,而是加入了三个新的虚函数。Derive类实例的虚函数表布局如图示:

  • Derive class 继承了 Base class 中的三个虚函数,准确的说,是该函数实体的地址被拷贝到 Derive 实例的虚函数表对应的 slot 之中。
  • 新增的 虚函数 置于虚函数表的后面,并按声明顺序存放。

(2)有虚函数覆盖

   如果在继承关系中,子类重写了父类的虚函数:

class Base
{
public:
	virtual void x() { cout << "Base::x()" << endl; }
	virtual void y() { cout << "Base::y()" << endl; }
	virtual void z() { cout << "Base::z()" << endl; }
};

class Derive : public Base
{
public:
	virtual void x() { cout << "Derive::x()" << endl; }  // 重写
	virtual void y1() { cout << "Derive::y1()" << endl; }
	virtual void z1() { cout << "Derive::z1()" << endl; }
};

则Derive类实例的虚函数表布局为:

 

相比于无覆盖的情况,只是把 Derive::x() 覆盖了Base::x(),即第一个槽的函数地址发生了变化,其他的没有变化。

这时,如果通过绑定了子类对象的基类指针调用函数 x(),会执行 Derive 版本的 x(),这就是多态。

多继承虚函数表

(1)无虚函数覆盖

现有如下的多重继承关系,子类没有覆盖父类的虚函数:

class Base1
{
public:
	virtual void x() { cout << "Base1::x()" << endl; }
	virtual void y() { cout << "Base1::y()" << endl; }
	virtual void z() { cout << "Base1::z()" << endl; }
};

class Base2
{
public:
	virtual void x() { cout << "Base2::x()" << endl; }  
	virtual void y() { cout << "Base2::y()" << endl; }
	virtual void z() { cout << "Base2::z()" << endl; }
};

class Derive : public Base1, public Base2
{
public:
	virtual void x1() { cout << "Derive::x1()" << endl; }  
	virtual void y1() { cout << "Derive::y1()" << endl; }
};

对于 Derive 实例 d 的虚函数表布局,如下图:

可以看出:

  • 每个基类子对象对应一个虚函数表
  • 派生类中新增的虚函数放到第一个虚函数表的后面

(2)有序函数覆盖

将上面的多重继承关系稍作修改,让子类重写基类的 x() 函数:

class Base1
{
public:
	virtual void x() { cout << "Base1::x()" << endl; }
	virtual void y() { cout << "Base1::y()" << endl; }
	virtual void z() { cout << "Base1::z()" << endl; }
};

class Base2
{
public:
	virtual void x() { cout << "Base2::x()" << endl; }  
	virtual void y() { cout << "Base2::y()" << endl; }
	virtual void z() { cout << "Base2::z()" << endl; }
};

class Derive : public Base1, public Base2
{
public:
	virtual void x() { cout << "Derive::x()" << endl; }     // 重写
	virtual void y1() { cout << "Derive::y1()" << endl; }
};

相比于无覆盖的情况,只是将Derive::x()覆盖了Base1::x()Base2::x()而已,你可以自己写测试代码测试一下,这里就不再赘述了。

注:若虚函数是 private 或 protected 的,我们照样可以通过访问虚函数表来访问这些虚函数,即上面的测试代码一样能运行。

附:编译器对指针的调整

在多重继承下,我们可以将子类实例绑定到任一父类的指针(或引用)上。以上述有覆盖的多重继承关系为例:

Derive b;
Base1* ptr1 = &b;   // 指向 b 的初始地址
Base2* ptr2 = &b;   // 指向 b 的第二个子对象
  • 因为 Base1 是第一个基类,所以 ptr1 指向的是 Derive 对象的起始地址,不需要调整指针(偏移)。
  • 因为 Base2 是第二个基类,所以必须对指针进行调整,即加上一个 offset,让 ptr2 指向 Base2 子对象。
  • 当然,上述过程是由编译器完成的。
Base1* b1 = (Base1*)ptr2;  
b1->y();                   // 输出 Base2::y()
Base2* b2 = (Base2*)ptr1;   
b2->y();                   // 输出 Base1::y()

这里,由于ptr2指向的是Base2子对象的地址,因此尽管b1是Base1型指针,同时也将ptr2转换成了Base1,但ptr2的地址仍然是Base2的地址,因此调用y()也将调用Base2中的y();
同理,由于ptr1指向的是Base1的地址,因此尽管b2是Base2型指针,同时也将ptr1转换成了Base2,但ptr1的地址仍然是Base1的地址,因此调用y()也将调用Base1中的y()
其实,通过某个类型的指针访问某个成员时,编译器只是根据类型的定义查找这个成员所在偏移量,用这个偏移量获取成员。由于 ptr2 本来就指向 Base2 子对象的起始地址,所以b1->y()调用到的是Base2::y()而 ptr1 本来就指向 Base1 子对象的起始地址(即 Derive对象的起始地址),所以b2->y()调用到的是Base1::y()

3、多态用虚函数来实现,结合了动态绑定。

4、纯虚函数是虚函数再加上 = 0,纯虚函数不用实现。

5、抽象类至少包含一个纯虚函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值