尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

嵌入式工程师进阶指南:面向对象思维的C语言

嵌入式工程师进阶指南:面向对象思维的C语言 C语言并非是为了编写面向对象程序设计的但是C语言也可以实现面向对象的的编程思维。为什么要在C语言上来实现C或者Python才能够使用的一种编程技巧原因在于面向对象设计确实是一种比面向过程设计更高级的编程方法并且它将改变嵌入式C语言工程师的编程模式更利于项目的开发和后期扩展。现在的嵌入式项目很多都是使用C语言并利用了面向对象的方法去设计程序。使用C语言来实现LED红绿两灯的控制常规写法如下所示static int redledstate 0; void redled_on() { redledstate 1; HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); } void redled_off() { redledstate 0; HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET); } int get_redled_state() { return redledstate; } static int greenledstate 0; void greenled_on() { greenledstate 1; HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_RESET); } void greenled_off() { greenledstate 0; HAL_GPIO_WritePin(GPIOA,GPIO_PIN_2,GPIO_PIN_SET); } int get_greenled_state() { return greenledstate; } int main() { redled_off(); greenled_on(); }以上的程序是每个初级嵌入式软件工程师很容易就写出的程序简单高效容易理解可以看到C语言在实际编程中是以一种上帝视角的开发模式要求哪个灯打开就让那个灯打开直接去调用对应的函数即可由开发者去充当上帝的角色掌握全局整个流程的发起由开发者决定并且凡事亲历其为。这个语言可以让开发者获取程序中的每个细节尤其是C语言开发者使用指针去操作内存的时候这种感觉更加明显。但如果使用C去驱动两个灯写法如下#include iostream #include stdint.h using namespace std; class led{ public: void on() { state 1; coutLED ON; } void off(){state 0; cout LED OFF;} int getstate(){return state;} private: uint8_t pin; uint8_t port; int state; }; int main() { led red_led; red_led.on(); led green_led; green_led.off(); }相比于C语言版本的程序可以发现C的明显更加简洁和紧凑在程序中明显可以看到灯被具象化为实例也就是red_led 和 green_led,它们每个对自己行为负责开启和关闭由自己的方法来实现彼此之间互不影响。开发者不需要再去对每个过程亲力亲为只需要按照流程对相对应的对象发布命令具体的操作让实例去操作即可。这种编程思维下开发者的工作重心转移到了设计每一个类和对象上这是一种工程师的思维而不是像C语言一样全盘考虑每个变量和函数的上帝思维。C的设计思维比C语言更倾向于工程师思维这也就是面向对象程序设计的优势。如果使用C语言实现类似于C的编程方法可以借用struct 和函数指针来实现。#include stdio.h #include stdint.h #include stdbool.h struct led{ uint8_t pin; uint8_t port; int state; int (*on)(struct led *me); int (*off)(struct led* me); int (*getstate)(struct led* me); }; int led_on(struct led *me) { me-state 0; printf(Class Open led ); return 0; } int led_off(struct led *me) { printf(Class Close led); me-state 0; return 0; } int get_led_state(struct led* me) { return me-state; } struct led redled; struct led greenled; int main() { redled.on led_on; redled.off led_off; redled.getstate get_led_state; greenled.on led_on; greenled.off led_off; greenled.getstate get_led_state; redled.on(redled); greenled.off(greenled); }1、相比于C的程序C语言的程序明显要多一点调用的参数也要多传递一层。2、还要增加一步函数指针的指向更加繁琐3、另外如果去计算实例中C语言中结构体的占用空间和C中class的占用空间会发现struct的占用空间比class还要大。为了解决2、3两个问题需要引入ops指针表。#include stdio.h #include stdint.h #include stdbool.h typedef struct led_ops ; struct led{ uint8_t pin; uint8_t port; int state; struct led_ops *ops; }; int led_on(struct led *me) { me-state 0; printf(Class Open led ); return 0; } int led_off(struct led *me) { printf(Class Close led); me-state 0; return 0; } int get_led_state(struct led* me) { return me-state; } struct led_ops{ int (*on)(struct led *me); int (*off)(struct led* me); int (*getstate)(struct led* me); } ops { led_on, led_off, get_led_state }; struct led redled; struct led greenled; int main() { redled.ops ops; redled.ops-off(redled); greenled.ops ops; greenled.ops-on(greenled); }ops的写法很多大同小异。ops指针又被称为虚函数表这种称呼方法完全是为了和C对应。为什么将所有的函数指针放置在一个ops指针内部就可以减少结构体的占用空间这是因为在同一个平台上指针占用的空间是固定的比如在32位的平台一个指针占用的空间就是4字节在64位的平台一个指针占用的空间就是8字节。而且指针的占用空间大小和指针的类型无关char型指针和long long型指针的大小是一样的。因此在结构体内不可能放置所有的指针。在嵌入式软件工程师转向面向对象的思想去编写程序的时候面临的问题就是理解什么是对象和怎么去应用到程序中设计类应用对象这两个直观的问题。如果手上有一个开发板它具有联网WIFI模块、LED闪烁、温湿度传感器、按键液晶显示屏等硬件这些可见的模块和设备抽象为对象是非常直观的。因为硬件模块是真实存在的看得见摸得着操作起来是具有明显的可视化。对于任何一个硬件设备它如果有自己的数据和操作行为那么就可以被抽象为类并创建对象。例如LED这个例子它有自己的状态值和开关灯行为那么就可构造一个struct的类来实例化每个LED灯,从而进行对LED的控制。再比如温湿度传感器它的温湿度数据和开关操作行为也可以做出一个类。温度传感器在板子上可能只会安装一个但是LED或者按键可能会设置多个所以在一些情况下传感器只能实例一个对象而按键和LED却可以实例多个对象。只能实例一个对象的就被称为单例并能衍生出了一个单例设计模式。对于一些项目开发中无法直观可见的中间环节同样可以抽象为类。例如在串口通信中的通信协议它是只存在于协议文档中不是直观能见到的硬件设备但是它能满足具有自己的数据和操作行为因此它可以被设计为类来使用。在C语言程序设计中结构体的定义一般为了简洁往往写在.h文件内但是这样会存在一个问题那就是任何包含此.h文件都可以直接去调用结构体的成员。在一些情况下如果不希望其余的.c文件随意调用结构体内的程序而是必须要通过接口函数来调用成员参数那么就需要按照一下方法来实现假设需要对一个LCD屏幕配置参数做一个结构体设计它应当有如下的成员参数struct LcdConfig {uint16_t width; // 屏幕宽度uint16_t height; // 屏幕高度LcdColorFormat format; // 颜色格式uint8_t rotation; // 旋转角度uint32_t clock_speed; // 时钟速度(Hz)void *spi_bus; // SPI总线句柄void *i2c_bus; // I2C总线句柄void *cs_pin; // 片选引脚void *dc_pin; // 数据/命令引脚void *rst_pin; // 复位引脚void *bl_pin; // 背光引脚};将以上的结构体存放在.c文件内例如lcd_interface.c内。而在lcd_interface.h内需要做以下声明struct LcdConfig ;typedef struct LcdConfig * LcdConfig_Handler;这就是前向声明也就是不透明指针。任何.c文件包含lcd_inteface.h后只能得到这两个关于结构体的声明而没有办法得到结构体的具体成员信息因此.c文件是无法直接获取或者修改结构体的成员参数。唯一的修改参数和获取办法应当有lcd_interface.c内提供对应的函数来实现。例如uint16_t LcdCfg_getwidth(LcdConfig_Handler lcd) { if(lcd) { return lcd-width; } return 0; ​​​​​​​}当使用了前置声明此技巧之后声明一个结构体变量的操作被死死地限制在了包含结构体的C文件内其余任何C文件均不能声明这个结构体变量因为这是一种不透明的数据类型编译器只能明白确实存在这个结构体但是无法得知结构体的具体内容也就无法明确具体占用的空间因而无法在其他源文件内定义一个变量。但是定义一个指向它的结构体指针依旧不受影响然而使用结构体指针后就必然要使用动态内存申请空间malloc才能正常使用。
返回列表