补充知识点:
一、为什么 C 语言需要"对象模型"?
在 C 语言里,只有两种基本东西:
-
✅ 数据(变量 / struct)
-
✅ 函数(全局函数)
它没有:
- class
- method
- interface
- virtual
- 多态
但系统软件(操作系统、虚拟机、驱动、中间件)必须要有:
- 抽象接口
- 模块解耦
- 运行时替换实现
- 多态调用
于是,C 语言世界里诞生了一种"约定俗成"的结构:
👉 struct(保存状态) + 函数指针(保存行为)
这套组合,就是 C 语言的"对象模型"。
二、最小对象模型:状态 + 行为
先从一个极小例子开始,看 C 如何"造对象"。
🎯 目标:做一个"计数器对象"
我们希望它有:
-
状态:value
-
方法:inc() / get()
✅ C 语言实现:
cpp
#include <stdio.h>
typedef struct Counter Counter;
struct Counter {
int value; // 状态
void (*inc)(Counter* self); // 行为
int (*get)(Counter* self);
};
void counter_inc(Counter* self) {
self->value++;
}
int counter_get(Counter* self) {
return self->value;
}
int main() {
Counter c;
c.value = 0;
c.inc = counter_inc;
c.get = counter_get;
c.inc(&c);
c.inc(&c);
printf("%d\n", c.get(&c)); // 2
}
🔍 这里发生了什么?
struct Counter保存状态- 函数指针保存"方法"
self参数 = C 里的thisc.inc(&c)= 对象调用方法
👉 这已经是一个完整意义上的"对象"。
三、为什么函数指针一定要带 self?
因为 C 的函数不属于任何对象。
void counter_inc(Counter* self)
是普通函数,不知道"我是谁"。
所以:
👉 必须手动把对象传进去。
这在系统层极其常见:
cpp
int (*read)(struct Device* dev, void* buf, int len);
int (*start)(struct Engine* engine);
第一个参数,几乎永远是:
- self
- ctx
- handle
- object
👉 它就是"对象上下文"。
四、对象能力从哪来?三大核心特性
✅ 1. 封装(Encapsulation)
调用方不直接操作内部数据,只通过函数指针访问:
c.inc(&c);
✅ 2. 多态(Polymorphism)
同一个结构体接口,可以绑定不同实现:
cpp
void inc1(Counter* c) { c->value += 1; }
void inc2(Counter* c) { c->value += 2; }
c.inc = inc1; // 普通计数器
c.inc = inc2; // 加速计数器
👉 调用代码不变,行为变化。
✅ 3. 解耦(Decoupling)
调用方不关心:
- 函数叫什么
- 函数在哪
- 如何实现
它只认:
👉 这个 struct 里有哪些"能力"。
五、进阶:vtable(虚函数表)模型
系统层通常不会把函数指针直接塞进对象,
而是拆成两层:
-
对象:状态
-
vtable:方法表(共享)
✅ 结构升级版:
cpp
typedef struct Counter Counter;
typedef struct CounterVTable CounterVTable;
struct CounterVTable {
void (*inc)(Counter* self);
int (*get)(Counter* self);
};
struct Counter {
int value;
const CounterVTable* vptr;
};
✅ 实现:
cpp
void counter_inc(Counter* self) { self->value++; }
int counter_get(Counter* self) { return self->value; }
const CounterVTable COUNTER_VT = {
.inc = counter_inc,
.get = counter_get
};
✅ 使用:
cpp
Counter c = { .value = 0, .vptr = &COUNTER_VT };
c.vptr->inc(&c);
printf("%d\n", c.vptr->get(&c));
👉 这已经和 C++ 虚函数机制几乎完全一致:
- 对象里有 vptr
- vptr 指向函数表
- 调用 = 查表 + 跳转
六、这套模型正是系统世界的通用设计
✅ Linux 内核
cpp
struct file_operations {
int (*open)(struct inode*, struct file*);
ssize_t (*read)(struct file*, char*, size_t, loff_t*);
ssize_t (*write)(...);
};
不同文件系统 → 填不同 ops 表。
✅ HAL / 驱动 / 中间件
cpp
struct audio_hw_device {
int (*init)(...);
int (*start)(...);
int (*stop)(...);
};
✅ JNI(你非常熟)
cpp
struct JNINativeInterface_ {
jclass (*FindClass)(JNIEnv*, const char*);
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
...
};
(*env)->FindClass(env, "java/lang/String");
👉 典型:vtable + self + 查表调用。
七、统一抽象视角(非常重要)
以后你看到任何系统接口,只要问三件事:
- 这个 struct 是不是"对象"?
- 这些函数指针是不是"方法表"?
- 第一个参数是不是 self / ctx?
如果是 →
👉 这就是 C 语言对象模型。
八、和 C++ / Java 的一一对应
| C 系统层 | C++ / Java |
|---|---|
| struct | class |
| 函数指针 | method |
| self 参数 | this |
| vtable | 虚函数表 |
| ops 结构体 | interface |
| 运行时赋值 | 多态 |
👉 所谓"面向对象",在底层几乎都落回这一套。
九、一句话系统级总结
C 没有类,
但用 struct 保存状态,
用函数指针保存行为,
用 vtable 共享方法,
就造出了整个系统世界。
Linux、Android、JVM、驱动、NDK,没有例外。
下一篇: