C++之菱形继承与虚继承(含虚函数)

面向对象的三大特征: 封装,多态,继承
前面我们已经讲了继承的一些知识点,在这基础上,我们讲的时候再涉猎一些多态的只是。
下面我们先接着上次讲有虚函数的菱形虚继承
首先什么是虚函数。?
虚函数:在类里面,函数前面有virtual关键字的成员函数就是虚函数。
代码块:
class base
{
public:
 base()
 {
  cout << "base()" << endl;
 }
 ~base()
 {
  cout << "~base()" << endl;
 }
 virtual void dis()//加了关键字virtual,dis就成了虚函数
 {
  cout << "pub=" << pub;
  cout << "     pro=" << pro;
  cout << "     pri=" << pri << endl;
 }
 int pub;
protected:
 int pro;
private:
 int pri;
};

我们先讲几个知识点:
1.虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。
2.在一个类中, 成员函数有虚函数的话,那么,这个对象的前四个字节一定是存放一个指向这个虚函数表(简称虚表)    的指针。虚表里面放的是虚函数的地址
3.对于菱形继承这样,有 多个基类的类对象,则会有多个虚表,每一个基类对应一个虚表,同时,虚表的顺序和继承时的顺序相同。
4.即使是存在虚基类指针, 虚表指针也是在虚基类指针的上方,这是为了保证正确取到虚函数的偏移量。
举个简单的例子
代码块:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
using namespace std;
class base
{
public:
 base(int a=1,int b=2,int c=3)
  : pub(a)
  , pro(b)
  , pri(c)
 {
  cout << "base()" << endl << endl;
 }
 ~base()
 {
  cout << "~base()" << endl << endl;
 }
 virtual void dis()//加了关键字virtual,dis就成了虚函数
 {
  cout << "dis()" << endl<<endl;
  cout << "pub=" << pub;
  cout << "     pro=" << pro;
  cout << "     pri=" << pri << endl << endl;
 }
 virtual void func()//同上,虚函数
 {
  pub *= 2;
  pro *= 2;
  pri *= 2;
 }
 int pub;
protected:
 int pro;
private:
 int pri;
};
void Test()//析构函数只在函数体结束时候调用,所以在main函数里声明不好看到析构函数
{
  base b;
  cout << "sizeof(b)=" << sizeof(b) << endl << endl;
}
int main()
{
 Test();
 system("pause");
 return 0;
}

运行结果:

分析:
在成员函数dis(),func()没有加上virtual之前,b对象的大小等于12
加上virtual之后输出size=16,说明多出了四个字节。
在调用监视和内存查看,在对象最开始的四个字节确实是多了一个_vfptr指针
我们又在内存窗口输入_vfptr的地址0x00F9DC74,发现里面的两个地址确实和dis(),func()一一对应,也印证了我们上面的观点。

上一篇文章我们讲了没有虚函数的菱形继承和虚继承。
下面我们在观察一下 带虚函数的菱形虚继承的对象模型是怎样的
这里有两种情况:
1.ABCD四个类的虚函数都是virtual void dis(),谁也不多谁也不少。
代码块:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<iostream>
using namespace std;
typedef void(*FUNC) ();//函数指针
class A
{
public:
	A(int x = 1)
		:a(x)
	{
		cout << "A类构造函数----A()" << endl << endl;
	}
	~A()
	{

		cout << "A类析造函数----~A()" << endl << endl;
	}
	virtual void dis()//记住这个dis(),每个类都有这个虚函数
	{
		cout << "a=" << a << endl;
	}
	int a;//把a放在公有位置,便于类外访问
};
class B :virtual public A//加上了关键字virtual,变成了虚继承
{
public:
	B(int y = 2)
		:b(y)
	{
		cout << "B类构造函数----B()" << endl << endl;
	}
	~B()
	{
		cout << "B类析构函数----~B()" << endl << endl;
	}
	virtual void dis()//记住这个dis(),每个类都有这个虚函数
	{
		cout << "b=" << b << endl;
	}
private:
	int b;
};

class C :virtual public A//加上了关键字virtual,变成了虚继承
{
public:
	C(int z = 3)
		:c(z)
	{
		cout << "C类构造函数----C()" << endl << endl;
	}
	~C()
	{
		cout << "C类析构函数----~C()" << endl << endl;
	}
	virtual void dis()//记住这个dis(),每个类都有这个虚函数
	{
		cout << "c=" << c << endl;
	}
private:
	int c;
};
class D :public B, public C//分别公有继承B,C
{
public:
	D(int z1 = 4)
		:d(z1)
	{
		cout << "D类构造函数------D()" << endl << endl;
	}
	~D()
	{
		cout << "D类析构函数------~D()" << endl << endl;
	}
	virtual void dis()//记住这个dis(),每个类都有这个虚函数
	{
		cout << "d=" << d << endl;
	}
private:
	int d;
};

