Skip to content

[English version](/posts/c教程/en-US/04-Structuring Data/039-Minimal Object-Oriented Design in C)

C 没有类或继承,但它通过约定为您提供结构、函数指针和封装。有了这些,您可以构建简单、快速且明确的面向对象风格的系统。您将学习如何设计“拥有”数据和行为的数据结构,例如轻量级对象。

核心理念 ​

在面向对象的设计中,对象结合了:

  • 数据 → 状态
  • 功能 → 操作

在 C 中,您可以通过将函数指针放置在结构中并将它们视为“方法”来实现此目的。

一个简单的例子:计数器对象 ​

#include <stdio.h>
#include <stdlib.h>
typedef struct Counter Counter; // forward declaration
struct Counter {
    int value;
    // methods (function pointers)
    void (*inc)(Counter *self);
    void (*reset)(Counter *self);
    void (*print)(const Counter *self);
};
void counter_inc(Counter *self) { self->value++; }
void counter_reset(Counter *self) { self->value = 0; }
void counter_print(const Counter *self) { printf("Value: %d\n", self->value); }
Counter* new_counter(void) {
    Counter *c = malloc(sizeof(Counter));
    c->value = 0;
    c->inc = counter_inc;
    c->reset = counter_reset;
    c->print = counter_print;
    return c;
}
void free_counter(Counter *c) { free(c); }
int main(void) {
    Counter *c = new_counter();
    c->inc(c);
    c->inc(c);
    c->print(c);
    c->reset(c);
    c->print(c);
    free_counter(c);
    return 0;
}

输出:

Value: 2
Value: 0

这里,Counter行为就像一个小类:它存储状态(value)及其方法(inc,reset,print).

它是如何运作的 ​

概念(面向对象编程)相当于 C
班级struct定义
对象一个实例(malloced 结构)
方法函数指针
构造函数new_...()功能
析构函数free_...()功能
这指向结构体的指针(self)

示例:形状接口(多态性) ​

您可以使用函数指针模拟多态性,即在不同类型上调用相同函数名的能力。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
typedef struct Shape Shape;
struct Shape {
    double (*area)(Shape *self);
    void (*print)(Shape *self);
};
typedef struct {
    Shape base;
    double radius;
} Circle;
typedef struct {
    Shape base;
    double width, height;
} Rectangle;
double circle_area(Shape *s) {
    Circle *c = (Circle*)s;
    return M_PI * c->radius * c->radius;
}
void circle_print(Shape *s) {
    Circle *c = (Circle*)s;
    printf("Circle (r=%.2f) area=%.2f\n", c->radius, circle_area(s));
}
double rect_area(Shape *s) {
    Rectangle *r = (Rectangle*)s;
    return r->width * r->height;
}
void rect_print(Shape *s) {
    Rectangle *r = (Rectangle*)s;
    printf("Rectangle (%.2fx%.2f) area=%.2f\n",
           r->width, r->height, rect_area(s));
}
Shape* new_circle(double r) {
    Circle *c = malloc(sizeof(Circle));
    c->radius = r;
    c->base.area = circle_area;
    c->base.print = circle_print;
    return (Shape*)c;
}
Shape* new_rectangle(double w, double h) {
    Rectangle *r = malloc(sizeof(Rectangle));
    r->width = w;
    r->height = h;
    r->base.area = rect_area;
    r->base.print = rect_print;
    return (Shape*)r;
}
int main(void) {
    Shape *s1 = new_circle(2.5);
    Shape *s2 = new_rectangle(3.0, 4.0);
    s1->print(s1);
    s2->print(s2);
    free(s1);
    free(s2);
    return 0;
}

输出:

Circle (r=2.50) area=19.63
Rectangle (3.00x4.00) area=12.00

两种形状共享相同的“界面”(area,print)但行为不同,典型的多态性。

为什么这有效 ​

每个“对象”都存储指向其方法的指针,因此您可以在不知道确切类型的情况下调用它们。第一个字段(base) 在派生结构中允许在父级 (Shape*)和孩子(Circle*,Rectangle*)。这模仿了组合继承。

好处 ​

  • 提供接口和实现之间的清晰分离。
  • 启用运行时调度(函数行为取决于类型)。
  • 保持代码模块化,函数可以对抽象“对象”进行操作。
  • 用于 Linux 内核、GTK 和 SQLite 等主要 C 项目。

局限性 ​

  • 没有真正的类型安全,强制转换可能会出错。
  • 没有自动析构函数或构造函数(您必须管理内存)。
  • 没有继承语法,一切都是明确的。

但这些也是优势:没有什么是隐藏的,一切都在你的掌控之中。

自己尝试一下 ​

  1. 添加新形状:Triangle与底座和高度。
  2. 编写函数print_all(Shape **arr, int n)打印数组中的所有形状。 3.添加一个destroy(Shape *s)方法指针并实现特定于类型的清理。
  3. 延长Counter结构体有一个decrement方法。
  4. 尝试设计一个小的“界面”Animal→Dog,Cat与一个speak()功能。

借助结构体和函数指针,C 成为一个最小但功能强大的对象系统。您现在拥有设计可重用、模块化代码所需的一切,同时又不会失去 C 语言永恒的清晰度和效率。

接下来,您将通过将所有这些想法放在一起来完成本章:用 C 语言构建一个小型的、真实的系统,您自己的小型库系统,具有数据结构、内存管理和模块化设计。

本站总访问 — 次