【C++】浅析C++中的对象模型

17 篇文章 1 订阅

以下代码运行环境:windows8.1 32位 VS2015

(一)不含有虚函数的单一继承模型:
测试代码:
//单一继承,无虚函数
class A
{
public:
       A(int a = 0, char c = 0)
              :_a(a)
              , _c(c)
       {}
       int GetA()
       {
              return _a;
       }
       static int staticFun()
       {
              return _val;
       }
       static int _val;
private:
       int _a;
       char _c;
};
class B :public A
{
public:
       B(char b = 0)
              :_b(b)
       {}
       char GetB()
       {
              return _b;
       }
private:
       char _b;
};
int A::_val = 100;
void test1()
{
       A a(10,'a');
       B b('b');
}

现象以及分析:
关于类的静态成员,我们需要知道以下几点:
(1)类的静态成员是属于类而不属于对象,所以他不是类的单个对象所有。
(2)静态成员只存在一个,不像普通的成员,每创建一个对象,就会创建一组普通的成员。
(3)静态成员的初始化不能在类中,肯定是不能在构造函数中,只能在类外并且在main函数之前,按照这样的格式进行初始化:int A::_val = 100。并且是先初始化再使用。
(4)在静态成员函数中不可以使用非静态成员。因为非静态成员是属于对象,而类的静态成员函数是属于类的,在类的对象实例化之前就已经完成了初始化。如果在静态成员函数用引用非静态成员,就好比是使用一个并没有完成初始化的变量。
(5)类的非静态成员函数中可以引用类的静态成员。反之不可以。
(6)静态成员函数没有this指针。

了解了静态成员的这些特性(当然本文主要是来分析对象模型的),我们在分析对象模型的时候,可以顺便来看看静态成员到底存储在哪里?
首先看一下上边代码的内存分布:

在程序调试中,是这样表现出来的:
这里,我们只是看到的A类的普通数据成员继承给子类(上述图中,子类自称自父类的成员默认是0,是因为我们的父类的默认构造函数给出的默认值是0),下边我们来看一下A类的静态成员是存储在哪里?是否会继承给子类?

从这里我们可以看出,父类的静态成员继承给子类,并且两者是存储在一个地址上的。这里就验证了这样的一句话:父类中定义了静态成员,则整个继承体系中只有一个这样的成员,无论派生出多少个子类。静态成员是存储在全局区的。

(二)含有虚函数的单一继承模型:
测试代码:
class A
{
public:
       virtual void foo()
       {
              cout << "A::foo()" << endl;
       }
       virtual void funA()
       {
              cout << "A::funA()" << endl;
       }
private:
       int _a;
       char _c;
};
class B :public A
{
public:
       virtual void foo()
       {
              cout << "B::foo()" << endl;
       }
       virtual void funB()
       {
              cout << "B::funB()" << endl;
       }
private:
       char _b;
};
void test2()
{
       A a;
       B b;
}
内存分布:

结合程序的调试进行分析:

结合程序的调试信息进行分析:

下边我们来写一个函数来打印出我们的虚表,看看与我们分析的是否一样~~
void PrintTable(int* vTable)
{
       typedef void(*pFun)();
       cout << "虚表地址为:" << vTable << endl;
       for (int i = 0; vTable[i] != NULL; ++i)
       {
              printf("第%d个虚表地址为:%p->", i, vTable[i]);
              pFun f = (pFun)vTable[i];
              f();
       }
       cout << endl;
}
void test2()
{
       A a;
       B b;
       int* vTable1 = (int*)(*(int*)&a);
       int* vTable2 = (int*)(*(int*)&b);
       PrintTable(vTable1);
       PrintTable(vTable2);
}
运行结果:

【总结】:
(1)一个类只要有一个虚函数,它就会被编译器分配一个虚表指针,也就是__vfptr,用来存储虚函数的地址;
(2)子类的虚函数表是在父类的虚函数表上进行修改的,就像上边的对象模型所示,B类的虚函数就是在A类的虚函数之后;
(3)父类中的虚函数被子类改写,也就是说,子类中含有与父类的虚函数 函数名相同,参数列表相同,返回值相同的函数(协变除外),这样就构成了重写。下边再次区分几个容易混淆的概念--重载、重写(覆盖)、重定义(隐藏)。

