Я склонен делать что-то вроде этого:
struct foo_ops {
void (*blah)(struct foo *, ...);
void (*plugh)(struct foo *, ...);
};
struct foo {
struct foo_ops *ops;
/* data fields for foo go here */
};
С этими определениями структуры код, реализующий foo, выглядит примерно так:
static void plugh(struct foo *, ...) { ... }
static void blah(struct foo *, ...) { ... }
static struct foo_ops foo_ops = { blah, plugh };
struct foo *new_foo(...) {
struct foo *foop = malloc(sizeof(*foop));
foop->ops = &foo_ops;
/* fill in rest of *foop */
return foop;
}
Затем в коде, который использует foo:
struct foo *foop = new_foo(...);
foop->ops->blah(foop, ...);
foop->ops->plugh(foop, ...);
Этот код можно привести в порядок с помощью макросов или встроенных функций, чтобы он выглядел более похожим на C
foo_blah(foop, ...);
foo_plugh(foop, ...);
хотя, если вы используете достаточно короткое имя для поля "ops", простое написание кода, показанного изначально, не особенно многословно.
Этот метод полностью подходит для реализации относительно простых объектно-ориентированных проектов в C, но он не обрабатывает более сложные требования, такие как явное представление классов и наследование методов. Для них вам может понадобиться что-то вроде GObject (как упоминал EFraim), но я бы посоветовал убедиться, что вам действительно нужны дополнительные функции более сложных сред.