下面我将详细解释如何在C语言中实现OOP的三大特性,并提供完整的代码示例。

核心思想:用结构体模拟对象
在C语言中,实现OOP的核心工具是 struct。
一个面向对象的对象,通常包含两个部分:
- 属性:对象的数据成员。
- 方法:对象的行为(函数)。
在C++/Java中,方法是对象的一部分,但在C中,我们无法将函数“放进”结构体里,我们采用一个变通的方法:
- 属性:直接作为结构体的成员变量。
- 方法:使用函数指针,作为结构体的成员。
通过这种方式,我们创建了一个包含数据和操作该数据函数指针的“对象”。

封装
封装是指将数据和操作数据的方法捆绑在一起,并对外部隐藏实现细节。
在C语言中,我们可以通过 不透明指针 和 头文件/源文件分离 来实现封装。
实现方式:
- 在头文件(如
shape.h)中,只声明一个不透明的指针struct Shape*。 - 在源文件(如
shape.c)中,定义struct Shape的具体内容,包括其属性和函数指针。 - 用户只能通过头文件中提供的函数(如
Shape_create(),Shape_draw())来操作这个对象,而无法直接访问其内部成员。
示例代码:

shape.h (接口/头文件)
#ifndef SHAPE_H #define SHAPE_H // 前向声明,告诉编译器 "struct Shape" 存在,但具体内容未知 typedef struct Shape Shape; // 创建一个 Shape 对象的函数 Shape* Shape_create(int x, int y); // 销毁 Shape 对象的函数 void Shape_destroy(Shape* this); // 绘制 Shape 对象的函数 void Shape_draw(Shape* this); #endif // SHAPE_H
shape.c (实现/源文件)
#include <stdio.h>
#include <stdlib.h>
#include "shape.h"
// 这里是真正的结构体定义,对外部是隐藏的
struct Shape {
int x;
int y;
// 函数指针,指向“绘制”行为
void (*draw)(struct Shape* this);
};
// 实际的绘制函数
void shape_draw_impl(Shape* this) {
printf("Drawing a generic shape at (%d, %d)\n", this->x, this->y);
}
// 创建函数的实现
Shape* Shape_create(int x, int y) {
// 为结构体分配内存
Shape* s = malloc(sizeof(Shape));
if (s == NULL) {
return NULL;
}
s->x = x;
s->y = y;
// 将函数指针指向具体的实现函数
s->draw = shape_draw_impl;
return s;
}
// 销毁函数的实现
void Shape_destroy(Shape* this) {
free(this);
}
main.c (使用)
#include "shape.h"
int main() {
// 创建一个 Shape 对象
Shape* my_shape = Shape_create(10, 20);
// 调用其方法,无需关心内部实现
my_shape->draw(my_shape);
// 销毁对象
Shape_destroy(my_shape);
return 0;
}
封装的效果:
main.c的使用者只知道如何通过Shape_create、Shape_draw等函数与Shape交互。- 他们无法直接访问
my_shape->x,也无法修改my_shape->draw指针。struct Shape的具体实现被完美地隐藏在了shape.c中,这就是封装。
继承
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,并可以扩展或重写它们。
在C语言中,我们通过 结构体嵌套 来模拟继承。
实现方式:
- 定义“子类”结构体时,将其第一个成员变量定义为“父类”结构体。
- (重要!) 这样做是为了保证类型转换的安全性,在C中,将指向子类结构体的指针转换为指向父类结构体的指针是安全的,因为子类结构体的起始地址就是其父类部分的起始地址。
- 子类可以拥有自己的新属性和新方法。
示例代码:
我们基于上面的 Shape 类,创建一个 Circle 类。
circle.h
#ifndef CIRCLE_H #define CIRCLE_H #include "shape.h" // 包入父类的头文件 // Circle 继承自 Shape typedef struct Circle Circle; Circle* Circle_create(int x, int y, int radius); void Circle_destroy(Circle* this); void Circle_draw(Circle* this); // 重写父类的 draw 方法 #endif // CIRCLE_H
circle.c
#include <stdio.h>
#include <stdlib.h>
#include "circle.h"
// Circle 结构体的第一个成员必须是它的父类 Shape
// 这是为了实现安全的向上转型
struct Circle {
Shape base; // 继承自 Shape 的所有属性和方法
int radius; // Circle 自己的属性
};
// Circle 自己的绘制实现
void circle_draw_impl(Shape* this) {
// 首先将父类指针转换回子类指针,以访问子类特有属性
Circle* c = (Circle*)this;
printf("Drawing a circle at (%d, %d) with radius %d\n",
c->base.x, c->base.y, c->radius);
}
// 创建函数
Circle* Circle_create(int x, int y, int radius) {
Circle* c = malloc(sizeof(Circle));
if (c == NULL) {
return NULL;
}
// 调用父类的创建函数来初始化继承的部分
// 注意:这里我们直接操作内部成员,只是为了演示,更好的做法是提供初始化函数。
c->base.x = x;
c->base.y = y;
c->radius = radius;
// 关键:重写父类的 draw 函数指针,指向自己的实现
c->base.draw = circle_draw_impl;
return c;
}
// 销毁函数
void Circle_destroy(Circle* this) {
free(this);
}
main.c (测试继承)
#include "circle.h"
int main() {
// 创建一个 Circle 对象
Circle* my_circle = Circle_create(15, 25, 50);
// 通过父类指针来操作子类对象,这就是多态的基础
Shape* my_shape = (Shape*)my_circle; // 安全的类型转换
// 调用 draw,它会执行 Circle 的 draw 方法,而不是 Shape 的
my_shape->draw(my_shape);
Circle_destroy(my_circle);
return 0;
}
继承的效果:
Circle拥有了Shape的所有属性(x,y)和方法(draw的接口)。Circle增加了自己的新属性(radius)。Circle可以重写父类的方法(draw),使其行为更具体。
多态
多态是指同一个接口(方法),作用于不同的对象时,会产生不同的执行效果。
在C语言中,多态是自动实现的,只要我们正确地使用了继承和函数指针。
实现方式:
- 定义一个父类指针(如
Shape*)。 - 让这个指针指向任何一个子类的对象(如
Circle*,Rectangle*等)。 - 通过这个父类指针调用方法(如
my_shape->draw(my_shape))。 - 程序会根据指针实际指向的对象类型,调用该对象自己的方法实现。
示例代码:
我们再创建一个 Rectangle 类来和 Circle 一起展示多态。
rectangle.h
#ifndef RECTANGLE_H #define RECTANGLE_H #include "shape.h" typedef struct Rectangle Rectangle; Rectangle* Rectangle_create(int x, int y, int width, int height); void Rectangle_destroy(Rectangle* this); void Rectangle_draw(Rectangle* this); #endif // RECTANGLE_H
rectangle.c
#include <stdio.h>
#include <stdlib.h>
#include "rectangle.h"
struct Rectangle {
Shape base;
int width;
int height;
};
void rectangle_draw_impl(Shape* this) {
Rectangle* r = (Rectangle*)this;
printf("Drawing a rectangle at (%d, %d) with size %dx%d\n",
r->base.x, r->base.y, r->width, r->height);
}
Rectangle* Rectangle_create(int x, int y, int width, int height) {
Rectangle* r = malloc(sizeof(Rectangle));
if (r == NULL) {
return NULL;
}
r->base.x = x;
r->base.y = y;
r->width = width;
r->height = height;
r->base.draw = rectangle_draw_impl;
return r;
}
void Rectangle_destroy(Rectangle* this) {
free(this);
}
main.c (展示多态)
#include "shape.h"
#include "circle.h"
#include "rectangle.h"
// 一个函数,它接受一个 Shape 指针,并调用它的 draw 方法
// 它不关心这个 Shape 具体是什么,只知道它有 draw 方法
void draw_shape(Shape* shape) {
if (shape) {
shape->draw(shape);
}
}
int main() {
// 创建一个父类指针数组,里面存放着不同子类的对象
Shape* shapes[3];
shapes[0] = (Shape*)Circle_create(10, 20, 30);
shapes[1] = (Shape*)Rectangle_create(5, 5, 100, 50);
shapes[2] = (Shape*)Shape_create(0, 0); // 一个普通的 Shape 对象
// 循环调用,通过同一个接口,得到不同的行为
for (int i = 0; i < 3; i++) {
draw_shape(shapes[i]);
}
// 销毁所有对象
for (int i = 0; i < 3; i++) {
// 注意:这里我们调用了 Shape 的销毁函数。
// 一个更健壮的系统需要每个子类都有自己的销毁逻辑,
// 并且通过一个通用的销毁接口来调用。
// 为了简单,这里直接 free。
free(shapes[i]);
}
return 0;
}
多态的效果:
draw_shape 函数只认识 Shape 类型,它不知道也不关心自己操作的是一个 Circle 还是 Rectangle,当它调用 shape->draw() 时,程序会根据 shape 指针在运行时实际指向的对象类型,自动找到正确的 draw 函数(circle_draw_impl 或 rectangle_draw_impl)来执行,这就是“同一接口,不同行为”的多态。
总结与优缺点
如何总结C语言的OOP实现:
| OOP特性 | C语言实现方式 | 关键点 |
|---|---|---|
| 对象 | struct 包含数据成员和函数指针成员 |
将数据和操作数据的方法绑定在一起 |
| 封装 | 不透明指针 + 头/源文件分离 | 隐藏 struct 内部细节,只暴露函数接口 |
| 继承 | 结构体嵌套(将父类作为子类的第一个成员) | 保证类型转换安全,实现代码复用 |
| 多态 | 父类指针指向子类对象 + 函数指针 | 运行时根据对象类型调用正确的函数实现 |
优点:
- 灵活高效:没有C++/Java那样的虚函数表开销,性能非常接近原生C函数。
- 跨平台:纯C语言,几乎可以在任何系统上编译和运行。
- 兼容性好:可以轻松地与现有的C库和系统集成。
缺点:
- 代码冗长:需要手动管理内存,编写大量的样板代码(创建、销毁函数等)。
- 类型安全性差:所有类型转换都需要手动完成,容易出错,编译器无法检查。
- 缺乏语言级支持:没有构造/析构函数、访问修饰符(
public/private)、模板等高级特性,一切都需要手动模拟,增加了复杂度。 - 调试困难:由于缺少编译时的类型检查,一些错误只能在运行时才能发现。
尽管存在这些缺点,这种在C语言中模拟OOP的范式(尤其是在Linux内核、GTK+、OpenGL等大型项目中)被证明是非常强大和成功的,它为C语言提供了一种组织大型、复杂代码的有效方式。
