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
因为继承的缘故,内部有mi
、getI()、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存放的起始地址