C++对象模型(上)单继承

探索对象模型

先看一个例子:

 
 
 
  
class Base
{
public:
 virtual void func_1()     
    {} 
virtual void func_2()    
    {}
protected:     
    int _a;
};
int main()
{
    Base b1;
    Base b2; 
    system("pause");
    return 0;
}


_vfptr是虚表指针,指向这个类的虚表,事实上_vfptr是一个指针数组,里面放的都是虚函数的地址,并且这个数组以NULL结束。而且同一类对象公用一张虚表。


二、单继承对象模型
 
 
class Base
{
public:
	virtual void func_1()//虚函数
	{
		cout << "Base ::func_1()\n";
	}
	virtual void func_2()//虚函数
	{
		cout << "Base ::func_2()\n";
	}
protected:
	int _a;
};
class Derive :public Base
{
public:
	virtual void func_1()//重写了父类中的虚函数
	{
		cout << "Derive ::func_1()\n";
	}
	virtual void func_3()//Derive 类自己的虚函数
	{
		cout << "Derive ::func_3()\n";
	}
	void func_4()//普通成员函数
	{
		cout << "Derive ::func_4()\n";
	}
protected:
	int _b;
};

typedef void (*VFUNC)(void);//声明一个函数指针,注意尽量将虚函数的返回值和参数都定义为一样的

//打印虚表内容,想要查看虚表的内容
void PrintVTable(int *table)
{
	int i = 0;
	printf("table :0x%p\n", table);//打印虚表指针
	for (i = 0; table[i] != NULL; ++i)
	{
		printf("table[%d]:0x%p->", i,table[i]);//将虚表中的每一个虚函数地址打印出来
		VFUNC f = (VFUNC)table[i];//将虚函数的地址转换为函数指针
		f();//调用虚函数
	}
}
int main()
{
	Base b1;
	Derive d1;
	PrintVTable((int *)(*((int *)(&b1))));
	PrintVTable((int *)(*((int *)(&d1))));
	system("pause");
	return 0;
}
查看结果:

我们看到父类的虚表和子类的虚表并不是一张虚表,并且内容也不同了,子类的虚表是如何构成的,我们可以这样理解,子类独自开辟一段空间,先将父类的虚表内容相当于是复制下来,如果自己有将父类中虚函数进行了重写的,就将其覆盖为自己的,再将自己独有的虚函数放进去。
并且一个对象被创建时在初始化列表中将虚表指针进行了初始化。

重写也叫覆盖,这里就很好理解了,重写可以说是从类的角度看,子类将父类中的虚函数进行了重写,覆盖就可以从对象模型角度来看,子类虚表中对父类的虚函数进行了覆盖。

三、值得一看

1.虚表不安全的一点
虚表中存放了该类了所有虚函数,不论是私有的还是保护的,例如下面的例子
class Base
{
public:
 virtual void func_1()//虚函数
 {
  cout << "Base ::func_1()\n";
 }
 virtual void func_2()//虚函数
 {
  cout << "Base ::func_2()\n";
 }
protected:
 int _a;
 virtual void func_protect()//虚函数
 {
  cout << "Base ::func_protect()\n";
 }
private:
 virtual void func_private()//虚函数
 {
  cout << "Base ::func_private()\n";
 }
};
class Derive :public Base
{
public:
 virtual void func_1()//重写了父类中的虚函数
 {
  cout << "Derive ::func_1()\n";
 }
 virtual void func_3()//Derive 类自己的虚函数
 {
  cout << "Derive ::func_3()\n";
 }
 void func_4()//普通成员函数
 {
  cout << "Derive ::func_4()\n";
 }
protected:
 int _b;
};
typedef void (*VFUNC)(void);//声明一个函数指针,注意尽量将虚函数的返回值和参数都定义为一样的
//打印虚表内容,想要查看虚表的内容
void PrintVTable(int *table)
{
 int i = 0;
 printf("table :0x%p\n", table);//打印虚表指针
 for (i = 0; table[i] != NULL; ++i)
 {
  printf("table[%d]:0x%p->", i,table[i]);//将虚表中的每一个虚函数地址打印出来
  VFUNC f = (VFUNC)table[i];//将虚函数的地址转换为函数指针
  f();//调用虚函数
 }
}
int main()
{
 Base b1;
 Derive d1;
 PrintVTable((int *)(*((int *)(&b1))));
 PrintVTable((int *)(*((int *)(&d1))));
 system("pause");
 return 0;
}

结果:

我们发现两个问题:

(1)我们在类外面可以访问到类中的私有成员
(2)父类中的私有成员在子类中应该是不可见的,但是这里却将私有成员暴露出来

2.打印虚表函数
上面的函数传参是这样理解的

这里是在32位2平台下,一个指针的大小是4个字节,如果是在64位平台下,一个指针的大小为8个字节,我们就可以用

long long 来代替int,但是为了更好的适应不同的平台下指针的大小,我们可以用下列的形式,每次去取出的都是对象开始的
sizeof(int *)【一个指针大小】个字节。


PrintVTable(  (int **) (  *(  (int **) (&b1) )  ) );

void PrintVTable(int **table);
本篇完,下一篇,探索多继承体系下的对象模型

猜你喜欢

转载自blog.csdn.net/misszhoudandan/article/details/80176531