用 C 语言实现面向对象:pThis 模式技术解析
一、引言
在嵌入式、汽车电子、驱动开发等场景中,工程约束往往要求使用纯 C 语言,而业务又希望具备封装、多实例、多态 等面向对象能力。一种成熟做法是:用结构体 + 函数指针 + 显式"当前对象"参数 在 C 中模拟类和 this 指针,该参数通常命名为 pThis。
本文从技术方法 和实现过程 出发,用通用示例对比 C++ 与 C 两种实现方式,并配合图示说明,不依赖任何具体业务代码。
二、概念对应关系
| 概念 | C++ | C(pThis 模式) |
|---|---|---|
| 类 | class |
含数据与函数指针的 struct |
| 对象 | 类实例 | struct 实例 |
| 当前对象 | 隐式 this |
显式参数 pThis |
| 成员函数 | 类内方法 | 首参为 pThis 的 C 函数 |
| 多态 | virtual 与虚表 |
结构体内函数指针,初始化时绑定 |
| 构造 | 构造函数 | XXX_Init(pThis, ...) |
三、示例场景说明
为便于说明,下文统一用一个计数器对象作为示例:
- 内部维护一个整型计数值;
- 提供:初始化、加一、取当前值、复位;
- 支持多个计数器实例,各自独立。
下面分别用 C++ 和 C(pThis)实现同一逻辑。
四、C++ 实现
4.1 类定义
cpp
// counter.hpp
#ifndef COUNTER_HPP
#define COUNTER_HPP
class Counter {
public:
Counter(const char* name); // 构造
void init(const char* name);
void increment(); // 加一
int getValue() const; // 取值
void reset(); // 复位
private:
const char* name_; // 对象名称
int value_; // 计数值
};
#endif
4.2 实现
cpp
// counter.cpp
#include "counter.hpp"
Counter::Counter(const char* name) : name_(name), value_(0) {}
void Counter::init(const char* name) {
name_ = name;
value_ = 0;
}
void Counter::increment() {
value_++; // 隐式使用 this->value_
}
int Counter::getValue() const {
return value_; // 隐式使用 this->value_
}
void Counter::reset() {
value_ = 0;
}
// 使用:多实例,各自独立
void example_cpp() {
Counter c1("A");
Counter c2("B");
c1.increment();
c1.increment();
c2.increment();
// c1.getValue() == 2, c2.getValue() == 1
}
4.3 类图
| 成员类型 | 成员名称 | 类型 | 说明 |
|---|---|---|---|
| 私有 | name_ | const char* | 对象名称 |
| 私有 | value_ | int | 计数值 |
| 公有 | init(name) | void | 初始化方法 |
| 公有 | increment() | void | 加一方法(内部通过 this->value_ 访问) |
| 公有 | getValue() | int | 获取当前值 |
| 公有 | reset() | void | 复位方法 |
五、C 实现(pThis 模式)
5.1 设计思路
- "类" :定义一个
struct,包含数据成员和可选的一组函数指针(用于多态或统一接口)。 - "当前对象" :不依赖编译器,由调用方在每次调用时传入,即第一个参数
pThis。 - "构造" :提供
Counter_Init(pThis, name),对pThis指向的实例做初始化,必要时挂接函数指针。
这样,同一套 C 函数可服务多个对象,仅通过传入不同的 pThis 区分。
5.2 头文件:结构体与接口
c
/* counter.h */
#ifndef COUNTER_H
#define COUNTER_H
typedef struct CounterObj CounterObj;
struct CounterObj {
const char* name;
int value;
/* 可选:函数指针表示"方法",便于多态或统一调用 */
void (*increment)(CounterObj* pThis);
int (*getValue)(CounterObj* pThis);
void (*reset)(CounterObj* pThis);
};
int Counter_Init(CounterObj* pThis, const char* name);
void Counter_Increment(CounterObj* pThis);
int Counter_GetValue(CounterObj* pThis);
void Counter_Reset(CounterObj* pThis);
#endif
5.3 实现文件:所有"方法"显式带 pThis
c
/* counter.c */
#include "counter.h"
static void do_increment(CounterObj* pThis) {
pThis->value++;
}
static int do_get_value(CounterObj* pThis) {
return pThis->value;
}
static void do_reset(CounterObj* pThis) {
pThis->value = 0;
}
int Counter_Init(CounterObj* pThis, const char* name) {
if (!pThis) return -1;
pThis->name = name;
pThis->value = 0;
pThis->increment = do_increment;
pThis->getValue = do_get_value;
pThis->reset = do_reset;
return 0;
}
void Counter_Increment(CounterObj* pThis) {
if (pThis) pThis->value++;
}
int Counter_GetValue(CounterObj* pThis) {
return pThis ? pThis->value : 0;
}
void Counter_Reset(CounterObj* pThis) {
if (pThis) pThis->value = 0;
}
/* 使用:多实例,同一套代码,不同 pThis */
void example_c(void) {
CounterObj c1, c2;
Counter_Init(&c1, "A");
Counter_Init(&c2, "B");
Counter_Increment(&c1);
Counter_Increment(&c1);
Counter_Increment(&c2);
/* 也可通过函数指针调用(多态/表驱动) */
c1.increment(&c1);
/* c1.value == 3, c2.value == 1 */
}
5.4 C 侧"类"与 pThis 关系
C 侧用结构体表示"类",所有"方法"的第一个参数都是 pThis,表示当前操作的对象。对应关系如下:
| 成员类型 | 成员名 | 说明 |
|---|---|---|
| 数据 | name |
对象名称 |
| 数据 | value |
计数值 |
| 函数指针 | increment(pThis) |
加一,首参为当前对象指针 |
| 函数指针 | getValue(pThis) |
取值,首参为当前对象指针 |
| 函数指针 | reset(pThis) |
复位,首参为当前对象指针 |
结构体形态可理解为:
CounterObj
├── name, value (数据)
└── increment, getValue, reset (函数指针,均带 pThis 参数)
六、pThis 在调用链中的角色
无论是 C++ 的 this 还是 C 的 pThis,在调用链中的作用都是:标识并传递"当前对象"。C++ 由编译器隐式传递,C 由开发者显式传递。下图表示一次"加一"调用的过程。
Counter_Increment() CounterObj 实例 (c1 / c2) 调用方 Counter_Increment() CounterObj 实例 (c1 / c2) 调用方 模拟 this 指针\npThis 指向对象实例 调用 Counter_Increment(&c1) 传入 pThis = &c1 pThis->>value++ 返回 调用完成
七、实现过程小结
7.1 C 侧实现步骤
-
定义"类"结构体
包含数据成员;若需要多态或统一接口,再增加函数指针成员。
-
约定"方法"形式
所有操作该对象的函数,第一个参数为
XXXObj* pThis,并在函数体内通过pThis->成员访问数据。 -
提供 Init
实现
XXX_Init(pThis, ...),对pThis指向的实例赋初值,必要时给函数指针赋值。 -
多实例使用
声明多个
struct实例,分别调用Init(&obj1, ...)、Init(&obj2, ...),之后所有接口都传入对应的&obj1/&obj2作为pThis。
7.2 流程概览(Mermaid)
定义 struct + 数据/函数指针
实现带 pThis 的 C 函数
实现 Init 绑定函数指针
多实例 Init 不同 pThis
业务调用时传入对应 pThis
八、C 与 C++ 实现对比
| 维度 | C++ | C(pThis) |
|---|---|---|
| 当前对象 | 编译器隐式 this |
显式参数 pThis |
| 多实例 | 多个类实例 | 多个 struct,传不同 pThis |
| 多态 | virtual + 虚表 |
结构体内函数指针,Init 时绑定 |
| 封装 | private / 命名空间 |
static 函数 + 仅暴露头文件 API |
| 可读性 | 更贴近"对象"思维 | 需约定"首参即 pThis" |
| 典型场景 | 允许 C++ 的项目 | 纯 C、MISRA-C、传统固件 |
九、适用场景与注意点
适合采用 pThis 模式的情况:
- 需要多个逻辑相同、数据独立的实例;
- 希望用函数指针做表驱动或策略替换(类似虚函数);
- 项目仅允许使用 C;
- 希望统一风格:所有"对象方法"首参为
pThis。
注意点:
- 调用时务必传入有效指针,避免空指针或野指针;
- 多线程场景下,若对象被多线程共享,需自行加锁保护;
- 函数指针在 Init 时绑定,避免未初始化即通过函数指针调用。
十、总结
- C++ 通过
class和隐式this表达"当前对象",语法自然。 - C 通过
struct+ 函数指针 + 显式pThis达到相同效果:同一套逻辑、多实例、可多态。
将 pThis 理解为"C 里的 this",并按照"定义结构体 → 实现带 pThis 的函数 → Init 绑定 → 多实例传不同 pThis"的过程实现,即可在纯 C 环境中复用面向对象的设计思路,而不依赖具体业务代码。本文提供的计数器示例与图示可直接用于理解与方法迁移。