从 Android 回调到 C 接口:函数指针 + void* self 的一次彻底理解

一、为什么学 C 回调会觉得"抽象"?

很多 Android / Java 开发者在第一次接触 C 回调时,都会被下面这种代码劝退:

复制代码
typedef struct {
    void (*open)(void* self);
    void (*close)(void* self);
} Ops;

看起来既不像函数,也不像类,更不像接口。

但当我真正理解它之后,才发现:

这套写法,本质上和 Android 的接口回调是完全一致的,只是语法层面更底层。

二、从 Android 的 setOnClickListener 说起

在 Android 中,我们非常熟悉这种写法:

复制代码
button.setOnClickListener(listener);

这里发生了三件事:

  1. listener 是一个对象实例

  2. 它实现了 OnClickListener 接口

  3. 系统在合适的时机回调:

    listener.onClick(view);

关键点在于:

  • 系统并不关心 listener 的具体类型

  • 只关心:你有没有实现规定的方法

三、C 语言里没有接口,那怎么办?

C 语言没有:

  • class
  • interface
  • this

但系统级代码同样需要:

  • 回调
  • 解耦
  • 多态

于是 C 选择了一种"手工实现接口"的方式:

struct + 函数指针

四、Ops:C 里的"接口定义"

复制代码
typedef struct {
    void (*open)(void* self);
    void (*close)(void* self);
} Ops;

这段代码不是在实现逻辑,而是在做一件事:

定义一组能力约定(接口)

可以直接在脑子里翻译成 Java:

复制代码
interface Ops {
    void open();
    void close();
}

区别只有一个:

  • Java 有隐式 this
  • C 需要显式传 self

五、void* self 是什么?

void* self 的本质是:

C 版的 this / Context / 上下文对象

因为 C 没有对象模型,所以:

  • 谁在调用

  • 状态在哪里

都必须由调用方手动传入

复制代码
void file_open(void* self) {
    File* f = (File*)self;
    printf("%d\n", f->fd);
}

这和 Java 里的:

复制代码
void open() {
    System.out.println(this.fd);
}

在"角色"上是完全等价的。

六、接口是怎么"绑定实现"的?

很多人会问:

file_open 为什么会成为 open 的实现?

答案不在函数名,而在赋值

复制代码
Ops file_ops = {
    .open  = file_open,
    .close = file_close
};

这一刻发生了绑定:

  • file_ops.open 指向 file_open

  • file_ops.close 指向 file_close

之后的调用:

复制代码
file_ops.open(&f);

等价于:

复制代码
file_open(&f);

七、file_ops 是什么角色?

可以非常准确地说:

file_ops****是一个"接口实例 / 方法表 / 回调对象"

它就像 Android 里的:

复制代码
OnClickListener listener = new MyClickListener();

只不过在 C 里:

  • 方法表(Ops)

  • 对象实例(self)

被拆开保存。

八、一句话总结构成完整模型

**C 的接口回调 = struct(接口定义)

  • 函数指针(方法)

  • void* self(this / Context)**

这并不是"奇怪的 C 写法",而是:

在没有语言级支持的情况下,手工实现的面向对象与接口机制。

九、写在最后

当我把 C 的这套回调模型和 Android 的接口机制对齐之后,才真正意识到:

语言不同,但工程思想是完全相通的。

相关推荐
wuxianda10309 小时前
苹果App上架4.3a问题3天解决方案汇报总结
开发语言·javascript·uni-app·ecmascript·ios上架·苹果上架
無斜9 小时前
【CAPL实用开发】--- CAPL调用 .NET DLL
开发语言·c#·capl·canoe
石榴树下的七彩鱼9 小时前
OCR API价格对比2026:身份证/发票/医疗票据识别哪家性价比最高?含Python对接+成本公式
开发语言·人工智能·python·ocr·图像识别·文字识别·api接口
大龄程序员狗哥9 小时前
第49篇:TensorFlow Lite实战——将图像分类模型部署到安卓手机(项目实战)
android·分类·tensorflow
风筝在晴天搁浅9 小时前
手撕单例模式
java·开发语言·单例模式
BetterNow.9 小时前
安卓内存Previous为什么可以算进freeRam
android·linux·安卓·安卓性能·安卓内存
码云数智-园园9 小时前
PHP 8.x 命名的参数与属性(Attribute):告别注释,构建真正的元数据
android·ide·android studio
0pen19 小时前
ZygiskNext 源码解析(三):zygiskd 的模块管理、memfd 与 companion
android·安全·开源
Android_xiong_st9 小时前
(原创)2026安卓面试复盘
android·面试·职场和发展
70asunflower9 小时前
堆与栈:C 语言内存管理的核心概念
c语言·开发语言