54、被遗弃的多重继承(下)

1、多重继承的问题3

  • 多重继承可能产生多个虚函数表
    在这里插入图片描述
    如果 BaseA 和 BaseB 这两个类里面都有定义虚函数的话,都会有虚函数表,由于继承的本质就是成员叠加,所以子类 Derived 的内部就会有两个虚函数表指针,分别指向不同的虚函数表。
#include <iostream>
#include <string>
using namespace std;

class BaseA
{
public:
	virtual void funcA()
	{
		cout << "BaseA::funcA()" << endl;
	}
};
class BaseB
{
public:
	virtual void funcB()
	{
		cout << "BaseB::funcB()" << endl;
	}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
	Derived d;
	cout << "sizeof(d) = " << sizeof(d) << endl;

	BaseA* pa = &d;
	BaseB* pb = &d;
	
	BaseB* pbb = (BaseB*)pa;
	BaseB* pbc = dynamic_cast<BaseB*>(pa);

	pa->funcA();
	pb->funcB();
	
	pbb->funcB();
	pbc->funcB();

	cout << "pa = " << pa << endl;
	cout << "pb = " << pb << endl;
	
	cout << "pbb = " << pbb << endl;
	cout << "pbc = " << pbc << endl;
	return 0;
}

在这里插入图片描述
由上一课已知, pa 指针和 pb 指针都指向对象d,但是两个指针指向的位置不一样,所以指针的值就不同。
从第27行代码可以看出,父类有虚函数,子类就会继承虚函数指针,Derived 这个子类就有两个虚函数指针。所以我们可以得到它占用的内存大小为8个字节。如果有两个虚函数指针,会出现一些我们意想不到的情况(错误)。比如在程序的29行和30行,我们用子类对象的地址来初始化父类对象的指针。这时候子类对象会退化为父类对象,初始化没有问题。但是在程序的31行,把一个BaseA* 类型的指针强制转换成BaseB* 类型的指针时,指针pbb指向的内容竟然还是类BaseA里面的内容,这就是我们所说的意想不到的情况。
在这里插入图片描述
为什么会这样呢?

pa->funcA();就会在 pa 指向的地址里面找到虚函数指针 vptr1,通过这个指针找到vptr1对应的虚函数,从而调用虚函数。

pb->funcB();就会在 pb 指向的地址里面找到虚函数指针 vptr2,通过这个指针找到vptr2对应的虚函数,从而调用虚函数。

pbb->funcB();pbb指向的地址其实就是 pa 指向地址的地方,找到的是vptr1,通过这个指针找到vptr1对应的虚函数,从而调用虚函数。

真正想得到我们想要的结果就要用C++里面新式类型转换关键字 dynamic_cast。在与继承和虚函数相关的强制类型转换我们就用 dynamic_cast。

2、正确的使用多继承

  • 工程开发中的 “多重继承” 方式:
    单继承某个类 + 实现(多个接口)
#include <iostream>
#include <string>
using namespace std;

class Base
{
protected:
	int mi;
public:
	Base(int i)
	{
		mi = i;
	}
	int getI()
	{
		return mi;
	}
	bool equal(Base* p)
	{
		return p == this;
	}
};
class Interface1
{
public:
	virtual void add(int i) = 0;
	virtual void minus(int i) = 0;
};
class Interface2
{
public:
	virtual void multiply(int i) = 0;
	virtual void divide(int i) = 0;
};
class Derived :public Base, public Interface1, public Interface2
{
public:
	Derived(int i):Base(i)
	{
	}
	void add(int i)
	{
		mi += i;
	}
	void minus(int i)
	{
		mi -= i;
	}
	void multiply(int i)
	{
		mi *= i;
	}
	void divide(int i)
	{
		if (i != 0)
		{
			mi /= i;
		}
	}
};
int main()
{
	Derived d(100);
	Derived* p = &d;
	Interface1* pInt1 = &d;
	Interface2* pInt2 = &d;

	cout << "d.getI() = " << d.getI() << endl;

	//本质是多态
	pInt1->add(10);
	pInt2->divide(11);
	pInt1->minus(5);
	pInt2->multiply(8);
	cout << "d.getI() = " << p->getI() << endl;

	cout << "pInt1 == this : " << p->equal(dynamic_cast<Base *>(pInt1)) << endl;
	cout << "pInt2 == this : " << p->equal(dynamic_cast<Base *>(pInt2)) << endl;
	return 0;
}