void Test()
{
	D obj;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

分析:
它有一个虚基类A,所以存在虚基表指针;
它每个类还有虚函数,所以存在,虚表指针;
虚表指针位于虚基表指针上方(这个第二种情况解释) ;
但是这里比较特殊的就是每个类中的虚函数都是一样的,所以就构成了这种特殊的对象模型。
在这里我们通过调用监视和查看内存来观察:
截图:
通过上图我们知道,要是 每个类都有一样的虚函数,则虚表指针就不会存在与每一个对象中,而是只在虚基类的前四个字节,然后这个指针就指向了存放着统一的虚函数的虚表。

2.这种情况就是每个类的虚函数存在着不同。
代码块:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<iostream>
using namespace std;
typedef void(*FUNC) ();//函数指针
class A
{
public:
 A(int x = 1)
  :a(x)
 {
  cout << "A类构造函数----A()" << endl << endl;
 }
 ~A()
 {
  cout << "A类析造函数----~A()" << endl << endl;
 }
 virtual void dis()//记住这个dis(),每个类都有这个虚函数
 {
  cout << "A::dis()" << endl;
 }
 int a;//把a放在公有位置,便于类外访问
};
class B :virtual public A//加上了关键字virtual,变成了虚继承
{
public:
 B(int y = 2)
  :b(y)
 {
  cout << "B类构造函数----B()" << endl << endl;
 }
 ~B()
 {
  cout << "B类析构函数----~B()" << endl << endl;
 }
 virtual void dis()//记住这个dis(),每个类都有这个虚函数
 {
  cout << "B::dis()" << endl;
 }
 virtual void func()//和D类相同的虚函数func()
 {
  cout << "B::func()" << endl;
 }
 virtual void func2()//B类自己的虚函数
 {
  cout << "B::func2()" << endl;
 }
 int b;
};
class C :virtual public A//加上了关键字virtual,变成了虚继承
{
public:
 C(int z = 3)
  :c(z)
 {
  cout << "C类构造函数----C()" << endl << endl;
 }
 ~C()
 {
  cout << "C类析构函数----~C()" << endl << endl;
 }
 virtual void dis()//记住这个dis(),每个类都有这个虚函数
 {
  cout << "C::dis()" << endl;
 }
 
 virtual void func3()//C类自己的虚函数
 {
  cout << "C::func3()" << endl;
 }
 int c;
};
class D :public B, public C//分别公有继承B,C
{
public:
 D(int z1 = 4)
  :d(z1)
 {
  cout << "D类构造函数------D()" << endl << endl;
 }
 ~D()
 {
  cout << "D类析构函数------~D()" << endl << endl;
 }
 virtual void dis()//记住这个dis(),每个类都有这个虚函数
 {
  cout << "d::dis()" << endl;
 }
 virtual void func()//和B类相同的虚函数func()
 {
  cout << "D::func()" << endl;
 }
 virtual void func4()//D类自己的虚函数
 {
  cout << "D::func4()" << endl;
 }
 int d;
};
void PrintVTable(int* VTable)
{
 cout << " 虚表地址-->" << VTable << endl;
 for (int i = 0; VTable[i] != 0; ++i)
 {
  printf(" 第%d个虚函数地址 :0X%x,-->", i, VTable[i]);
  FUNC f = (FUNC)VTable[i];//函数指针
  f();//执行这个函数
 }
 cout << endl;
}
int main()
{
 D obj;
 A obj_a;
 B obj_b;
 C obj_c;
 int* VTable1 = (int*)(*(int*)&obj);//obj的地址,通过一系列的类型转换,所以指针这块要熟
 PrintVTable(VTable1);
 int* VTable2 = (int*)(*(int*)&obj+32);//obj_a的地址,我是知道对象模型然后把代码写死了,你们可以尝试其它方法
 PrintVTable(VTable2);
 int* VTable3 = (int*)(*(int*)&obj_b);//obj_b的地址
 PrintVTable(VTable3);
 int* VTable4 = (int*)(*(int*)&obj_c);//obj_c的地址
 PrintVTable(VTable4);
 cout << "sizeof (obj)=" << sizeof(obj) << endl;//obj对象的大小,一开始以为是36,但看内存发现多了一串0占了四个字节
 cout << "sizeof (obj_a)=" << sizeof(obj_a) << endl;
 cout << "sizeof (obj_b)=" << sizeof(obj_b) << endl;
 cout << "sizeof (obj_c)=" << sizeof(obj_c) << endl;
 system("pause");
 return 0;
}

运行结果加后期
截图1:

截图2:

模型草图:

分析:
先了解一下多态:
概念:多态就是多种形态,C++的 多态分为静态多态和动态多态
1. 静态多态:主要通过函数的重载和模板来实现,是在编译时决定,也叫编译时多态。
2. 动态多态:就是通过继承重写基类的虚函数实现的多态,是在运行时决定,也叫运行时多态。

结合上一个例子模型的分析,这里的不同就是每个类里面的虚函数不一样了,所以它的对象模型也发生了改变,通过内存窗口相信大家也大概知道了是怎样的一个布局。
很多东西在截图上面都画出来了,所以我也不知道该怎么再分析了
相信大家通过自己动手去实现一遍,会有更加深刻的了解。。

猜你喜欢

转载自blog.csdn.net/komacc/article/details/80210947