湖南中维电力建设有限公司网站,施工企业公司管理制度,石家庄网站建设浩森宇特,成品影视app下载有哪些软件点击“蓝字”关注我们吧前言#xff1a;何为面向过程#xff1a;面向过程#xff0c;本质是“顺序#xff0c;循环#xff0c;分支” 面向过程开发#xff0c;就像是总有人问你要后续的计划一样#xff0c;下一步做什么#xff0c;再下一步做什么#xff0c;意外、事物… 点击“蓝字”关注我们吧前言何为面向过程面向过程本质是“顺序循环分支” 面向过程开发就像是总有人问你要后续的计划一样下一步做什么再下一步做什么意外、事物中断、突发事件怎么做。理论上来说任何一个过程都可以通过“顺序循环分支”来描述出来但是实际上很多项目的复杂度都不是“顺序循环分支”几句话能说清楚的。稍微大一点的项目多线程几十件事情并发 如果用这种最简单的描述方式要么几乎无法使用缺失细节太多要么事无巨细用最简单的描述都会让后期复杂度提升到一个爆炸的状态。何为面向对象面向对象本质是“继承封装多态” 面向对象的核心是把数据和处理数据的方法封装在一起。面向对象可以简单的理解为将一切事物模块化 面向对象的代码结构有效做到了层层分级、层层封装每一层只理解需要对接的部分其他被封装的细节不去考虑有效控制了小范围内信息量的爆炸。然而当项目的复杂度超过一定程度的时候模块间对接的代价远远高于实体业务干活的代价 因为面向对象概念的层级划分要实现的业务需要封装封装好跟父类对接。多继承是万恶之源让整个系统结构变成了网状、环状最后变成一坨乱麻。Erlang 的创建者 JoeArmstrong 有句名言面向对象语言的问题在于它们依赖于特定的环境。你想要个香蕉但拿到的却是拿着香蕉的猩猩乃至最后你拥有了整片丛林。能解决问题的就是最好的程序设计要专注于“应用逻辑的实现”本身应该尽量避免被“某种技术”分心 。《UNIX编程艺术》第一原则就是KISS原则整本书都贯彻了KISS(keep it simple, stupid) 原则。写项目、写代码目的都是为了解决问题。而不是花费或者说浪费过多的时间在考虑与要解决的问题完全无关的事情上。不管是面向过程还是面向对象都是为了解决某一类问题的技术。各有各的用武之地在驱动开发、嵌入式底层开发这些地方面向过程开发模式干净利索直观资源掌控度高。在这些环境面向过程开发几乎是无可替代的。在工作量大难度较低、细节过多、用简单的规范规则无法面面俱到的环境下用面向对象开发模式用低质量人力砸出来产业化项目。1、面向对象编程面向对象只是一种设计思路是一种概念并没有说什么C是面向对象的语言java是面向对象的语言。C语言一样可以是面向对象的语言Linux内核就是面向对象的原生GNU C89编写的但是为了支持面向对象的开发模式Linux内核编写了大量概念维护modules维护struct的函数指针内核驱动装载等等机制。而C和java为了增加面向对象的写法直接给编译器加了一堆语法糖。2、什么是类和对象在C语言中结构体是一种构造类型可以包含若干成员变量每个成员变量的类型可以不同可以通过结构体来定义结构体变量每个变量拥有相同的性质。在C语言中,类也是一种构造类型但是进行了一些扩展可以将类看做是结构体的升级版类的成员不但可以是变量还可以是函数不同的是通过结构体定义出来的变量还是叫变量而通过类定义出来的变量有了新的名称叫做对象(Object)在 C 中通过类名就可以创建对象这个过程叫做类的实例化因此也称对象是类的一个实例(Instance)类的成员变量称为属性(Property)将类的成员函数称为方法(Method)。在C语言中的使用struct这个关键字定义结构体在C 中使用的class这个关键字定义类。结构体封装的变量都是 public 属性类相比与结构体的封装多了 private 属性和 protected 属性 private 和protected 关键字的作用在于更好地隐藏了类的内部实现 只有类源代码才能访问私有成员只有派生类的类源代码才能访问基类的受保护成员每个人都可以访问公共成员。这样可以有效的防止可能被不知道谁访问的全局变量。C语言中的结构体1//通过struct 关键字定义结构体2struct object3{4 char name[8]; 5 char type; 6 char flag; 7 //指向函数的指针类型8 void (*display)(void); 9};C语言中的类 1//通过class关键字类定义类 2class object{ 3public: 4 char name[8]; 5 char type; 6 char flag; 7 //类包含的函数体 8 void display(){ 9 printf(123456789);10 }11};3、内存分布的对比不管是C语言中的结构体或者C中的类都只是相当于一个模板起到说明的作用不占用内存空间结构体定义的变量和类创建的对象才是实实在在的数据要有地方来存放才会占用内存空间。结构体变量的内存模型结构体的内存分配是按照声明的顺序依次排列涉及到内存对齐问题。为什么会存在内存对齐问题引用傻孩子公众号裸机思维的文章《漫谈C变量——对齐》加以解释在ARM Compiler里面结构体内的成员并不是简单的对齐到字(Word)或者半字(Half Word)更别提字节了(Byte)结构体的对齐使用以下规则整个结构体根据结构体内最大的那个元素来对齐。比如整个结构体内部最大的元素是WORD那么整个结构体就默认对齐到4字节。结构体内部成员变量的排列顺序严格按照定义的顺序进行。结构体内部成员变量自动对齐到自己的大小——这就会导致空隙的产生。结构体内部成员变量可以通过 attribute ((packed))单独指定对齐方式为byte。strut对象的内存模型1//通过struct 关键字定义结构体2struct {3 uint8_t a;4 uint16_t b;5 uint8_t c;6 uint32_t d7};memory layoutclass对象的内存模型假如创建了 10 个对象编译器会将成员变量和成员函数分开存储分别为每个对象的成员变量分配内存但是所有对象都共享同一段函数代码放在code区。如下图所示成员变量在堆区或栈区分配内存成员函数放在代码区。对象的大小只受成员变量的影响和成员函数没有关系。对象的内存分布按照声明的顺序依次排列和结构体非常类似也会有内存对齐的问题。可以看到结构体和对象的内存模型都是非常干净的C语言里访问成员函数实际上是通过指向函数的指针变量来访问(相当于回调),那么C编译器究竟是根据什么找到了成员函数呢实际上C的编译代码的过程中把成员函数最终编译成与对象无关的全局函数如果函数体中没有成员变量那问题就很简单不用对函数做任何处理直接调用即可。如果成员函数中使用到了成员变量该怎么办呢成员变量的作用域不是全局不经任何处理就无法在函数内部访问。C规定编译成员函数时要额外添加一个this指针参数把当前对象的指针传递进去通过this指针来访问成员变量。this 实际上是成员函数的一个形参在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的它并不出现在代码中而是在编译阶段由编译器默默地将它添加到参数列表中。这样通过传递对象指针完成了成员函数和成员变量的关联。这与我们从表明上看到的刚好相反通过对象调用成员函数时不是通过对象找函数而是通过函数找对象。这在C中一切都是隐式完成的对程序员来说完全透明就好像这个额外的参数不存在一样。无论是C还是C其函数第一个参数都是一个指向其目标对象的指针也就是this指针只不过C由编译器自动生成——所以方法的函数原型中不用专门写出来而C语言模拟的方法函数则必须直接明确的写出来4 掩码结构体在C语言的编译环境下不支持结构体内放函数体除了函数外就和C语言里定义类和对象的思路完全一样了。还有一个区别是结构体封装的对象没有好用的private 和protected属性不过C语言也可以通过掩码结构体这个骚操作来实现private 和protected的特性。注此等操作并不是面向对象必须的这个属于锦上添花的行为不用也不影响面向对象。先通过一个例子直观体会一下什么是掩码结构体以下例子来源为傻孩子的PLOOC的readme作者仓库地址https://github.com/GorgonMeducer/PLOOC 1//! the original structure in class source code 2struct byte_queue_t { 3 uint8_t *pchBuffer; 4 uint16_t hwBufferSize; 5 uint16_t hwHead; 6 uint16_t hwTail; 7 uint16_t hwCount; 8}; 910//! the masked structure: the class byte_queue_t in header file11typedef struct byte_queue_t {12 uint8_t chMask [sizeof(struct {13 uint8_t *pchBuffer;14 uint16_t hwBufferSize;15 uint16_t hwHead;16 uint16_t hwTail;17 uint16_t hwCount;18 })];19} byte_queue_t;为了使其工作我们必须确保类源代码不包括其自己的接口头文件。您甚至可以这样做…如果您对内容很认真 1//! the masked structure: the class byte_queue_t in header file 2typedef struct byte_queue_t { 3 uint8_t chMask [sizeof(struct { 4 uint32_t : 32; 5 uint16_t : 16; 6 uint16_t : 16; 7 uint16_t : 16; 8 uint16_t : 16; 9 })];10} byte_queue_t;通过这个例子我们可以发现给用户提供的头文件其实是一个固态存储器即使用字节数组创建的掩码用户通过掩码结构体创建的变量无法访问内部的成员这就是实现属性私有化的方法。至于如何实现只有类源代码才能访问私有成员只有派生类的类源代码才能访问基类的受保护成员的特性这里先埋个伏笔关注本公众号后续文章再深入探讨。还回到掩码结构体本身的特性上可以发现一个问题掩码结构体丢失了结构体的对齐信息因为掩码的本质是创建了一个chMask数组我们知道数组是按照元素对齐的而原本结构体是按照Word对齐的。所以当你用掩码结构体声名结构体变量的时候这个变量多半不是对齐到word的当你在模块内访问这个对象的时候…编译器默认你整个结构体是对齐到word这就会导致错位的产生可能会直接导致hardfault了为了解决这个问题可以利用_ attribute_ ((align))以及 _ alignof_的操作对它进行如下改进 1//! the original structure in class source code 2struct byte_queue_t { 3 struct { \ 4 uint8_t *pchBuffer; 5 uint16_t hwBufferSize; 6 uint16_t hwHead; 7 uint16_t hwTail; 8 uint16_t hwCount; \ 9 }__attribute__((aligned(__alignof__(struct {uint8_t *pchBuffer;10 uint16_t hwBufferSize;11 uint16_t hwHead;12 uint16_t hwTail;13 uint16_t hwCount;})))); 14};1516//! the masked structure: the class byte_queue_t in header file17typedef struct byte_queue_t {18 uint8_t chMask \19 [sizeof(struct {uint8_t *pchBuffer;20 uint16_t hwBufferSize;21 uint16_t hwHead;22 uint16_t hwTail;23 uint16_t hwCount;})] \24 __attribute__((aligned(__alignof__(struct {uint8_t *pchBuffer;25 uint16_t hwBufferSize;26 uint16_t hwHead;27 uint16_t hwTail;28 uint16_t hwCount;})))); \29} byte_queue_t;这部分理解起来可能稍微有点复杂但是不理解也没关系现在先知道有这个东西后续文章还会有更骚的操作来更直观的实现封装、继承和多态5 C语言实现类的封装如果你趟过了掩码结构体那条河那么恭喜你你已经成功上岸了。我们继续回到面向对象的问题上面向对象的核心是把数据和处理数据的方法封装在一起。封装并不是只有放在同一个结构体里这一种形式放在同一个接口头文件里(也就是.h)里也是一种形式——即一个接口头文件提供了数据的结构体以及处理这些数据的函数原型声明这已经完成了面向对象所需的基本要求。下边将通过C语言的具体实例加以说明。假设我们要封装一个基于字节的队列类不妨叫做Queue因此我们建立了一个类文件queue.c和对应的接口头文件queue.h。假设我们约定queue.c将不包含queue.h(这么做的好处很多在以后的内容里再讲解当然对掩码结构体技术来说模块的实现是否包含模块的接口头文件并不是关键)。queue.h 1... 2//! the masked structure: the class byte_queue_t in header file 3typedef struct queue_t { 4 uint8_t chMask \ 5 [sizeof(struct {uint8_t *pchBuffer; 6 uint16_t hwBufferSize; 7 uint16_t hwHead; 8 uint16_t hwTail; 9 uint16_t hwCount;})] \10 __attribute__((aligned(__alignof__(struct {uint8_t *pchBuffer;11 uint16_t hwBufferSize;12 uint16_t hwHead;13 uint16_t hwTail;14 uint16_t hwCount;})))); \15} queue_t;1617...18extern bool queue_init(queue_t *ptQueue, uint8_t *pchBuffer, uint16_t hwSize);19extern bool enqueue(queue_t *ptQueue, uint8_t chByte);20extern bool dequeue(queue_t *ptQueue, uint8_t *pchByte);21extern bool is_queue_empty(queue_t *ptQueue);22...queue.c 1... 2//! the original structure in class source code 3typedef struct __queue_t { 4 struct { \ 5 uint8_t *pchBuffer; 6 uint16_t hwBufferSize; 7 uint16_t hwHead; 8 uint16_t hwTail; 9 uint16_t hwCount; \10 }__attribute__((aligned(__alignof__(struct {uint8_t *pchBuffer;11 uint16_t hwBufferSize;12 uint16_t hwHead;13 uint16_t hwTail;14 uint16_t hwCount;})))); 15}__queue_t;16...可以看到实际上类型queue_t是一个掩码结构体里面只有一个起到掩码作用的数组chMask其大小和真正后台的的类型__queue_t相同——这就是掩码结构体实现私有成员保护的秘密。解决了私有成员保护的问题剩下还有一个问题对于queue.c的函数来说queue_t只是一个数组那么正常的功能要如何实现呢下面的代码片将断为你解释一切 1... 2#define __class(__NAME) __##__NAME 3#define class(__NAME) __class(__NAME) 4bool is_queue_empty(queue_t *ptQueue) 5{ 6 CLASS(queue_t) *ptQ (CLASS(queue_t) *)ptQueue; 7 if (NULL ptQueue) { 8 return true; 9 }10 return ((ptQ-hwHead ptQ-hwTail) (0 ptQ-hwCount));11}12...可以从这里看出来只有类的源文件才能看到内部使用的结构体而掩码结构体是模块内外都可以看到的简单来说如果实际内部的定义为外部的模块所能直接看见那自然就没有办法起到保护作用。从编译器的角度来说这种从queue_t到__queue_t类型指针的转义是逻辑上的并不会因此产生额外的代码简而言之使用掩码结构体几乎是没有代价的。再次强调实现面向对象掩码结构体并不是必须的只是锦上添花所以不理解的话也不要纠结想要更深入了解C语言面向对象的思想建议参考的书籍《UMLOOPC嵌入式C语言开发精讲》点击下方“蓝字”发现更多精彩。STM32通用Bootloader——FOTASTM32通用FLASH管理软件包——SFUD/FALSTM32通用低功耗组件——PM长按关注我们CSDN博客Aladdin Wang微信号:17630350805“在看”的小可爱永远十八岁!