在这里插入图片描述

正确的使用多重继承

  • 一些有用的工程建议
    1、先继承一个父类,然后实现多个接口
    2、父类中提供 equal( ) 函数
    3、equal ( ) 成员函数用于判断指针是否指向当前对象
    4、与多重继承相关的强制类型转换用dynamic_cast 完成

小结

  • 多继承中可能出现多个虚函数表指针
  • 与多继承相关的强制类型转换用 dynamic_cast 完成
  • 工程开发中采用 "单继承多接口"的方式使用多继承
  • 父类提供成员函数用于判断指针是否指向当前对象
#include <iostream>

using namespace std;

class Parent
{
public:
    virtual void print()
    {
        cout << "class Parent" << endl;
    }
};

class Child : public Parent
{
public:
    void print()
    {
        cout << "class Child" << endl;
    }
};

int main()
{
    Child c;
    Parent* p = &c;

    p->print();

    return 0;
}

#include <iostream>
using namespace std;

class Demo
{
protected:
    int mi;
public:
    Demo(int i)
    {
        mi = i;
    }

    int getI()
    {
        return mi;
    }

    void print()
    {
        cout << this << endl;
    }

    bool equal(Demo* p)
    {
        return p == this;
    }
};

class Interface1
{
public:
    virtual void add(int n) = 0;
    virtual void minus(int n) = 0;
};

class Interface2
{
public:
    virtual void multiply(int n) = 0;
    virtual void divide(int n) = 0;
};

class Derived : public Demo, public Interface1, public Interface2
{
public:
    Derived(int i):Demo(i)
    {

    }
    void add(int n)
    {
        mi += n;
    }

    void minus(int n)
    {
        mi -= n;
    }

    void multiply(int n)
    {
        mi *= n;
    }

    void divide(int n)
    {
        mi /= n;
    }

    void print()
    {
        cout << this << endl;
    }

};

int main()
{
    Derived d(100);
    cout << sizeof(d) << endl;

    Interface1* p1 = &d;
    Interface2* p2 = &d;
    cout << d.getI() << endl;
    d.add(10);
    cout << d.getI() << endl;

    p1->add(10);
    p2->divide(2);
    p1->minus(50);
    p2->multiply(3);

    cout << d.getI() << endl;
    cout << endl;

    cout << d.equal(dynamic_cast<Demo*>(p1)) << endl;
    cout << d.equal(dynamic_cast<Demo*>(p2)) << endl;
    cout << endl;

    cout << &d << endl;
    d.print();

    cout << endl;
    d.Demo::print();
    cout << p1 << endl;
    cout << p2 << endl;

    return 0;
}

在这里插入图片描述

正确理解上诉程序:

父类指针指向子类对象,如果没有虚函数,那么子类对象退化为父类对象, p->print() 输出的就是父类中的函数;如果定义了虚函数,那么就存在虚函数指针,这就是多态的变式,Parent* p = &c;,就等同于函数的参数,那么 p->print() 输出的就是子类中的函数。

再回到上面的程序,class Derived 因为继承的缘故,内部有migetI()、equal、print 和重写的4个加减乘除函数和一个print函数mi等同于d.paernt::mi,由于两个接口的指针都指向子类,同时是虚函数的缘故,根据上一段分析的多态原因,所以程序调用的四个加减乘除函数都是子类重写的函数。

整个class Derived有三段地址
在这里插入图片描述

通过cout << &d << endl;
d.print();
能得到 class Derived 的起始地址。
通过 d.parent::print(); 能得到 class Derived 中继承于父类Demo存放的起始地址。如果我没有在子类中重写print函数,那么d.print得到是class Derived 中继承于父类Demo存放的起始地址
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值