编程思想
有没有编程思想是区分行业新人和行业老人的最重要因素。一个有编程思想的人,不会受限于项目具体内容,他总能按照项目需求找到最佳的实现方式。那什么是编程思想呢?作者觉得就是解决问题的直觉和手段。也就是身边前辈们常说的多做几个项目,编程思想自然就有了。碰到的项目案例多了,再有相似的案例,自然就能将现有的解决方法应用到新项目当中。同样的,以前项目中学习到的编程技巧同样可以应用到当前新项目中。日积月累,菜鸟变专家。
在嵌入式开发领域,C/C++是开发主流,每种开发语言背后都有自己的一套编程思想,或者叫编程哲学。就比如说C是面向过程的编程语言,就是说C编程的思想是分步骤解决问题。C++是面向对象的编程语言,所谓面向对象就是说编程对象有共同的抽象概念(类),它解决问题的思想是多态、继承和封装。
C如何实现OOP特性
C严格来讲,不算是一种高级编程语言,有人称它为中级开发语言,而C++毫无疑问是典型的高级开发语言,在游戏行业,驱动引擎基本上都是基于C++开发的。试想一下,如果用面向过程的C去开发游戏引擎,那将会是一项多么艰巨的工作,后期的项目维护成本也会高的离谱。高级语言的优势很明显,但是劣势也很明显,高级语言会占用更多的硬件资源,复杂的工程项目才会体现其优势。所以,在嵌入式开发行业,尤其是底层开发方面,直接面向硬件开发,C依然是首选。但是,语言本身的适用方向并不代表编程思想不可以互相借鉴。举个例子:QEMU硬件模拟器框架和Zephyr RTOS框架都是基于C语言做开发,通过查看这两个开源项目的源码,QEMU硬件模拟器框架开发借用了C++的多态、继承和封装思想,Zephyr RTOS框架开发也大量借用了C++的封装、多态和继承思想等。
C语言实现封装特性,主要基于结构体数据类型实现,结构体中包括自定义数据类型和函数指针,类似于C++的类,类中包括成员变量和成员函数。这是C语言实现封装特性的用法。
c
typedef struct
{
uint8_t id;
uint8_t s;
uint8_t cmd;
uint8_t dlchigh;
uint8_t dlclow;
uint8_t address;
uint8_t size;
uint8_t data[128];
} SPI_MCU_Write_Data_Into_ETR_Master_Or_Slave_Frame;
typedef struct
{
uint8_t id;
uint8_t s;
uint8_t cmd;
uint8_t dlchigh;
uint8_t dlclow;
uint8_t address;
uint8_t size;
uint8_t data[128];
} SPI_MCU_Read_Data_From_ETR_Master_Frame;
typedef struct
{
uint8_t slave_id;
uint8_t slave_s;
uint8_t slave_cmd;
uint8_t slave_dlchigh;
uint8_t slave_dlclow;
uint8_t slave_address;
uint8_t master_id;
uint8_t master_s;
uint8_t master_cmd;
uint8_t master_dlchigh;
uint8_t master_dlclow;
uint8_t master_address; //readback FIFO address=0xC0
uint8_t size;
uint8_t data[128];
} SPI_MCU_Read_Data_From_ETR_Slave_Frame;
typedef struct {
uint8_t length;
uint8_t command;
uint8_t hw_sw;
uint8_t mode;
uint8_t (*write)(SPI_MCU_Write_Data_Into_ETR_Master_Or_Slave_Frame*);
uint8_t (*master_read)(SPI_MCU_Read_Data_From_ETR_Master_Frame*);
uint8_t (*slave_read)(SPI_MCU_Read_Data_From_ETR_Slave_Frame*);
} SPI_ETR_T;
开发者只需要实现具体的write、master_read、slave_read函数就行了。初始化时,将函数指针的初始值指向具体的实现函数就行了。函数名就是函数的入口地址。
c
uint8_t phbkt2733q_spi_mode0_write(SPI_MCU_Write_Data_Into_ETR_Master_Or_Slave_Frame* spi_frame)
{
//...函数体实现
}
uint8_t phbkt2733q_spi_mode0_read_from_etr_master(SPI_MCU_Read_Data_From_ETR_Master_Frame* spi_frame)
{
//...函数体实现
}
uint8_t phbkt2733q_spi_mode0_read_from_etr_slave(SPI_MCU_Read_Data_From_ETR_Slave_Frame* spi_frame_slave)
{
//...函数体实现
}
SPI_ETR_T sw_spi_etr_mode0 = {
.length = 0x01,
.command = 0,
.hw_sw = 0,
.mode = 0,
.write = phbkt2733q_spi_mode0_write,
.master_read = phbkt2733q_spi_mode0_read_from_etr_master,
.slave_read = phbkt2733q_spi_mode0_read_from_etr_slave
};
C语言实现多态特性,首先我们需要了解多态的概念,在嵌入式开发中,多态表现为统一接口函数,但具体实现不一样,典型的就是不同外设的实现,比如:UART、Timer外设,都需要初始化、复位、读寄存器、写寄存器,完全可以借用C++的OOP思想将这些行为抽象为init、reset、read、write 函数指针,统一用结构体封装。
c
#ifndef __OOP_POLYMORPHISM_H
#define __OOP_POLYMORPHISM_H
#ifdef __cplusplus
extern "C" {
#endif
// 1. 定义"虚函数表" ------ 多态的核心
struct DeviceState;
typedef struct DeviceClass {
// 统一接口
void (*init)(struct DeviceState* dev); // 虚函数
void (*reset)(struct DeviceState* dev); // 虚函数
void (*write)(struct DeviceState* dev); // 虚函数
int (*read)(struct DeviceState* dev); // 虚函数
} DeviceClass;
// 2. 父设备(基类)
typedef struct DeviceState {
DeviceClass* deviceclass; // 关键:每个对象持有虚函数表指针
} DeviceState;
// 3. 串口设备(子类)
typedef struct {
DeviceState parent; // 继承
int uart_reg; // 子类数据
} SerialState;
extern void serial_init(DeviceState* dev);
extern void serial_reset(DeviceState* dev);
extern void serial_write(DeviceState* dev);
extern int serial_read(DeviceState* dev);
// 4. 定时器设备(子类)
typedef struct {
DeviceState parent;
int timer_reg;
} TimerState;
extern void timer_init(DeviceState* dev);
extern void timer_reset(DeviceState* dev);
extern void timer_write(DeviceState* dev);
extern int timer_read(DeviceState* dev);
#ifdef __cplusplus
}
#endif
#endif /* __OOP_POLYMORPHISM_H */
完成上面的定义和函数实现后,如何调用呢?
c
#include "oop_polymorphism.h"
// 串口自己的实现
void serial_init(DeviceState* dev)
{
// printf("串口初始化\n");
}
void serial_reset(DeviceState* dev)
{
// printf("串口复位\n");
}
void serial_write(DeviceState* dev)
{
// printf("串口写\n");
}
int serial_read(DeviceState* dev)
{
// printf("串口读\n");
}
// 定时器自己的实现
void timer_init(DeviceState* dev)
{
// printf("定时器初始化\n");
}
void timer_reset(DeviceState* dev)
{
// printf("定时器复位\n");
}
void timer_write(DeviceState* dev)
{
// printf("定时器写\n");
}
int timer_read(DeviceState* dev)
{
// printf("定时器读\n");
}
static DeviceClass serial_class = {
.init = serial_init,
.reset = serial_reset,
.write = serial_write,
.read = serial_read
};
static DeviceClass timer_class = {
.init = timer_init,
.reset = timer_reset,
.write = timer_write,
.read = timer_read
};
void device_init(DeviceState* dev) {
// 多态发生点:自动调用对应设备的实现
dev->deviceclass->init(dev);
LOG(INFO) << "running the initial function:" << dev->deviceclass;
}
void device_reset(DeviceState* dev) {
// 多态发生点:自动调用对应设备的实现
dev->deviceclass->reset(dev);
LOG(INFO) << "running the reset function:" << dev->deviceclass;
}
void device_write(DeviceState* dev) {
// 多态发生点:自动调用对应设备的实现
dev->deviceclass->write(dev);
LOG(INFO) << "running the write function:" << dev->deviceclass;
}
void device_read(DeviceState* dev) {
// 多态发生点:自动调用对应设备的实现
dev->deviceclass->read(dev);
LOG(INFO) << "running the read function:" << dev->deviceclass;
}
int oop_polymorphism_main(void)
{
SerialState serial;
TimerState timer;
serial.parent.deviceclass = &serial_class; //类地址
timer.parent.deviceclass = &timer_class; //类地址
device_init(&serial.parent); // 输出:串口初始化
device_reset(&serial.parent);
device_write(&serial.parent);
device_read(&serial.parent);
device_init(&timer.parent); // 输出:定时器初始化
device_reset(&timer.parent);
device_write(&timer.parent);
device_read(&timer.parent);
return 0;
}
在C语言里,可以通过结构体嵌套的方式,实现类的继承,但是需要确保父类结构体引用需要放在子类结构体成员的第一个位置。这样,不论是数据的访问,还是强转都不会有什么问题。典型的QEMU的QOM对象模型就使用C实现了继承特性。
c
#include <stdio.h>
#include <stdlib.h>
typedef struct human { //人都有年龄、性别(基类)
int age;
char sex;
} Human;
//person(派生类)继承human的年龄、性别特性
typedef struct person{
Human human; //继承
char *name; //每个人有自己的名字
} Person;
Person* create_person(int age, char sex, char *name) {
Person* cperson = (Person*) malloc(sizeof(Person));
cperson->human.age = age;
cperson->human.sex = sex;
cperson->name = name;
return cperson;
}
int main() {
Person* cperson = create_person(18, 'w', "asher");
printf("(%d, %c) - name: %s\n", cperson->human.age, cperson->human.sex, cperson->name);
free(cperson);
return 0;
}
总之,C实现C++的OOP特性,关键就是结构体和函数指针的设计。