重载--在同一个作用域内,函数名相同,参数列表不同,返回值可以相同可以不同的两个函数可以构成重载,需要声明的是,c++语言中支持函数的重载,而c语言中不支持函数的重载。原因是c语言和c++对函数的处理是不同的。具体可以点击文末的链接。
重写(覆盖)--在不同的作用域中(分别在父类和子类中),函数名相同,参数列表,返回值都相同(协变除外),并且父类的函数是虚函数,访问限定符可同可不同的两个函数就构成了重写。
重定义(隐藏)--在不同的作用域中(分别在父类和子类),函数名相同,只要不是构成重写就是重定义。
     协变: 协变也是一种重写,只是父类和子类中的函数的返回值不同,父类的函数返回父 类的指针或者引用,子类函数返回子类的指针或者引用。

(4)只有类的成员函数才可以被定义为虚函数,静态成员函数不可以被定义为虚函数。

(三)多继承的对象模型
测试代码:
class A
{
public:
       virtual void foo()
       {
              cout << "A::foo()" << endl;
       }
       virtual void funA()
       {
              cout << "A::funA()" << endl;
       }
private:
       int _a;
};
class B
{
public:
       virtual void foo()
       {
              cout << "B::foo()" << endl;
       }
       virtual void funB()
       {
              cout << "B::funB()" << endl;
       }
private:
       int _b;
};
class C :public A, public B
{
public:
       virtual void foo()
       {
              cout << "C::foo()" << endl;
       }
       virtual void funC()
       {
              cout << "C::funC()" << endl;
       }
private:
       int _c;
};
void test3()
{
       C c;
}
下边我们先通过调试信息,看看对象的内存分布:

通过这个图,我们就可以画出多继承的对象的内存分布:

下边我们仍然编写一个函数打印出虚函数表:
void PrintTable(int* vTable)
{
       typedef void(*pFun)();
       cout << "虚表地址为:" << vTable << endl;
       for (int i = 0; vTable[i] != NULL; ++i)
       {
              printf("第%d个虚函数地址为:0x%p->", i, vTable[i]);
              pFun f = (pFun)vTable[i];
              f();
       }
       cout << endl;
}
void test3()
{
       C c;
       int* vTable = (int*)(*(int*)&c);
       PrintTable(vTable);
       vTable = (int *)(*((int*)&c + sizeof(A) / 4));
       PrintTable(vTable);
}
程序运行结果:

【总结】
在多重继承体系下,有n个含有虚函数的父类,派生类中就有n个虚函数表,最终子类的虚函数是在第一个父类的虚函数表中;

(四)含有虚继承的多重继承模型(含有虚函数)
测试代码:
class A
{
public:
	A()
		:_a(1)
	{}
	virtual void foo()
	{
		cout << "A::foo()" << endl;
	}
	virtual void funA()
	{
		cout << "A::funA()" << endl;
	}
private:
	int _a;
};
class B : public virtual A
{
public:
	B()
		:_b(2)
	{}
	virtual void foo()
	{
		cout << "B::foo()" << endl;
	}
	virtual void funB()
	{
		cout << "B::funB()" << endl;
	}
private:
	int _b;
};
class C : public virtual A
{
public:
	C()
		:_c(3)
	{}
	virtual void foo()
	{
		cout << "C::foo()" << endl;
	}
	virtual void funC()
	{
		cout << "C::funC()" << endl;
	}
private:
	int _c;
};
class D :public B, public C
{
public:
	D()
		:_d(4)
	{}
	virtual void foo()
	{
		cout << "D::foo()" << endl;
	}
	virtual void FunD()
	{
		cout << "D::funD()" << endl;
	}
private:
	int _d;
};


通过调试信息看对象模型:
B类对象的调试信息:

C类对象的调试信息:

D类对象的调试信息:



下边,根据调试信息画出内存分布:



文中涉及到的内容链接:
参考文档:

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值