C — процедурный язык, минималистичный и лишенный объектной семантики. Вместо этого у вас есть структуры, функции (процедуры) и указатели. Вы можете использовать их для реализации чего-то похожего на вызовы методов в объектно-ориентированных языках.

В следующем примере мы определяем квазиобъект с помощью структуры C. Он имеет два метода получения и установки, getA и setA, и связанную переменную a. Обратите внимание, что эту переменную нельзя сделать приватной, т. е. к ней нельзя получить доступ исключительно с помощью геттеров и сеттеров.

typedef struct object_t {
 int a;
 int (* getA)();
 void (* setA)();
} Object;

Методы реализованы с помощью указателей функций C. Чтобы использовать их правильно, нам нужно написать следующее:

obj->setA(obj, 42)

Как видите, это довольно многословно, так как нам нужно явно передать экземпляр объекта в его методе. Однако мы создаем некоторый синтаксический сахар, используя препроцессор C, определяя два новых макроса, CALL и CALL_W_ARGS. Первый макрос используется, когда метод не принимает аргументов, а второй реализует вариативный макрос (обратите внимание на многоточие ... и использование __VA_ARGS__) для работы с любым количеством аргументов, которое может принять метод. Это похоже на то, как Smalltalk синтаксически реализует передачу сообщений; различая унарные, двоичные сообщения и сообщения с ключевыми словами.

#define CALL_W_ARGS(x, y, ...) x->y(x, __VA_ARGS__)
#define CALL(x, y) x->y(x)

Теперь мы можем использовать эти макросы, чтобы облегчить громоздкий набор текста:

CALL_W_ARGS(obj, setA, 42)
CALL(obj, getA)

Препроцессор расширяет их как:

obj->setA(obj, 42)
obj->getA(obj)

Если вы используете gcc, вы можете увидеть вывод препроцессора с помощью gcc -E main.c

Теперь нам просто нужно определить две новые функции, newObjectи freeObject, чтобы получить что-то, что очень отдаленно напоминает объектно-ориентированное программирование. Вот полный код:

#include <stdio.h>
#include <stdlib.h>
#define CALL_W_ARGS(x, y, ...) x->y(x, __VA_ARGS__)
#define CALL(x, y) x->y(x)
typedef struct object_t {
 int a;
 int (* getA)();
 void (* setA)();
} Object;
// Getter method for Object
int Object_getA(Object *obj)
{
 return obj->a;
}
// Setter method for Object
void Object_setA(Object *obj, int val)
{
 obj->a = val;
}
// Create and construct an Object
Object *newObject(void)
{
 Object *obj = malloc(sizeof(*obj));
 obj->a = 0;
 obj->getA = &Object_getA;
 obj->setA = &Object_setA;
 return obj;
}
// Destroy and free the allocated Object memory
void freeObject(Object *obj)
{
 free(obj);
}
int main(int argc, char *argv[])
{
 // Create a new "object"
 Object *obj = newObject();
 // Call method "setA" of said object
 CALL_W_ARGS(obj, setA, 42);
 // Print the value returned by calling method "getA"
 printf("%d\n", CALL(obj, getA));
 // Free the allocated heap memory for the "object"
 freeObject(obj);
 return 0;
}