Coop
Using C language to implement object-oriented programming
Install / Use
/learn @NUC-A413/CoopREADME
coop
本文将大量涉及C语言高级操作,如函数指针、结构体指针、二级指针、指针频繁引用解引用、typedef、static、inline和C语言项目结构等知识,请确保自己不会被上述知识强暴,如果没有这顾虑,请尽情享受~
渊源
一开始时候,我是不知道这个技术的。在某一天我在刷B站的时候,看到一个作者为“一点五编程”的视频。他提出了一种编程思想,命名为“一点五编程”。其中:
"一"指的是模块化思想
“点五”指的是(*p)->f(p)技巧
我一看,好像是一种高端的技巧哇,于是开始看他的视频,发现讲解这一技术核心的视频全都是充电的!!!好吧,那我只好翻你文档看了,找到了他的个人博客。唔,好像什么都写了,但好像少点什么,,,哦,没教我到底怎么组织文件。
然后我继续翻网页,在CSDN上发现三篇文章,讲的是对“一点五编程”的解读。但是后来在自己实操过程中,还是发现了其中的错误。
看了这么多文章和视频,脑子一拍,这不就是面向对象的编程范式吗,只不过C语言是面向过程的语言,没有现成的面向对象的组件,但是思想上完全就是OOP那套嘛!
于是我开始自己扒,终于,也是让我扒出来了~
在这个仓库里我会放上一个循迹小车的项目和一套可供修改使用的模板,当然,小车在功能上伪实现(狗头),那接下来先讲下这门技术的基本理论和开发流程吧
理论
面向对象编程
我们先来说一下面向对象编程是什么:
面向对象编程是一种编程范式,它通过定义类和对象来组织和设计程序。
在面向对象编程中,程序猿通过创建类来定义数据结构和行为,通过创建对象来实例化这些类,并通过对象之间的交互来实现程序的功能。这种方法使得程序的结构更加清晰和易于维护。
面向对象编程有几个特性,分别是:封装、继承、多态、抽象,这里就不再说了,只要知道本文会体现就行,(纠结)因为毕竟还是挺难理解的,我也讲不明白,可以看看别的大佬的文章。
为什么要把面向对象编程拉出来说呢?众所众知,嵌入式是一个软硬件结合的学科,这就会存在一个问题,就是我们会非常在乎硬件的实现,上层的功能实现就实现了,也不会在乎开发的结构、后期的维护等,这一点在初学者身上体现的淋漓尽致。
而面向对象编程就致力于让程序更加模块化,通过继承和多态,使得大量代码复用,它还有模拟现实世界中对象和关系的能力。这样,就为开发者提供了一种自顶向下的开发思路。同时,它将上层实现与下层驱动相隔离,让更换开发平台变得简单。
流程
我将整个C语言面向对象编程的开发分为三个阶段,分别是声明、实现和使用。
声明
声明阶段又可以分为五个步骤,这些都是在<mark>头文件</mark>中写入的,分别是:
- 声明接口函数
- 定义接口结构体
- 定义类型转换内联函数
- 定义类结构体
- 声明方法实例 其中,声明接口函数、定义接口结构体和定义类型转换内联函数仅需书写一次,另外两个步骤可以根据实际需求定义更多的类和方法实例。
声明接口函数
在这里,接口就是类的行为方法集,控制整个类的行为方式。以循迹模块为例,读取循迹信息就是它的一个行为;以电机驱动模块为例,控制电机停转、正转、反转和控制转动速度就是它的一系列行为。我们首先要思考我们所抽象出的类有哪些行为方法,写成下面形式:
typedef int (*Method0FnT)(void* self, ...);
typedef int (*Method1FnT)(void* self, ...);
.
.
.
typedef int (*MethodnFnT)(void* self, ...);
定义接口结构体
接下来,我们要将上面的接口函数放到一个接口结构体中,方便由各个类使用:
typedef struct
{
Method0FnT method0Fn;
Method1FnT method1Fn;
.
.
.
MethodnFnT methodnFn;
} MethodsT;
定义类型转换内联函数
这里是我们实现多态这一特性最核心的步骤,写成如下格式:
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)的形式,而且不需要管类的具体函数实现。这里我们将指向类的一级指针强制类型转换为指向接口的二级指针,再解引用就得到了一个仅指向接口的一级指针,再用成员访问符使用接口函数。
这个过程中,要将指向类的一级指针强制类型转换为指向接口的二级指针,就需要类的起始地址与接口的起始地址相同,也就是为什么上面说方法集的指针一定要放在类结构体的第一个,这样指向接口的二级指针解引用后才会指向接口。
定义类结构体
完成上面步骤后,一个类的方法集就总结好了,再由方法集和类的各个属性组成完整的类,这里一定要注意,方法集指针一定要放在类结构体的第一个,否则会出现错误:
typedef struct
{
MethodsT* methods;
Type attribute0;
Type attribute1;
.
.
.
Type attributen;
} Class;
声明方法实例
上面定义了抽象的接口和类,该到这个接口函数的具体实现了,当然,还要写上类初始化函数的声明:
int classMethod0(void* self, ...);
int classMethod1(void* self, ...);
.
.
.
int classMethodn(void* self, ...);
int classInit(void* self, ...);
实现
<mark>头文件</mark>内容就完成了,下面是具体的相关函数的实现了,下面部分都在<mark>源文件</mark>中写入,分为三个步骤:
- 定义方法实例
- 定义接口实例
- 定义类初始化函数 三个步骤的内容均由头文件中的声明限制。
定义方法实例
方法的实例我们已经在头文件中声明过了,在这里我们进行这些方法实例的定义:
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, ...);
就这样,我们的所有功能就实现啦,我相信你一定学会了!(狗头)
附录
循迹小车的伪实现,会体现出上面没有提及的继承特性,见本仓库example_project文件夹。
我们还提供了一套代码模板,供直接修改使用,见本仓库template文件夹。
Related Skills
node-connect
345.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
104.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。

