Подтвердить что ты не робот

Динамический метод отправки в C

Я знаю, что это звучит глупо, и я знаю, что C не является объектно-ориентированным языком.

Но есть ли способ, которым динамическая отправка метода может быть достигнута в C? Я думал о указателях функций, но не получил всю идею.

Как я мог реализовать это?

4b9b3361

Ответ 1

Как отмечали другие, это, безусловно, возможно реализовать в C. Не только это возможно, но и довольно распространенный механизм. Наиболее часто используемым примером является, вероятно, интерфейс дескриптора файла в UNIX. A read() вызов файлового дескриптора отправит функцию чтения, специфичную для устройства или службы, которая предоставила этот файловый дескриптор (был ли он файлом? Был он сокет? Был ли он каким-то другим устройством?).

Единственный трюк - восстановить указатель на конкретный тип из абстрактного типа. Для файловых дескрипторов UNIX использует таблицу поиска, содержащую информацию, относящуюся к этому дескриптору. Если вы используете указатель на объект, указатель, удерживаемый пользователем интерфейса, является "базовым" типом, а не "производным" типом. C не имеет наследования как такового, но он гарантирует, что указатель на первый элемент a struct равен указателю содержащего struct. Таким образом, вы можете использовать это, чтобы восстановить "производный" тип, сделав экземпляр "базы" первым элементом "производного".

Вот простой пример со стеком:

struct Stack {
    const struct StackInterface * const vtable;
};

struct StackInterface {
    int (*top)(struct Stack *);
    void (*pop)(struct Stack *);
    void (*push)(struct Stack *, int);
    int (*empty)(struct Stack *);
    int (*full)(struct Stack *);
    void (*destroy)(struct Stack *);
};

inline int stack_top (struct Stack *s) { return s->vtable->top(s); }
inline void stack_pop (struct Stack *s) { s->vtable->pop(s); }
inline void stack_push (struct Stack *s, int x) { s->vtable->push(s, x); }
inline int stack_empty (struct Stack *s) { return s->vtable->empty(s); }
inline int stack_full (struct Stack *s) { return s->vtable->full(s); }
inline void stack_destroy (struct Stack *s) { s->vtable->destroy(s); }

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

struct StackArray {
    struct Stack base;
    int idx;
    int array[STACK_ARRAY_MAX];
};
static int stack_array_top (struct Stack *s) { /* ... */ }
static void stack_array_pop (struct Stack *s) { /* ... */ }
static void stack_array_push (struct Stack *s, int x) { /* ... */ }
static int stack_array_empty (struct Stack *s) { /* ... */ }
static int stack_array_full (struct Stack *s) { /* ... */ }
static void stack_array_destroy (struct Stack *s) { /* ... */ }
struct Stack * stack_array_create () {
    static const struct StackInterface vtable = {
        stack_array_top, stack_array_pop, stack_array_push,
        stack_array_empty, stack_array_full, stack_array_destroy
    };
    static struct Stack base = { &vtable };
    struct StackArray *sa = malloc(sizeof(*sa));
    memcpy(&sa->base, &base, sizeof(base));
    sa->idx = 0;
    return &sa->base;
}

И если бы я хотел реализовать стек, используя список:

struct StackList {
    struct Stack base;
    struct StackNode *head;
};
struct StackNode {
    struct StackNode *next;
    int data;
};
static int stack_list_top (struct Stack *s) { /* ... */ }
static void stack_list_pop (struct Stack *s) { /* ... */ }
static void stack_list_push (struct Stack *s, int x) { /* ... */ }
static int stack_list_empty (struct Stack *s) { /* ... */ }
static int stack_list_full (struct Stack *s) { /* ... */ }
static void stack_list_destroy (struct Stack *s) { /* ... */ }
struct Stack * stack_list_create () {
    static const struct StackInterface vtable = {
        stack_list_top, stack_list_pop, stack_list_push,
        stack_list_empty, stack_list_full, stack_list_destroy
    };
    static struct Stack base = { &vtable };
    struct StackList *sl = malloc(sizeof(*sl));
    memcpy(&sl->base, &base, sizeof(base));
    sl->head = 0;
    return &sl->base;
}

Реализации операций стека будут просто отличать struct Stack * от того, что, как он знает, должно быть. Например:

static int stack_array_empty (struct Stack *s) {
    struct StackArray *sa = (void *)s;
    return sa->idx == 0;
}

static int stack_list_empty (struct Stack *s) {
    struct StackList *sl = (void *)s;
    return sl->head == 0;
}

Когда пользователь стека вызывает операцию стека в экземпляре стека, операция отправляет соответствующую операцию в vtable. Этот vtable инициализируется функцией создания с функциями, которые соответствуют его конкретной реализации. Итак:

Stack *s1 = stack_array_create();
Stack *s2 = stack_list_create();

stack_push(s1, 1);
stack_push(s2, 1);

stack_push() вызывается как для s1, так и для s2. Но для s1 он отправит stack_array_push(), а для s2 он отправит на stack_list_push().

Ответ 2

Да. Его можно легко достичь. Вы должны использовать массив указателей на функции, а затем использовать эти указатели для выполнения вызова. Если вы хотите "переопределить" функцию, вы просто установите соответствующий слот для указания новой функции. Это как раз то, как С++ реализует виртуальные функции.

Ответ 3

С++ был (первоначально) построен поверх C. Первый компилятор С++ фактически генерировал C в качестве промежуточного шага. Эрго, да, возможно.

Вот как это делает С++.

В Интернете доступно много надежной информации, больше, чем мы можем ввести здесь через несколько минут. "Google и вы найдете".

Вы сказали в комментарии выше:

Хорошо, кто-то предпочел бы, чтобы у них есть код, написанный уже в c, но для добавления некоторых функций. вместо написания с нуля с помощью OOlanguage.

Чтобы иметь такую ​​функциональность, как на C, вам в основном нужно переопределить возможности языка OO. Привлечение людей к использованию этого нового метода OO является самым большим фактором против удобства использования. Другими словами, создав еще один метод повторного использования, вы фактически делаете вещи менее многоразовыми.

Ответ 4

Я немного удивлен, что никто не добавил glib и/или весь материал gtk в качестве примера. Поэтому, пожалуйста, проверьте: http://www.gtk.org/features.php

Я знаю, что это довольно какой-то шаблонный код, который вы должны использовать для gtk, и это не то, что "легко" можно получить в первый раз. Но если использовать его, то это замечательно. Единственное, что вы должны помнить, - использовать в качестве первого параметра ваши функции "объект". Но если вы просмотрите API, вы увидите, что он используется повсюду. ИМХО, который действительно служит хорошим примером по достоинствам и проблемам, которые могут иметь с ООП -