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; }