C语言实现面向对象编程

C语言实现面向对象编程


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

author: Y.Z.T.


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

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





0️⃣ 前言

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

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

  • 封装
  • 继承
  • 多态

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




1️⃣ 封装

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


在C语言中:

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

image-20220829085728552


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


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代码:

以底盘结构体为例:

结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/*************************************底盘结构体(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语言没有,所以我们需要创建一个自己的构造函数

image-20220831182345598


注意:

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

image-20220901113321988



3.2 测试代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#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;
}

运行结果:

image-20220831224350162


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

    [ 应用层 ] :主要提供了底盘控制的状态机状态切换方法和类


软件框架:




4.1.1 fsm.c/h

[ 应用层 ] :主要提供了底盘控制的状态机状态切换方法和类

应用层 底盘控制任务 状态机状态转换任务
业务逻辑层 底盘模式1 底盘模式2 底盘模式3..
功能模块层 电机驱动模块 遥控接收处理模块 算法库模块
硬件驱动层 各外设驱动...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/************************************ 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文件) , 同时提供了整个 底盘总的控制结构体(类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/************************************  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

[ 业务逻辑层 ] :主要封装了各底盘行为模式功能 , 并提供了底盘电机驱动和控制的 方法和类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/************************************ 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接口 , 并提供底盘控制状态行为函数方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/******************************************* 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接口 , 并提供遥控数据处理方法和类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/******************************************* 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 底盘进程 - 函数调用图

底盘控制 - 函数调用图

-------------已经到底啦! -------------