【C++】浅析C++中的对象模型
来源:互联网 发布:德力西 知乎 编辑:程序博客网 时间:2024/04/26 17:23
以下代码运行环境: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
- 【C++】浅析C++中的对象模型
- 【C++】浅析C++中的对象模型
- Objective-C对象模型
- Objective-C对象模型
- objective C 对象模型
- C ++ 对象模型
- Objective-C 对象模型
- 【C++】对象模型
- Objective-C对象模型
- 【C++】虚函数在不同继承方式中的对象模型
- 浅析c/c++中的指针
- 浅析C语言中的指针
- Objective-c中的delegate浅析
- 【C++】浅析C++中的继承
- C/C++的对象模型
- Objective-C的对象模型
- 图解Objectvie-C对象模型
- C/C++的对象模型
- LeetCode: Search for a Range 解题报告
- hihocoder 136 #1269 优化延迟 二分+优先队列
- cocos2dx-lua 对lua项目中class(sub,super)的理解
- 《C++ Primer》读书笔记-第一章 Hello World
- getSystemService与getService区别
- 【C++】浅析C++中的对象模型
- struts2 无法获取action属性的问题解决
- LeetCode 16. 3Sum Closest
- CSS基础{精灵图、梅兰商贸}
- 第五周练习计划
- 三维几何,四面体(压纸器,LA 4795)
- JavaScript 实现面向对象(入门)
- Kafka之sync、async以及oneway
- eclipse的代码不全功能