本文将大量涉及C语言高级操作,如函数指针、结构体指针、二级指针、指针频繁引用解引用、typedef、static、inline和C语言项目结构等知识,请确保自己不会被上述知识强暴,如果没有这顾虑,请尽情享受~
渊源
一开始时候,我是不知道这个技术的。在某一天我在刷B站的时候,看到一个作者为“一点五编程”的视频。他提出了一种编程思想,命名为“一点五编程”。其中:
"一"指的是模块化思想
“点五”指的是(*p)->f(p)技巧
我一看,好像是一种高端的技巧哇,于是开始看他的视频,发现讲解这一技术核心的视频全都是充电的!!!好吧,那我只好翻你文档看了,找到了他的个人博客。唔,好像什么都写了,但好像少点什么,,,哦,没教我到底怎么组织文件。
然后我继续翻网页,在CSDN上发现三篇文章,讲的是对“一点五编程”的解读。但是后来在自己实操过程中,还是发现了其中的错误。
看了这么多文章和视频,脑子一拍,这不就是面向对象的编程范式吗,只不过C语言是面向过程的语言,没有现成的面向对象的组件,但是思想上完全就是OOP那套嘛!
于是我开始自己扒,终于,也是让我扒出来了~
在文章的最后我会放上一个循迹小车的项目,当然,功能上伪实现(狗头),那接下来先讲下这门技术的基本理论和开发流程吧
理论
面向对象编程
我们先来说一下面向对象编程是什么:
面向对象编程是一种编程范式,它通过定义类和对象来组织和设计程序。
在面向对象编程中,程序猿通过创建类来定义数据结构和行为,通过创建对象来实例化这些类,并通过对象之间的交互来实现程序的功能。这种方法使得程序的结构更加清晰和易于维护。
面向对象编程有几个特性,分别是:封装、继承、多态、抽象,这里就不再说了,只要知道本文会体现就行,(纠结)因为毕竟还是挺难理解的,我也讲不明白,可以看看别的大佬的文章。
为什么要把面向对象编程拉出来说呢?众所众知,嵌入式是一个软硬件结合的学科,这就会存在一个问题,就是我们会非常在乎硬件的实现,上层的功能实现就实现了,也不会在乎开发的结构、后期的维护等,这一点在初学者身上体现的淋漓尽致。
而面向对象编程就致力于让程序更加模块化,通过继承和多态,使得大量代码复用,它还有模拟现实世界中对象和关系的能力。这样,就为开发者提供了一种自顶向下的开发思路。同时,它将上层实现与下层驱动相隔离,让更换开发平台变得简单。
流程
我将整个C语言面向对象编程的开发分为三个阶段,分别是声明、实现和使用。
声明
声明阶段又可以分为五个步骤,这些都是在头文件中写入的,分别是:
- 声明接口函数
- 定义接口结构体
- 定义类结构体
- 定义类型转换内联函数
- 声明方法实例
其中,声明接口函数、定义接口结构体和定义类型转换内联函数仅需书写一次,另外两个步骤可以根据实际需求定义更多的类和方法实例。
声明接口函数
在这里,接口就是类的行为方法集,控制整个类的行为方式。以循迹模块为例,读取循迹信息就是它的一个行为;以电机驱动模块为例,控制电机停转、正转、反转和控制转动速度就是它的一系列行为。我们首先要思考我们所抽象出的类有哪些行为方法,写成下面形式:
typedef int (*Method0FnT)(void* self, ...);
typedef int (*Method1FnT)(void* self, ...);
.
.
.
typedef int (*MethodnFnT)(void* self, ...);
定义接口结构体
接下来,我们要将上面的接口函数放到一个接口结构体中,方便由各个类使用:
typedef struct
{
Method0FnT method0Fn;
Method1FnT method1Fn;
.
.
.
MethodnFT methodnFn;
} MethodsT;
定义类结构体
完成上面步骤后,一个类的方法集就总结好了,再由方法集和类的各个属性组成完整的类,这里一定要注意,方法集指针一定要放在类结构体的第一个,否则会出现错误:
typedef struct
{
MethodsT* methods;
Type attribute0;
Type attribute1;
.
.
.
Type attributen;
} Class;
定义类型转换内联函数
这里是我们实现多态这一特性最核心的步骤,写成如下格式:
static inline int method0Fn(void* self, ...)
{
return (*(MethodsT**)self)->method0Fn(self, ...);
}
static inline int method1Fn(void* self, ...)
{
return (*(MethodsT**)self)->method1Fn(self, ...);
}
.
.
.
static inline int methodnFn(void* self, ...)
{
return (*(MethodsT**)self)->methodnFn(self, ...);
}
使用上面的语句,我们能够将(*p)->f(p)
改写为f(p)
的形式,而且不需要管类的具体函数实现。这里我们将指向类的一级指针强制类型转换为指向接口的二级指针,再解引用就得到了一个仅指向接口的一级指针,再用成员访问符使用接口函数。
这个过程中,要将指向类的一级指针强制类型转换为指向接口的二级指针,就需要类的起始地址与接口的起始地址相同,也就是为什么上面说方法集的指针一定要放在类结构体的第一个,这样指向接口的二级指针解引用后才会指向接口。

