面向对象只是一种设计方法,是一种概念,不要和编程语言混为一谈。
面向对象思想的核心是把数据和处理数据的方法封装在一起。 首先这里的封装,不是指放在同一个结构体里这种形式,只要逻辑上在一起就算在一起,比如放在同一个接口头文件里(也就是.h),也是一种形式——即,一个接口头文件提供了数据的结构体,以及处理这些数据的方法(函数原型声明),这已经完成了面向对象所需的最基本要求。
在数据结构里,类,其实属于 ADT(Abstract Data Type), 类型 = 大小(对齐信息) + 操作集合;变量(对象 )= 地址值 + 类型信息;
一个变量(Variable),使用面向对象(OO)的概念,我们统一称为对象(Object),除了保存于其中的内容以外,只有三个要素:
地址数值(Address Value) 地址的数值是一个无符号整数,其位宽由CPU的地址总线宽度所决定。话虽如此,其实主要还是编译器在权衡了“用户编写代码的便利性”以及“生成机器码的效率”后为我们提供的解决方案:例如,针对8位机,编译器普遍以等效为uint16_t的整数来保存地址信息;针对16位机和32位机,编译器则普遍选择uint32_t的整数来保存地址信息;针对64位机,编译器则可能会提供两种指针类型,分别用来对应uint32_t的4G地址空间和由uint64_t所代表的恐怖地址空间……总而言之,地址的数值是一个无符号整数。 大小(Size)和对齐 如果仅从变量的大小来看整个计算机世界,就好像一副彩色图片被二值化了,到处是Memory Block,他们的尺寸通常是1个字节、2个字节、4个字节、8个字节、16个字节或者由他们组合而成的长度各异Block。这些Block通常被编译器在代码生成的时候对齐到地址的宽度上,比如地址宽度是32bit的,就对齐到4字节,地址宽度是16bit的,就对齐到2字节…… 一个类型的大小信息除了描述一个变量所占用的存储器尺寸以外,还隐含了该变量的对齐信息。从结论来说,32位处理器架构下:
//通过struct 关键字定义结构体
struct {
uint8_t a;
uint16_t b;
uint8_t c;
uint32_t d;
};
memory layout:
在ARM Compiler里面,结构体的对齐使用以下规则: 整个结构体根据结构体内对齐要求最大的那个元素来对齐。比如,整个结构体内部对齐要求最大的元素是希望对齐到WORD,那么整个结构体就默认对齐到4字节。 结构体内部,成员变量的排列顺序严格按照定义的顺序进行。 结构体内部,成员变量自动对齐到自己的大小——这就会导致空隙的产生。 结构体内部,成员变量可以通过 attribute ((packed))单独指定对齐方式为byte。
适用的方法(Method)和运算(Operation) 对面向对象中的对象来说,方法就是该对象类中描述的各种成员函数(Method);对数据结构中的各类抽象数据类型(ADT,Abstract Data Type)来说,就是各类针对该数据类型的操作函数,比如链表的添加(Add)、插入(Insert)、删除(Delete)、和查找(Search)操作;比如队列对象的入队(enqueue)、出队(Dequeue)函数;比如栈对象的入栈(PUSH)、出栈(POP)等等……
对普通数值类的变量来说,就是所适用的各类运算,比如针对 int的四则运算(+、-、*、/、>、<、==、!=…)。你不能对float型的数据进行移位操作,为什么呢?因为不同的类型拥有不同的适用方法和运算。
看完这里,您应该能理解了,面向对象的思想其实应用在我们使用的各种代码里,比如用标准数据类型char、int定义的变量,是对象,用抽象数据类型的操作系统的任务控制块,是对象;STM32的HAL库的句柄,是对象;只要心中有对象,对象无处不在。
典型的国产操作系统RT-Thread 采用的就是内核对象管理系统来访问 / 管理所有内核对象,内核对象包含了内核中线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上,如图 RT-Thread 的内核对象容器及链表如下图所示:
下图则显示了 RT-Thread 中各类内核对象的派生和继承关系:
面向对象思想的核心是把数据和处理数据的方法封装在一起。封装是面向对象的出发点,扩展出的功能还有“继承和多态”,面向对象可以简单的理解为将一切事物抽象化 ,面向对象的代码结构,有效做到了层层分级、层层封装,每一层只理解需要对接的部分,其他被封装的细节不去考虑,有效控制了小范围内信息量的爆炸。学会面向对象的编程思想,只是基础,如何要使用好,还需要设计模式等编程思想的指导。在裸机编程中,如果心怀对象,自然而然的就会将各种统一的变量进行封装,而不是全局变量满天飞,形成难以维护的代码。
然而当项目的复杂度超过一定程度的时候,模块间对接的代价远远高于实体业务干活的代价, 因为面向对象概念的层级划分,要实现的业务需要封装,封装好跟父类对接。多继承是万恶之源,让整个系统结构变成了网状、环状,最后变成一坨乱麻。
Erlang 的创建者 JoeArmstrong 有句名言:
面向对象语言的问题在于,它们依赖于特定的环境。你想要个香蕉,但拿到的却是拿着香蕉的猩猩,乃至最后你拥有了整片丛林。
程序设计要专注于“应用逻辑的实现”本身,应该尽量避免被“某种技术”分心 。《UNIX编程艺术》,第一原则就是KISS原则,整本书都贯彻了KISS(keep it simple, stupid!) 原则。写项目、写代码,目的都是为了解决问题。而不是花费或者说浪费过多的时间在考虑与要解决的问题完全无关的事情上。