C++面向对象编程
C++对象模型:
语言中直接支持面向对象程序设计的部分。
对于各种支持的底层实现机制。
1.直接支持面向对象程序设计,包括了构造函数、析构函数、多态、虚函数等等
2.对象模型的底层实现机制并未标准化,不同的编译器有一定的自由来设计对象模型的实现细节
虚函数表解析。含有虚函数或其父类含有虚函数的类,编译器都会为其添加一个虚函数表,vptr,
C++类对象在下面情形中的内存布局: 单继承 多继承 虚继承
C++中虚函数的作用主要是为了实现多态机制。多态,
简单来说,是指在继承层次中,父类的指针可以具有多种形态——当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的函数
有两种数据成员(class data members):static 和 nonstatic,
以及三种类成员函数(class member functions):static、nonstatic 和virtual:
继承下的C++对象模型
C++“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。
在C++中,我们通过类来将属性与操作绑定在一起
属于类而不属于类对
虚函数机制(virtual function) 虚基类 (virtual base class)
影响对象大小的因素
0. 成员变量 1. 虚函数表指针(_vftptr) 2. 虚基类表指针(_vbtptr) 3. 内存对齐
Linux
通过 GDB 查看对象的内存布局,探讨成员变量、成员方法、虚函数表等在内存中的存储位置和实现细节
class 是一种特殊的 struct 运行时的对象退化为结构体的形式
一个类而言,成员变量和成员函数是分开存放的
valgrind 工具
stace ./demo
valgrind ./demo
windows 编译器提供了/d1 reportSingleClassLayout[类名]编译选项来查看对象完整的内存布局:
面向过程
程序=数据结构+算法
面向过程是围绕功能进行的,为每一个功能写一个函数,需要考虑其中的每一个细节,以步骤划分
其重点在于过程和函数的实现,强调算法的流程和逻辑控制
职业化军团
面向对象
从语言开发角度 : 模块化的组织代码,降低了大军团作战的门槛
面向对象的程序= N个对象(算法+数据结构)+控制信息
面向对象= 对象+交互-消息传递
标注的开发方式:面向对象,更多的是要进行子模块化的设计,每一个模块都需要单独存在
面向对象编程思想:将生活逻辑映射到我们的程序里
编译器
编译器前端 编译器中端 编译器后端
编译器后端并不是一个严格的流水线式结构
编译器角度
编译器中实现面向对象功能
从编译器的视角来看,面向对象语言重组了程序的命名空间
一个对象就是:“一块可以用来存储值的内存区域”
将源语言名字映射到运行时地址,使得编译后代码可以访问与名字关联的数据
编译器必须跟踪方法内部和类内部的作用域规则建立的名字/作用域的层次结构,
同样还需要跟踪通过扩展建立的子类和超类的继承层次结构所产生的名字/作用域的层次结构
编译器对于成员函数的处理方法是把成员函数转化成类似于C语言中的普通函数,转化之后编译器就能像编译C语言的函数一样编译成员函数。
编译器对于虚函数的实现主要通过增加虚函数指针和虚函数表的方式来实现。
汇编角度
变量
复合变量
数组:
数组和普通的变量都是栈上分配的内存,只是数组是连续的。若数组太大,会导致栈溢出。
数组中的中括号会编译成(数组首地址+索引*偏移量)计算具体地址。
所以数组和指针本质是相同的,都是操作地址
结构体: 汇编代码和通过"首地址+偏移量"确定变量本质是一样的。
枚举变量都被实际值替换,类似宏定义
每个对象都有个指向该对象的this指针,对象通过this指针调用成员或函数。
CPU执行角度 以及内存布局
一个对象在内存中的布局取决于以下因素:
成员变量的类型和顺序。
对象可能有的任何内置成员函数(如构造函数、析构函数等)。
虚函数和虚继承(如果有)可能引入的额外开销,例如虚函数表(vtable)和虚基类指针。
编译器对于优化的可能性
成员变量:类的成员变量存储了对象的属性信息。每个对象都有自己的成员变量副本
成员函数:类的成员函数定义了对象的行为。成员函数不与特定的对象实例关联,而是与类本身关联。因此,成员函数通常只在内存中存储一份,供所有对象共享
虚函数表指针(如果有):如果类中有虚函数,编译器会为类生成一个虚函数表,用于实现动态绑定。虚函数表指针指向这个虚函数表。
当创建一个类的对象时,编译器会为对象在堆或栈上分配内存空间。
分配的空间大小等于类中所有成员变量的大小之和,
加上可能的内存对齐填充。
对于包含虚函数的类,还需要额外的空间存储虚函数表指针。
历史的教训
C语言教材都有个巨大的问题是不引导学生去用Linux,C语言上来就集中在print的格式繁杂中,捡不了的芝麻,忽视了西瓜
web技术实际上已经渗透到了编程的方方面面
在面向对象的程序课里面,一定要积累3000行左右的代码的开发经验
数据结构---栈和递归 递归是一种在对象定义和过程调用中自我包含的概念
仓颉
仓颉编程语言中,通过关键字 class 定义一个类
classDefinition
: classModifierList? 'class' identifier
typeParameters?
('<:' superClassOrInterfaces)?
genericConstraints?
classBody
;
superClassOrInterfaces
: classType ('&' superInterfaces)?
| superInterfaces
;
classBody
: '{'
classMemberDeclaration*
classPrimaryInit?
classMemberDeclaration*
'}'
;
classMemberDeclaration
: classInit
| staticInit
| variableDeclaration
| functionDefinition
| operatorFunctionDefinition
| macroExpression
| propertyDefinition
| classFinalizer
;
classPrimaryInit
: classNonStaticMemberModifier? className '(' classPrimaryInitParamLists? ')'
'{'
superCallExression?
( expression
| variableDeclaration
| functionDefinition)*
'}'
;
className
: identifier
;
classPrimaryInitParamLists
: unnamedParameterList (',' namedParameterList)? (',' classNamedInitParamList)?
| unnamedParameterList (',' classUnnamedInitParamList)?
(',' classNamedInitParamList)?
| classUnnamedInitParamList (',' classNamedInitParamList)?
| namedParameterList (',' classNamedInitParamList)?
| classNamedInitParamList
;
classUnnamedInitParamList
: classUnnamedInitParam (',' classUnnamedInitParam)*
;
classNamedInitParamList
: classNamedInitParam (',' classNamedInitParam)*
;
classUnnamedInitParam
: classNonStaticMemberModifier? ('let'|'var') identifier ':' type
;
classNamedInitParam
: classNonStaticMemberModifier? ('let'|'var') identifier'!' ':' type ('=' expression)?
;
classNonStaticMemberModifier
: 'public'
| 'protected'
| 'internal'
| 'private'
;
Hotspot虚拟机 对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
1. 对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,
如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
对象头的另外一部分是类型指针,即对象指向他的类元素数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
2. 实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各自类型的字段内容
参考
自己动手写编译器 https://pandolia.net/tinyc/ch2_TinyC_syntax.html
基于LLVM 自制编译器—
https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html