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

(一)不含有虚函数的单一继承模型:
测试代码:
//单一继承,无虚函数
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类对象的调试信息:



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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值