首页 > 编程 > C++ > 正文

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

2019-11-11 05:39:12
字体:
来源:转载
供稿:网友

以下代码运行环境: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类对象的调试信息:下边,根据调试信息画出内存分布:文中涉及到的内容链接:内存对齐重载、重写、重定义详解重载参考文档:对象模型(一)对象模型(二)


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选