C语言实现面向对象编程

C语言实现面向对象编程


version : v1.0 「2022.8.31」 最后补充


author: Y.Z.T.


摘要: 简单记录用C语言实现面向对象编程

(自留记录用🌟 , 写的比较混乱)



⭐️ 目录





0️⃣ 前言

在设计复杂系统的时候,面向对象的程序设计具有 拓展性好、维护性好、复用性好等优点。

面向对象编程,它是一种设计方法、设计思想 ,具备三个基本特征:

  • 封装
  • 继承
  • 多态

因为平时进行嵌入式开发主要还是应用C语言进行开发,所以通过C语言来实现面向对象




1️⃣ 封装

一个类中主要包含两种基本成员: 属性方法


在C语言中:

  • 一个结构体就相当于一个类,结构体实例化之后就是一个对象。
  • 结构体中的成员类似类中的成员;
  • 结构体中的函数指针类似类方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z7nFx2uj-1662392427830)(image/6316166216f2c2beb1d4bf10-1662392380782-1.png)]


良好的封装可以有效减少耦合性,封装内部实现可以自由修改,对系统的其它部分没有影响


1.1内存模型:

栈区中储存着Gr对象 , 在程序代码区 储存着Gr对象的实现方法




2️⃣ 继承


  • 在面向对象编程中,封装和继承其实是不分开的 ; 封装就是为了更好地继承。
  • 我们将几个类共同的一些属性和方法抽取出来,封装成一个类
  • 通过继承最大化地实现代码复用
  • 通过继承,子类可以直接使用父类中的属性和方法。

2.1 继承方法
  • 可以通过内嵌结构体结构体指针来模拟继承
  • 也可以把使用结构体类型定义各个不同的结构体变量也看作是继承.
    • 这种继承一般用在子类和父类差别不大的场合;
    • 各个结构体变量就是子类,然后各个子类拓展各自的方法和属性


2.2内存模型:

以上面Gr_t结构体为例:

image-20220829205321949
  • 如图所示,子类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底盘继承关系图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vGpnrOD-1662392427830)(image/631617b016f2c2beb1d6b6ee.png)]


  • 以上就是在多任务操作系统中 ,底盘任务中比较重要的一些结构体和对应的函数操作集
  • 底盘各个状态切换和正常运行都是通过这些函数接口来进行维护的
  • 最后封装成一个底盘任务的运行结构体 , 并实例化用于储存任务运行中的属性和方法
  • 其他任务也是如此(如云台任务等) , 不同的是不同任务对象各自对应实现方法有些不同
  • 像这样子类继承父类的过程中,一个接口在不同的子类中有不同的实现 ,就是接下来要介绍的多态



3️⃣ 多态

  • 在C++里面实现多态使用的是虚函数 , 子类在继承父类之后,在内存中又会开辟一块空间来放置子类自己的虚表
  • C中对于多态的实现可以借助函数指针(即函数映射)来实现。
    • 因为对于不同的子类对象 , 一个方法在内存中只有一个变量,就是那个函数指针;
    • 当p->pfun指向不同的传入实参的函数地址 , 子类中的实现方法也自然会不同.


3.1 构造函数与this指针

在实例化一个对象时,C++通过构造函数来进行初始化,而C语言没有,所以我们需要创建一个自己的构造函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wV4g8dTg-1662392427831)(image/631616b516f2c2beb1d53a08.png)]


注意:

C 语言中不存在像 C++ 中那样的 this 指针,如果我们不显式地通过参数提供,那么在函数内部就无法访问结构体实例的其它成员。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4b0IoOiB-1662392427831)(image/6316156516f2c2beb1d35213.png)]



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;
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyB81hnY-1662392427832)(image/6316150416f2c2beb1d2c356.png)]


  • 通过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;
}

/* *******************************  遥控数据处理 ******************************************/
...

通过指针传递方式 , 传递父类信息



4.2 控制流程图
4.2.1 底盘进程总流程:
底盘进程总流程


4.2.2 底盘进程 - 函数调用图

底盘控制 - 函数调用图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值