C语言实现面向对象编程
version : v1.0 「2022.8.31」 最后补充
author: Y.Z.T.
摘要: 简单记录用C语言实现面向对象编程
(自留记录用🌟 , 写的比较混乱)
⭐️ 目录
文章目录
0️⃣ 前言
在设计复杂系统的时候,面向对象的程序设计具有 拓展性好、维护性好、复用性好等优点。
面向对象编程,它是一种设计方法、设计思想 ,具备三个基本特征:
- 封装
- 继承
- 多态
因为平时进行嵌入式开发主要还是应用C语言进行开发,所以通过C语言来实现面向对象
1️⃣ 封装
一个类中主要包含两种基本成员: 属性和方法
在C语言中:
- 一个结构体就相当于一个类,结构体实例化之后就是一个对象。
- 结构体中的成员类似类中的成员;
- 结构体中的函数指针类似类方法。
【良好的封装可以有效减少耦合性,封装内部实现可以自由修改,对系统的其它部分没有影响】
1.1内存模型:
在栈区中储存着Gr
对象 , 在程序代码区 储存着Gr
对象的实现方法
2️⃣ 继承
- 在面向对象编程中,封装和继承其实是不分开的 ; 封装就是为了更好地继承。
- 我们将几个类共同的一些属性和方法抽取出来,封装成一个类
- 通过继承最大化地实现代码复用。
- 通过继承,子类可以直接使用父类中的属性和方法。
2.1 继承方法
- 可以通过内嵌结构体或结构体指针来模拟继承
- 也可以把使用结构体类型定义各个不同的结构体变量也看作是继承.
- 这种继承一般用在子类和父类差别不大的场合;
- 各个结构体变量就是子类,然后各个子类拓展各自的方法和属性
2.2内存模型:
以上面Gr_t
结构体为例:
- 如图所示,子类
Gr_t
与第一个继承而来的Motor_t
共用一个地址 ;- 在栈区中有一处空间储存着
Gr_t
类实例后的对象 , 而Gr_t
结构体中的第一个成员是Motor_t
对象 , 所以包含了父类中定义的属性- 因此对于一个子类对象 ,它可以像访问自己的成员一样访问父类的成员
2.3代码:
以底盘结构体为例:
结构体定义:
/*************************************底盘结构体(Chassis_t)********************************************/
typedef __packed struct
{
/*数据变量*/
C_t C;
REMOTE_t *RC;
FSM_t *Fsm;
/*函数指针变量*/
void (*Fsm_Init)(void); //状态机初始化(相当于父类构造函数)
void (*Wheel_Init)(C_t *C); //电机1初始化(相当于父类构造函数)
void (*Rescue_Init)(C_t *C); //电机2初始化(相当于父类构造函数)
void (*Indepen)(C_t*,float ,float ,float ,int16_t); //底盘跟随(实现方法)
....
}Chassis_t;
/**********************************************底盘数据结构体(C_t)**********************************************/
typedef __packed struct
{
Motor_t WheelMotor[4];
Motor_t RescueMotor;
PYR_t *gyro;
PID_t Yaw_Pid;
void (*Can_Send_Wheel)(int16_t,int16_t,int16_t,int16_t);
void (*Can_Send_Rescue)(int16_t,int16_t,int16_t,int16_t);
PYR_t* (*Get_PYR_t)(void);
}C_t;
/*电机结构体*/
typedef __packed struct
{
uint8_t ID; //电机ID号
uint8_t Pos_Lock; //位置锁
...
}Motor_t;
/* 陀螺仪欧拉角结构体 */
typedef __packed struct
{
u8 data[128];
float Roll;
float Pitch;
...
}PYR_t;
/*PID结构体*/
typedef __packed struct
{
fp32 Set;
fp32 Ref;
fp32 LastRef;
...
}PID_t;
/******************************************有限状态机(FSM_t)************************************************/
typedef struct _fsm
{
unsigned char (*State_Change)(State_t *L_Sta,State_t *C_Sta);
State_t *Last_State;
State_t *Current_State;
State_t (*State_Table)[State_Column];
}FSM_t;
/*状态结构体*/
typedef struct _state
{
void (*State_Prepare)(void);
void (*State_Process)(void);
void (*Behavior_Process)(void);
}State_t;
/***************************************遥控数据结构体(REMOTE_t)***********************************/
typedef __packed struct
{
RC_ctrl_t *RC_ctrl;
First_Order_t RC_X;
First_Order_t KM_Z;
KeyBoard_State_t state;
}REMOTE_t;
/*一阶滤波函数*/
typedef __packed struct
{
fp32 Input;
fp32 LastOuput;
...
}First_Order_t;
/*键盘控制状态结构体*/
typedef __packed struct
{
unsigned char Electromagnet : 1;
unsigned char RFID : 1;
unsigned char Magazine : 1;
unsigned char Auto_Clamp : 5;
}KeyBoard_State_t;
/*遥控数据*/
typedef __packed struct
{
int16_t ch0;
int16_t ch1;
...
}RC_ctrl_t;
2.4底盘继承关系图:
- 以上就是在多任务操作系统中 ,底盘任务中比较重要的一些结构体和对应的函数操作集
- 底盘各个状态切换和正常运行都是通过这些函数接口来进行维护的
- 最后封装成一个底盘任务的运行结构体 , 并实例化用于储存任务运行中的属性和方法
- 其他任务也是如此(如云台任务等) , 不同的是不同任务对象各自对应实现方法有些不同
- 像这样子类继承父类的过程中,一个接口在不同的子类中有不同的实现 ,就是接下来要介绍的多态
3️⃣ 多态
- 在C++里面实现多态使用的是虚函数 , 子类在继承父类之后,在内存中又会开辟一块空间来放置子类自己的虚表
- C中对于多态的实现可以借助函数指针(即函数映射)来实现。
- 因为对于不同的子类对象 , 一个方法在内存中只有一个变量,就是那个函数指针;
- 当p->pfun指向不同的传入实参的函数地址 , 子类中的实现方法也自然会不同.
3.1 构造函数与this
指针
在实例化一个对象时,C++通过构造函数来进行初始化,而C语言没有,所以我们需要创建一个自己的构造函数。
注意:
C 语言中不存在像 C++ 中那样的 this 指针,如果我们不显式地通过参数提供,那么在函数内部就无法访问结构体实例的其它成员。
3.2 测试代码
#include <stdio.h>
/* 基类 */
typedef struct {
int test1 ; // 父类成员
void (*test_printf)(void); // 父类实现方法
}base_t;
/* 派生类 */
typedef struct {
base_t test_base; // 继承
void (*base_init)(base_t* ba); // 父类构造函数实现方法
}derive_t;
/* 子类2重新实现的函数方法 */
void derive2_printf(void)
{
printf("derive2\n");
}
/* 父类构造函数2 */
void Fbase2_init(base_t* base)
{
/* 数据成员初始化 */
base->test1 = 0;
/* 函数映射 */
base->test_printf = derive2_printf;
}
/* 子类构造函数2 */
void derive2_init(derive_t* der)
{
der->base_init = Fbase2_init; // 函数映射
der->base_init(&der->test_base); // 初始化父类对象(即运行父类构造函数)
}
/* 子类1重新实现的函数方法 */
void derive1_printf(void)
{
printf("derive1\n");
}
/* 父类构造函数1 */
void Fbase1_init(base_t* base)
{
base->test1 = 0;
base->test_printf = derive1_printf;
}
/* 子类构造函数1 */
void derive1_init(derive_t* der)
{
der->base_init = Fbase1_init;
der->base_init(&der->test_base);
}
int main(void)
{
/* 实例化对象并初始化 */
derive_t der1,der2;
derive1_init(&der1);
derive2_init(&der2);
/* 通过子类指针 去访问各个子类中同名函数的不同实现 */
derive_t* der_point;
der_point = &der2;
der_point->test_base.test_printf();
der_point = &der1;
der_point->test_base.test_printf();
return 0;
}
运行结果:
- 通过der->printf指向不同的传入实参的函数地址 , 实现子类中不同的实现方法;
- 通过这种方法在C语言中模拟"多态"
❗️ 应用实例
以底盘驱动任务为例 , 说明C语言面向对象思想在项目中的使用
4.1 基本框架文件介绍
-
Chassis_Task.c/h
[ 应用层 ] : 是进程运行的主要文件(相当于main文件) , 同时提供了整个 底盘总的控制结构体(类)
-
Chassis_Fsm.c/h
[ 应用接口层 ] :主要统一汇总下层API接口 , 并提供底盘控制状态行为函数的 方法;
-
RemoteDeal.c/h
[ 功能模块层 ] : 主要 封装遥控接收模块功能 为上层提供API接口 , 并提供遥控数据处理的 方法和类
-
ChassisMotor.c/h
[ 业务逻辑层 ] :主要封装了各底盘行为模式功能 , 并提供了底盘电机驱动和控制的 方法和类
-
fsm.c/h
[ 应用层 ] :主要提供了底盘控制的状态机状态切换的 方法和类
软件框架:
应用层 | 底盘控制任务 | 状态机状态转换任务 | |
---|---|---|---|
业务逻辑层 | 底盘模式1 | 底盘模式2 | 底盘模式3.. |
功能模块层 | 电机驱动模块 | 遥控接收处理模块 | 算法库模块 |
硬件驱动层 | 各外设驱动... | ||
4.1.1 fsm.c/h
[ 应用层 ] :主要提供了底盘控制的状态机状态切换的 方法和类
/************************************ fsm.c **************************************************/
#include "fsm.h"
/*状态机处理*/
void FSM_Deal(FSM_t *fsm, unsigned char s1, unsigned char s2)
{
/*误操作判断*/
if(s1 <= 0 || s1 > State_Line || s2 <= 0 || s2 > State_Column)
{
return;
}
/*状态指向*/
fsm->Current_State = &fsm->State_Table[s1-1][s2-1]; // 用事件表,通过查表的方式获取底盘状态
/*状态变化*/
if( fsm->State_Change(fsm->Last_State,fsm->Current_State) == 1)
{
fsm->Current_State->State_Prepare(); // 状态切换前的准备 ,如:将电机输出清零 ,或记录当前底盘的yaw轴坐标等
}
/*保留上次状态*/
fsm->Last_State = fsm->Current_State;
/*执行状态*/
fsm->Current_State->State_Process(); // 函数映射
/*执行实际行为*/
fsm->Current_State->Behavior_Process(); // 函数执行
}
/************************************ fsm.h **************************************************/
/*状态结构体(提供有限状态机的状态操作函数)*/
typedef struct _state
{
void (*State_Prepare)(void); // 状态准备函数
void (*State_Process)(void); // 状态执行函数
void (*Behavior_Process)(void); // 状态行为函数
}State_t;
/*有限状态机*/
typedef struct _fsm
{
unsigned char (*State_Change)(State_t *L_Sta,State_t *C_Sta); // 比较状态是否发生改变
State_t *Last_State; // 上一次的状态
State_t *Current_State; // 当前状态
State_t (*State_Table)[State_Column];
}FSM_t;
/*状态机处理函数*/
void FSM_Deal(FSM_t *fsm, unsigned char s1, unsigned char s2);
4.1.2 Chassis_Task.c/h
[ 应用层 ] : 是进程运行的主要文件(相当于main文件) , 同时提供了整个 底盘总的控制结构体(类)
/************************************ Chassis_Task.c **************************************************/
#include "Chassis_Task.h"
extern Chassis_t Chassis;
Chassis_t Chassis; // 实例化对象
/*Chassis_Task初始化(Chassis类的构造函数)*/
static void Chassis_Init(void)
{
/*函数映射 (将实现方法映射到 特定的API接口)*/
Chassis.Fsm_Init = Chassis_FSM_Init;
Chassis.Wheel_Init = Wheel_Motor_Init;
Chassis.Rescue_Init = Rescue_Motor_Init;
Chassis.Indepen = Chassis_Indepen_Drive;
Chassis.Wiggle = Chassis_Wiggle_Drive;
Chassis.Poweroff = Chassis_Poweroff_Drive;
/*数据初始化*/
Chassis.RC = Return_RemoteDeal_Point(); //Chassis的获取Remote数据指针 (组合)
Chassis.Wheel_Init(&Chassis.C); //Chassis的电机初始化 (继承 相当于调用父类构造函数)
Chassis.Rescue_Init(&Chassis.C); //Chassis的电机初始化 (继承 相当于调用父类构造函数)
Chassis.Fsm = Return_Chassis_FSM(); //Chassis获取状态机数据指针 (组合)
Chassis.Fsm_Init(); //底盘状态机初始化 (调用父类构造函数)
}
/* 底盘进程入口函数 */
void Chassis_Task(void *pvParameters)
{
vTaskDelay(500);
Chassis_Init(); // 显式调用Chassis类构造函数
while(1)
{
FSM_Deal(Chassis.Fsm,Chassis.RC->RC_ctrl->s1,Chassis.RC->RC_ctrl->s2); // 进入状态机处理函数进行处理
vTaskDelay(2);
}
}
/************************************ Chassis_Task.h **************************************************/
/* 底盘控制结构体 */
typedef __packed struct
{
/*数据变量*/
C_t C;
REMOTE_t *RC;
FSM_t *Fsm;
/*函数指针变量*/
void (*Fsm_Init)(void); //状态机初始化(相当于父类构造函数)
void (*Wheel_Init)(C_t *C); //电机1初始化(相当于父类构造函数)
void (*Rescue_Init)(C_t *C); //电机2初始化(相当于父类构造函数)
void (*Indepen)(C_t*,float ,float ,float ,int16_t); //底盘跟随(实现方法)
....
}Chassis_t;
void Chassis_Task(void *pvParameters);
4.1.3 ChassisMotor.c/h
[ 业务逻辑层 ] :主要封装了各底盘行为模式功能 , 并提供了底盘电机驱动和控制的 方法和类
/************************************ ChassisMotor.c **************************************************/
#include "ChassisMotor.h"
/* 底盘电机初始化 (电机数据类的构造函数) */
void Wheel_Motor_Init(C_t *C)
{
u8 CMi = 0;
/* 初始化各电机pid参数表 */
float Spid[4][3] = {
{WHEEL_MOTOR1_P,WHEEL_MOTOR1_I,WHEEL_MOTOR1_D},
{WHEEL_MOTOR2_P,WHEEL_MOTOR2_I,WHEEL_MOTOR2_D},
{WHEEL_MOTOR3_P,WHEEL_MOTOR3_I,WHEEL_MOTOR3_D},
{WHEEL_MOTOR4_P,WHEEL_MOTOR4_I,WHEEL_MOTOR4_D},
};
/*函数映射*/
C->Can_Send_Rescue = CAN1_205_To_208_SEND;
....
/*清零处理*/
MotorValZero(&C->WheelMotor[0]);
....
/* 初始化各电机数据 */
for(CMi = 0;CMi < 4;CMi ++)
{
C->WheelMotor[CMi].ID = CMi + 1;
C->WheelMotor[CMi].Encoder = C->Get_Encoder(CMi+1);
PID_INIT(&C->WheelMotor[CMi].SPID,Spid[CMi][0],Spid[CMi][1],Spid[CMi][2],15000,16000); //速度环初始化
C->WheelMotor[CMi].MotorType = CHASSIS_M; //初始化电机种类
C->WheelMotor[CMi].Radio = 19; //初始化底盘电机减速比
}
/*获取底盘陀螺仪数据*/
C->gyro = C->Get_PYR_t(); // 组合(获取父类数据)
}
/* 底盘模式1 */
void Chassis_Indepen_Drive(C_t *C,float X_IN,float Y_IN,float Z_IN,int16_t ExpRescue)
{
...
}
/* 底盘模式2 */
void Chassis_Wiggle_Drive(C_t *C,float X_IN,float Y_IN,float Z_IN)
{
...
}
/* 底盘模式3 */
....
/************************************ ChassisMotor.h **************************************************/
/* 底盘数据结构体 */
typedef __packed struct
{
Motor_t WheelMotor[4]; // 底盘电机
....
}C_t;
4.1.4 Chassis_Fsm.c/h
[ 应用接口层 ] :主要统一汇总下层API接口 , 并提供底盘控制状态行为函数的 方法;
/******************************************* Chassis_Fsm.c **************************************************/
#include "Chassis_Fsm.h"
#include "Chassis_Task.h"
FSM_t Chassis_FSM; /* 底盘状态机*/
State_t OFFLINE; /* 状态模式1 */
State_t INDEPEN; /* 状态模式2 */
State_t WIGGLE; /* 状态模式3 */
...
/* 用事件表,通过查表的方式获取底盘状态 */
State_t Chassis_State_Table[RC_SW1_lift][RC_SW2_right] =
{ /*右上*/ /* 右下 */ /* 右中 */
/*左上*/ {CHASSIS_REMOTECONTROL, CHASSIS_STANDBY, CHASSIS_REMOTECONTROL},
/*左下*/ {CHASSIS_REMOTECONTROL, CHASSIS_STANDBY, CHASSIS_REMOTECONTROL},
/*左中*/ {CHASSIS_REMOTECONTROL, CHASSIS_STANDBY, CHASSIS_REMOTECONTROL}
};
/*底盘状态机初始化*/
void Chassis_FSM_Init(void)
{
Chassis_FSM.State_Table = Chassis_State_Table; // 映射事件表 (调用示例 : 如下所示 ,获取当前状态
Chassis_FSM.Last_State = NULL; // fsm->Current_State = &fsm->State_Table[s1-1][s2-1]
Chassis_FSM.Current_State = NULL;
/*状态1初始化*/
OFFLINE.Behavior_Process = NULL;
OFFLINE.State_Process = Offline_State;
OFFLINE.State_Prepare = Offline_Prepare;
/*状态2初始化*/
INDEPEN.Behavior_Process = NULL;
INDEPEN.State_Process = Indepen_State;
INDEPEN.State_Prepare = Indepen_Prepare;
/*状态3初始化*/
WIGGLE.Behavior_Process = NULL;
WIGGLE.State_Process = Wiggle_State;
WIGGLE.State_Prepare = Wiggle_Prepare;
}
/*************************************** 状态1行为函数定义 **************************************/
/*执行函数 */
static void Offline_State(void)
{
Chassis_FSM.Current_State->Behavior_Process = PowerOff_bhv; // 函数映射 , 映射为当前状态的执行函数(实现父类调用子类的方法)
}
/*OFFLINE状态准备函数*/
static void Offline_Prepare(void)
{
Chassis.Poweroff(&Chassis.C); // 底盘
}
/*行为函数*/
static void PowerOff_bhv(void)
{
Chassis.Poweroff(&Chassis.C); // 调用子类方法
}
/*************************************** 状态2行为函数定义 **************************************/
//同理
...
/*************************************** 状态3行为函数定义 **************************************/
...
- 各个状态模式 (即
State_t
实例化的对象) 可以当成继承自同一父类的不同子类- 其对各自API接口在不同子类中有不同的实现 , 即是函数多态的一种应用
4.1.5 RemoteDeal.c/h
[ 功能模块层 ] : 主要 封装遥控接收模块功能 为上层提供API接口 , 并提供遥控数据处理的 方法和类
/******************************************* RemoteDeal.c **************************************************/
#include "RemoteDeal.h"
/*遥控结构体*/
REMOTE_t REMOTE;
/**
*名称: Get_RemoteDeal_Point
*功能: 返回处理后的遥控数值控制变量,通过指针传递方式传递信息
*形参: 无
*返回: 无
*说明: 无
**/
REMOTE_t *Return_RemoteDeal_Point(void)
{
return &REMOTE;
}
/* ******************************* 遥控数据处理 ******************************************/
...
通过指针传递方式 , 传递父类信息