声明方法实例
上面定义了抽象的接口和类,该到这个接口函数的具体实现了,当然,还要写上类初始化函数的声明:
int classMethod0(void* self, ...);
int classMethod1(void* self, ...);
.
.
.
int classMethodn(void* self, ...);
int classInit(void* self, ...);
实现
头文件中内容就完成了,下面是具体的相关函数的实现了,下面部分都在源文件中写入,分为三个步骤:
- 定义方法实例
- 定义接口实例
- 定义类初始化函数
三个步骤的内容均由头文件中的声明限制。
定义方法实例
方法的实例我们已经在头文件中声明过了,在这里我们进行这些方法实例的定义:
int classMethod0(void* self, ...);
{
Class* pClass= (Class*)self;
//具体内容实现
//异常处理
return 1;
}
int classMethod1(void* self, ...)
{
Class* pClass= (Class*)self;
...
return 1;
}
.
.
.
int classMethodn(void* self, ...)
{
Class* pClass= (Class*)self;
...
return 1;
}
定义接口实例
具体的方法已经有了,接下来我们要实现具体的接口了,将方法实例的函数指针传入到接口结构体中:
static MethodsT classMethods=
{
.method0Fn= classMethod0,
.method1Fn= classMethod1,
.
.
.
.methodnFn= classMethodn
}
定义类的初始化函数
最后我们编写所需类的初始化的函数,类的属性值将通过初始化函数传入:
int classInit(void* self, ...)
{
Class* pClass= (Class*)self;
pClass->methods= &classMethods;
pClass->attribute0= ...;
pClass->attribute1= ...;
.
.
.
pClass->attributen= ...;
//其他初始化内容
//异常处理
return 1;
}
使用
使用起来就简单了,首先我们要生成类的实例,然后使用初始化函数进行类初始化,然后,使用!
//生成实例可以放在main.h中或者主函数前或者主函数开头
Class class
//初始化要放在开头
classInit(&class, ...);
//使用方法就放在任何你需要其执行的地方即可
method0Fn(&class, ...);
method1Fn(&class, ...);
.
.
.
methodnFn(&class, ...);
就这样,我们的所有功能就实现啦,我相信你一定学会了!(狗头)
附录
循迹小车的伪实现,会体现出上面没有提及的继承特性
trace.h
#ifndef _TRACE_H
#define _TRACE_H
typedef int (*TraceReadFnT)(void* self, int* result);
typedef struct
{
TraceReadFnT traceReadFn;
} TraceMethodsT;
typedef struct
{
TraceMethodsT* pTraceMethods;
int tracePin;
} Trace;
static inline int traceReadFn(void* self, int* result)
{
return (*(TraceMethodsT**)self)->traceReadFn(self, result);
}
int traceRead(void* self, int* result);
int traceInit(void* self, int pin);
#endif
trace.c
#include <stdlib.h>
#include <stdio.h>
#include "trace.h"
int traceRead(void* self, int* result)
{
Trace* pTrace= (Trace*)self;
int num= rand()%2;
if(num!=0&&num!=1)
return 0;
*result=num;
printf("trace result is %d\n",*result);
return 1;
}
static TraceMethodsT TraceMethods=
{
.traceReadFn= (TraceReadFnT)traceRead
};
int traceInit(void* self, int pin)
{
Trace* pTrace= (Trace*)self;
pTrace->pTraceMethods= &TraceMethods;
pTrace->tracePin= pin;
printf("Trace object inited successfully\n");
return 1;
}
motor.h
#ifndef _MOTOR_H
#define _MOTOR_H
typedef enum
{
MOTOR_STOP,
MOTOR_FORWARD,
MOTOR_REVERSE
} MotorStat;
typedef int (*MotorStopFnT)(void* self);
typedef int (*MotorForwardFnT)(void* self, int speed);
typedef int (*MotorReverseFnT)(void* self, int speed);
typedef int (*MotorControlSpeedFnT)(void* self, int speed);
typedef struct
{
MotorStopFnT motorStopFn;
MotorForwardFnT motorForwardFn;
MotorReverseFnT motorReverseFn;
MotorControlSpeedFnT motorControlSpeedFn;
} MotorMethodsT;
typedef struct
{
MotorMethodsT* pMotorMethods;
int motorPinA;
int motorPinB;
int motorPinControlSpeed;
MotorStat motorStat;
int motorSpeed;
} Motor;
static inline int motorStopFn(void* self)
{
return (*(MotorMethodsT**)self)->motorStopFn(self);
}
static inline int motorForwardFn(void* self, int speed)
{
return (*(MotorMethodsT**)self)->motorForwardFn(self, speed);
}
static inline int motorReverseFn(void* self, int speed)
{
return (*(MotorMethodsT**)self)->motorReverseFn(self, speed);
}
static inline int motorControlSpeedFn(void* self, int speed)
{
return (*(MotorMethodsT**)self)->motorControlSpeedFn(self, speed);
}
int motorStop(void* self);
int motorForward(void* self, int speed);
int motorReverse(void* self, int speed);
int motorControlSpeed(void* self, int speed);
int motorInit(void* self, int pinA, int pinB, int pinControlSpeed);
#endif
motor.c
#include <stdio.h>
#include "motor.h"
int motorStop(void* self)
{
Motor* pMotor= (Motor*)self;
pMotor->motorStat= MOTOR_STOP;
pMotor->motorSpeed= 0;
printf("motor stopped\n");
return 1;
}
int motorForward(void* self, int speed)
{
Motor* pMotor= (Motor*)self;
pMotor->motorStat= MOTOR_FORWARD;
pMotor->motorSpeed= speed;
printf("motor turned forward, speed is %d\n",pMotor->motorSpeed);
return 1;
}
int motorReverse(void* self, int speed)
{
Motor* pMotor= (Motor*)self;
pMotor->motorStat= MOTOR_REVERSE;
pMotor->motorSpeed= speed;
printf("motor turned reverse, speed is %d\n",pMotor->motorSpeed);
return 1;
}
int motorControlSpeed(void* self, int speed)
{
Motor* pMotor= (Motor*)self;
pMotor->motorSpeed= speed;
printf("motor speed is turned to %d\n",pMotor->motorSpeed);
return 1;
}
static MotorMethodsT MotorMethods=
{
.motorStopFn= (MotorStopFnT)motorStop,
.motorForwardFn= (MotorForwardFnT)motorForward,
.motorReverseFn= (MotorReverseFnT)motorReverse,
.motorControlSpeedFn= (MotorControlSpeedFnT)motorControlSpeed
};
int motorInit(void* self, int pinA, int pinB, int pinControlSpeed)
{
Motor* pMotor= (Motor*)self;
pMotor->pMotorMethods= &MotorMethods;
pMotor->motorPinA= pinA;
pMotor->motorPinB= pinB;
pMotor->motorPinControlSpeed= pinControlSpeed;
pMotor->motorStat= MOTOR_STOP;
pMotor->motorSpeed= 0;
printf("motor object inited successfully\n");
return 1;
}
trail_car.h
#ifndef _TRAIL_CAR_H
#define _TRAIL_CAR_H
#include "trace.h"
#include "motor.h"
enum TrailCarStat
{
CAR_STOP,
CAR_RUN
};
typedef int (*TrailCarRunFnT)(void* self, int speedStraight, int speedTurnFast, int speedTurnLow);
typedef int (*TrailCarStopFnT)(void* self);
typedef struct
{
TrailCarRunFnT trailCarRunFn;
TrailCarStopFnT trailCarStopFn;
} TrailCarMethodsT;
typedef struct
{
TrailCarMethodsT* pTrailCarMethods;
Trace* trailCarTrace0;
Trace* trailCarTrace1;
Trace* trailCarTrace2;
Trace* trailCarTrace3;
Trace* trailCarTrace4;
Motor* trailCarMotorLeft;
Motor* trailCarMotorRight;
enum TrailCarStat trailCarStat;
} TrailCar;
static inline int trailCarRunFn(void* self, int speedStraight, int speedTurnFast, int speedTurnLow)
{
return (*(TrailCarMethodsT**)self)->trailCarRunFn(self, speedStraight, speedTurnFast, speedTurnLow);
}
static inline int trailCarStopFn(void* self)
{
return (*(TrailCarMethodsT**)self)->trailCarStopFn(self);
}
int trailCarRun(void* self, int speedStraight, int speedTurnFast, int speedTurnLow);
int trailCarStop(void* self);
int trailCarInit
(
void* self,
Trace* trace0,
Trace* trace1,
Trace* trace2,
Trace* trace3,
Trace* trace4,
Motor* motorLeft,
Motor* motorRight
);
#endif
trail_car.c
#include <stdio.h>
#include "trace.h"
#include "motor.h"
#include "trail_car.h"
int trailCarRun(void* self, int speedStraight, int speedTurnFast, int speedTurnLow)
{
TrailCar* pTrailCar= (TrailCar*)self;
pTrailCar->trailCarStat= CAR_RUN;
int result0, result1, result2, result3, result4;
traceReadFn(pTrailCar->trailCarTrace0, &result0);
traceReadFn(pTrailCar->trailCarTrace1, &result1);
traceReadFn(pTrailCar->trailCarTrace2, &result2);
traceReadFn(pTrailCar->trailCarTrace3, &result3);
traceReadFn(pTrailCar->trailCarTrace4, &result4);
if(result0==1&&result1==1&&result2==1&&result3==1&&result4==1)
{
motorStopFn(pTrailCar->trailCarMotorLeft);
motorStopFn(pTrailCar->trailCarMotorRight);
printf("trail car stopped\n");
}
else if(result0==0&&result1==0&&result2==1&&result3==0&&result4==0)
{
motorForwardFn(pTrailCar->trailCarMotorLeft, speedStraight);
motorForwardFn(pTrailCar->trailCarMotorRight, speedStraight);
printf("trail car went straight\n");
}
else if(result0==0&&result1==1&&result2==0&&result3==0&&result4==0)
{
motorForwardFn(pTrailCar->trailCarMotorLeft, speedTurnLow);
motorForwardFn(pTrailCar->trailCarMotorRight, speedTurnFast);
printf("trail car turned left\n");
}
else if(result0==0&&result1==0&&result2==0&&result3==1&&result4==0)
{
motorForwardFn((void*)(pTrailCar->trailCarMotorLeft), speedTurnFast);
motorForwardFn((void*)(pTrailCar->trailCarMotorRight), speedTurnLow);
printf("trail car turned right\n");
}
else
{
motorForwardFn(pTrailCar->trailCarMotorLeft, speedStraight);
motorForwardFn(pTrailCar->trailCarMotorRight, speedStraight);
printf("trail car went straight\n");
}
return 1;
}
int trailCarStop(void* self)
{
TrailCar* pTrailCar= (TrailCar*)self;
pTrailCar->trailCarStat= CAR_RUN;
motorStopFn(pTrailCar->trailCarMotorLeft);
motorStopFn(pTrailCar->trailCarMotorRight);
printf("trail car stopped\n");
return 1;
}
static TrailCarMethodsT TraceMethods=
{
.trailCarRunFn= (TrailCarRunFnT)trailCarRun,
.trailCarStopFn= (TrailCarStopFnT)trailCarStop
};
int trailCarInit
(
void* self,
Trace* trace0,
Trace* trace1,
Trace* trace2,
Trace* trace3,
Trace* trace4,
Motor* motorLeft,
Motor* motorRight
)
{
TrailCar* pTrailCar= (TrailCar*)self;
pTrailCar->pTrailCarMethods= &TraceMethods;
pTrailCar->trailCarTrace0= trace0;
pTrailCar->trailCarTrace1= trace1;
pTrailCar->trailCarTrace2= trace2;
pTrailCar->trailCarTrace3= trace3;
pTrailCar->trailCarTrace4= trace4;
pTrailCar->trailCarMotorLeft= motorLeft;
pTrailCar->trailCarMotorRight= motorRight;
// trailCarStopFn(self);
// pTrailCar->pTrailCarMethods.trailCarStopFn(pTrailCar);
motorStopFn(pTrailCar->trailCarMotorLeft);
motorStopFn(pTrailCar->trailCarMotorRight);
pTrailCar->trailCarStat= CAR_STOP;
return 1;
}
main.h
#ifndef _MAIN_H
#define _MAIN_H
#include "trace.h"
#include "motor.h"
#include "trail_car.h"
Trace trace0;
Trace trace1;
Trace trace2;
Trace trace3;
Trace trace4;
Motor motorLeft;
Motor motorRight;
TrailCar trailCar;
#endif
main.c
#include <stdlib.h>
#include <stdio.h>
#include "trace.h"
#include "motor.h"
#include "trail_car.h"
#include "main.h"
int main()
{
traceInit(&trace0,0);
traceInit(&trace1,1);
traceInit(&trace2,2);
traceInit(&trace3,3);
traceInit(&trace4,4);
motorInit(&motorLeft,5,6,7);
motorInit(&motorRight,8,9,10);
trailCarInit
(
(void*)&trailCar,
&trace0,
&trace1,
&trace2,
&trace3,
&trace4,
&motorLeft,
&motorRight
);
trailCarRunFn(&trailCar,100,100,100);
trailCarStopFn(&trailCar);
return 0;
}
Comments 4 条评论
哇,会长好棒
nb
Struggle发来贺电!🥳
期待煮包更多优秀的文章!☝🏻🤓
Идеальные источники бесперебойного питания для дома, в нашем руководстве.
Советы по выбору источников бесперебойного питания, изучайте.
Преимущества использования ИБП, узнайте больше.
Топ-5 ИБП для защиты техники, читайте.
Как выбрать идеальный источник бесперебойного питания, разберитесь.
Покупка ИБП: на что обратить внимание, в этой статье.
Ваш идеальный ИБП, узнайте.
Все о принципах работы источников бесперебойного питания, на нашем сайте.
Эффективное использование ИБП, читайте.
Инновации в области источников бесперебойного питания, в этой статье.
Правила подключения источника бесперебойного питания, читайте.
Как выбрать ИБП для разных нужд, узнайте.
Как выбрать оптимальный ИБП, получите советы.
Сравнение ИБП: какой выбрать?, здесь.
Советы по монтажу источников бесперебойного питания, получите информацию.
Что выбрать: ИБП или альтернативу?, в нашем блоге.
Как продлить срок службы ИБП, читайте.
Как выбрать ИБП для игры, читайте.
Что учесть при выборе источника бесперебойного питания, в нашем блоге.
источники бесперебойного питания купить [url=https://www.istochniki-bespereboynogo-pitaniya.ru/#источники-бесперебойного-питания-купить]https://www.istochniki-bespereboynogo-pitaniya.ru/[/url] .