C实现多态
2026/8/23大约 4 分钟
C语言用函数指针实现多态:从 UART 到多外设
C 没有 C++ 的类和虚函数,但可以用:
函数指针 + 统一接口 +
void \*self
实现类似的多态。
下面从一个 UART 一步一步推到 UART、SPI、I2C。
一、先有一个 UART
最直接的写法:
typedef struct
{
int port;
} UartDevice;
void uart_open(UartDevice *uart);
int uart_read(UartDevice *uart, uint8_t *buf, int len);
void uart_close(UartDevice *uart);调用:
uart_open(&uart1);
uart_read(&uart1, buf, 10);
uart_close(&uart1);问题是:
上层代码直接依赖 UART。
以后加入 SPI、I2C,就会出现:
uart_read(...);
spi_read(...);
i2c_read(...);上层必须知道每一种设备。
所以第一步要做的是:
把“设备有什么操作”抽象出来。
二、把设备操作抽出来
不同设备虽然具体实现不同,但都有:
open
read
close于是定义操作表:
typedef struct
{
void (*open)(void *self);
int (*read)(void *self, uint8_t *buf, int len);
void (*close)(void *self);
} DeviceOps;这里保存的不是数据,而是:
open → 哪个函数
read → 哪个函数
close → 哪个函数因此:
DeviceOps就是设备的“操作表”。
三、再定义一个通用设备
现在我们已经把“做什么”抽出来了,还需要解决:
这些操作到底作用于哪个具体设备?
所以:
typedef struct
{
DeviceOps ops;
void *self;
} Device;ops 表示:
调用哪个函数。
self 表示:
这个函数操作哪个对象。
self 类似 C++ 中的 this。
四、把 UART 接进来
UART 自己还有自己的数据:
typedef struct
{
Device base;
int port;
} UartDevice;UART 的具体实现:
void uart_open(void *self)
{
UartDevice *uart = (UartDevice *)self;
printf("UART%d open\n", uart->port);
}
int uart_read(void *self, uint8_t *buf, int len)
{
UartDevice *uart = (UartDevice *)self;
printf("UART%d read %d bytes\n", uart->port, len);
return len;
}
void uart_close(void *self)
{
UartDevice *uart = (UartDevice *)self;
printf("UART%d close\n", uart->port);
}为什么要把 void *self 转回 UartDevice *?
因为 DeviceOps 是通用的,它不知道具体设备是什么。
只有 UART 自己知道:
UartDevice *uart = (UartDevice *)self;这样才能访问:
uart->port五、初始化 UART
UartDevice uart1;
void uart_init(void)
{
uart1.port = 1;
uart1.base.ops.open = uart_open;
uart1.base.ops.read = uart_read;
uart1.base.ops.close = uart_close;
uart1.base.self = &uart1;
}这一步完成了两件事:
ops
↓
告诉 Device:
“这些操作由 UART 函数实现”
self
↓
告诉 UART:
“你操作的是 uart1”六、统一调用接口
现在上层不应该再直接写:
uart1.base.ops.read(...);而是再封装一层:
void device_open(Device *dev)
{
dev->ops.open(dev->self);
}
int device_read(Device *dev, uint8_t *buf, int len)
{
return dev->ops.read(dev->self, buf, len);
}
void device_close(Device *dev)
{
dev->ops.close(dev->self);
}于是:
device_open(&uart1.base);
device_read(&uart1.base, buf, 10);
device_close(&uart1.base);调用链就是:
device_read()
↓
dev->ops.read
↓
uart_read()
↓
self → uart1到这里,UART 已经完成了抽象。
七、从 UART 扩展到 SPI、I2C
现在增加 SPI:
typedef struct
{
Device base;
int bus;
} SpiDevice;SPI 只需要实现自己的操作:
void spi_open(void *self)
{
SpiDevice *spi = (SpiDevice *)self;
printf("SPI%d open\n", spi->bus);
}
int spi_read(void *self, uint8_t *buf, int len)
{
SpiDevice *spi = (SpiDevice *)self;
printf("SPI%d read %d bytes\n", spi->bus, len);
return len;
}
void spi_close(void *self)
{
SpiDevice *spi = (SpiDevice *)self;
printf("SPI%d close\n", spi->bus);
}初始化:
SpiDevice spi1 =
{
.base =
{
.ops =
{
.open = spi_open,
.read = spi_read,
.close = spi_close
},
.self = &spi1
},
.bus = 1
};I2C 完全一样:
typedef struct
{
Device base;
uint8_t addr;
} I2cDevice;实现:
void i2c_open(void *self)
{
I2cDevice *i2c = (I2cDevice *)self;
printf("I2C 0x%02X open\n", i2c->addr);
}
int i2c_read(void *self, uint8_t *buf, int len)
{
I2cDevice *i2c = (I2cDevice *)self;
printf("I2C 0x%02X read %d bytes\n", i2c->addr, len);
return len;
}
void i2c_close(void *self)
{
I2cDevice *i2c = (I2cDevice *)self;
printf("I2C 0x%02X close\n", i2c->addr);
}八、上层代码完全统一
现在可以把三种设备放进同一个数组:
Device *devices[] =
{
&uart1.base,
&spi1.base,
&i2c1.base
};统一调用:
for (int i = 0; i < 3; i++)
{
device_open(devices[i]);
device_read(devices[i], buf, sizeof(buf));
device_close(devices[i]);
}上层根本不需要判断:
if (UART)
else if (SPI)
else if (I2C)因为:
Device
↓
ops
↓
函数指针
↓
具体实现不同设备只需要替换自己的 ops。
九、为什么这就是 C 语言的“多态”?
最终形成:
Device
│
┌─────┴─────┐
│ │
ops self
│ │
┌──────┼──────┐ │
↓ ↓ ↓ ↓
open read close 具体设备
│ │ │
UART UART UART
SPI SPI SPI
I2C I2C I2C核心只有三件事:
DeviceOps
↓
规定“有哪些操作”
void *self
↓
规定“操作谁”
函数指针
↓
决定“具体调用哪个实现”因此:
接口相同,具体实现不同;调用者只依赖接口,不依赖具体设备。
这就是 C 语言用函数指针实现的多态,也是 Linux VFS、驱动框架中常见的思想。
完整代码
#include<stdio.h>
#include<stdint.h>
#include<stdlib.h>
typedef struct
{
void (*open)(void* self);
int (*read)(void* self,uint8_t* buf,int len);
void (*close)(void* self);
}DeviceOps;
typedef struct
{
DeviceOps ops;
void* self;
}Device;
typedef struct
{
Device base;
int port;
}UartDevice;
UartDevice uart1;
/* UART具体实现 */
void uart_open(void* self)
{
UartDevice* uart = (UartDevice*)self;
printf("uart%d open\n", uart->port);
}
int uart_read(void* self, uint8_t* buf, int len)
{
UartDevice* uart = (UartDevice*)self;
printf("uart%d read, len = %d\n", uart->port, len);
return 0;
}
void uart_close(void* self)
{
UartDevice* uart = (UartDevice*)self;
printf("uart%d close\n", uart->port);
}
/* 统一接口 */
int device_read(Device* dev, uint8_t* buf, int len)
{
return dev->ops.read(dev->self, buf, len);
}
void device_open(Device* dev)
{
dev->ops.open(dev->self);
}
void device_close(Device* dev)
{
dev->ops.close(dev->self);
}
/* 初始化 UART */
void device_init(void)
{
uart1.port = 1;
uart1.base.ops.open = uart_open;
uart1.base.ops.read = uart_read;
uart1.base.ops.close = uart_close;
uart1.base.self = &uart1;
}
int main(void)
{
uint8_t buf[10];
device_init();
device_open(&uart1.base);
device_read(&uart1.base, buf, sizeof(buf));
device_close(&uart1.base);
return 0;
}
