Несколько структур, те же поля, которые должны быть доступны в методе - PullRequest
0 голосов
/ 11 октября 2018

В настоящее время я пытаюсь написать какую-нибудь лиловую консольную игру для удовольствия на C.

Для этого мне нужно иметь возможность печатать оконные структуры на ... ну ... C.

Я хочу использовать универсальный метод рендеринга (давайте назовем его frame_render(...)) для рендеринга всех различных "элементов пользовательского интерфейса"

Теперь возникает проблема: как решить эту проблему?

учитывая сценарий:

// Theoretical base
struct frame { int x; int y; int width; int height; }
struct button { int x; int y; int width; int height; ... }
struct whatever { int x; int y; int width; int height; ... }

как я могу убедиться, что мои x, y, width и height всегда находятся в правильном месте памяти?Достаточно ли "просто" поместить их в одном и том же порядке в самом начале?

Кроме того, как спроектировать заголовок метода для его принятия?

Ответы [ 3 ]

0 голосов
/ 11 октября 2018

Достаточно ли просто "просто" расположить их в одном и том же порядке в самом начале?

Да, если вы будете осторожны, как делали выше.

также, как спроектировать заголовок метода для его принятия?

Существуют различные способы сделать это.

Вот пример, использующий [уродливый]эквивалент c++ «базового» класса:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
};

struct frame {
    struct geo geo;
};

struct button {
    struct geo geo;
    int updown;
};

struct whatever {
    struct geo geo;
    int what;
    int ever;
};

void
frame_render(struct geo *geo)
{
    struct frame *frm;
    struct button *but;
    struct whatever *what;

    switch (geo->type) {
    case FRAME:
        frm = (struct frame *) geo;
        frame_render_frame(frm);
        break;

    case BUTTON:
        but = (struct button *) geo;
        printf("x=%d y=%d updown=%d\n",geo->x,geo->y,but->updown);
        frame_render_button(but);
        break;

    case WHATEVER:
        what = (struct whatever *) geo;
        printf("x=%d y=%d what=%d ever=%d\n",
            what->geo.x,what->geo.y,what->what,what->ever);
        frame_render_whatever(what);
        break;
    }
}

Вот способ использования таблицы виртуальных функций:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct geo;

// virtual function table
struct virtfnc {
    void (*calc)(struct geo *);
    void (*render)(struct geo *);
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
    struct virtfnc *fnc;
};

struct frame {
    struct geo geo;
};

struct button {
    struct geo geo;
    int updown;
};

struct whatever {
    struct geo geo;
    int what;
    int ever;
};

void
frame_render(struct geo *geo)
{
    struct frame *frm = (struct frame *) geo;

    // whatever
}

void
button_render(struct geo *geo)
{
    struct button *but = (struct button *) geo;

    // whatever
}

void
whatever_render(struct geo *geo)
{
    struct whatever *what = (struct whatever *) geo;

    // whatever
}

void
any_render(struct geo *geo)
{

    geo->fnc->render(geo);
}

Вот третий способ, который использует union.Это проще, но требует, чтобы базовая структура была больше самого большого подкласса:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct frame {
    ...
};

struct button {
    int updown;
};

struct whatever {
    int what;
    int ever;
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
    union {
        struct frame frame;
        struct button button;
        struct whatever what;
    } data;
};

void
any_render(struct geo *geo)
{

    switch (geo->type) {
    case FRAME:
        render_frame(&geo->data.frame);
        break;

    case BUTTON:
        render_button(&geo->data.button);
        break;

    case WHATEVER:
        render_whatever(&geo->data.what);
        break;
    }
}

ОБНОВЛЕНИЕ:

этоподходить литье безопасно?например.положить все в некоторый массив типа frame*, а затем просто получить доступ к frame->geo?или это может вызвать проблемы с последующими вызовами free(..)?

Нет проблем с free, если выделения выполняются с производными типами (например, frame, button), но не базовый тип geo: malloc(sizeof(struct button)).

Чтобы иметь простой массив [фигур], метод unionпотребуется использовать (т.е. все производные структуры должны иметь одинаковый размер).Но это было бы расточительно, если бы у нас был какой-то подтип, который использовал бы лот больше места, чем другие:

struct polyline {
    int num_points;
    int x[100];
    int y[100];
};

Этот мог бы все еще быть выполнен с методами #1 или # 2 [где структуры подтипа имеют разные размеры] с массивом указателей косвенного :

void
all_render(struct geo **glist,int ngeo)
{

    for (;  ngeo > 0;  --ngeo, ++glist)
        any_render(*glist);
}

Вместо массива различных форм, я бы рассмотрел [вдвойне] связанный список.Это позволяет структурам подтипа иметь разные размеры.Мы добавили бы struct geo *next элемент к struct geo.Затем мы могли бы сделать:

void
all_render(struct geo *geo)
{

    for (;  geo != NULL;  geo = geo->next)
        any_render(geo);
}

Подход со списком может быть предпочтительным, особенно если мы добавляем / удаляем фигуры на динамической основе [или переупорядочиваем их на основе глубины Z].

Или, некоторые формы могут содержать других.Таким образом, мы можем добавить struct geo *children к struct geo.Затем легко (например) нарисовать содержащую рамку, а затем все фигуры в этой рамке, пройдя список children.Если мы выберем children, мы также можем добавить struct parent *parent, чтобы каждая фигура знала, в какой форме она содержится.

0 голосов
/ 11 октября 2018

Если все структуры начинаются с элементов одного типа, в том же порядке, соответствующие элементы будут иметь одинаковые смещения.Большинство компиляторов можно настроить так, чтобы указатель любого типа структуры можно было использовать для проверки членов общей начальной последовательности любого другого, но есть несколько проблем:

  1. В некоторых необычныхНа платформах, если за объектом следуют байты заполнения, инструкция, которая записывает объект вместе и байты заполнения (вероятно, хранящие бессмысленные значения в последнем), может быть быстрее, чем инструкции, которые пишут только объект.Если за элементом следует байт заполнения в некоторых структурах, но в другой структуре значимые данные, запись в этот член с использованием типа, за которым следуют байты заполнения, может записывать поверх «байтов заполнения» бессмысленные значения, что приводит к повреждению значений.из следующих членов в других типах структуры.Мне неизвестны какие-либо архитектуры в текущем использовании, где это могло бы быть проблемой для любых элементов структуры, кроме битовых полей, и мне не известны какие-либо текущие реализации, где это могло бы быть проблемой даже для них, но такие возможности могут возникнуть на некоторых платформах,особенно с битовыми полями.

  2. Учитывая что-то вроде:

    int readField1OfS1(struct s1 *p) { return p->field1; }
    
    struct s2 *myStruct2Ptr;
    
    if (readField1ofS1((struct s1*)myStruct2Ptr)
       ...
    

    некоторые компиляторы, такие как gcc и clang, не будут надежно учитывать возможность того, что значение, возвращаемое функциейможет зависеть от значения, которое хранится частью Общей начальной последовательности объекта типа struct s2 во время вызова, если оптимизация не отключена (например, с использованием опции -fno-strict-aliasing).Я бы подумал, что присутствие приведения от struct s2* до struct s1* в выражении вызова функции должно позволить качественному компилятору распознать, что любая функция может что-то делать с объектом типа struct s1, что может быть сделано на struct s2, но поскольку Стандарт явно не требует этого, авторы gcc и clang отказываются прилагать какие-либо усилия для надежного распознавания таких конструкций даже в простых случаях, подобных приведенному выше.

Код, которыйИспользование правила Common Initial Sequence будет надежно работать практически на любом подходяще сконфигурированном компиляторе, но некоторые, такие как gcc и clang, должны быть специально сконфигурированы с использованием -fno-strict-aliasing option .Способность использовать гарантии Common Initial Sequence была укоренившейся частью языка с 1974 года, и когда был написан стандарт, любой, кто знаком с этим языком, понял бы, что он предназначен для использования в конструкциях, подобных приведенным выше, которые компиляторыне должно быть проблем с распознаванием.Поскольку авторы Стандарта не смогли явно потребовать, чтобы гарантии СНГ выполнялись полезным образом, однако, авторы clang и gcc решили, что они скорее будут утверждать, что программы, основанные на давних гарантиях СНГ, "нарушены", чемчесть 40+ лет прецедента.

0 голосов
/ 11 октября 2018

«Классический» метод должен иметь struct, который содержит union всех возможных объектов и enum, который определяет, какой именно объект был пройден:

struct renderable {
    enum {FRAME, BUTTON, WHATVERE, ...} type;
    union {
        struct frame frame;
        struct button button;
        struct whatever whatever;
        ....
    } data;
};

После прохожденияэту структуру для средства визуализации, используйте switch в поле type для извлечения координат и т. д .:

void renderer (renderable *obj, ...) {
    ...
    switch(obj->type) {
        case FRAME: x = obj->data.frame.x; ...; break;
        case BUTTON: x = obj->data.button.x; ...; break;
        ...
    }
    ...
}

Как сообщается, именно этот вид чудовищности побудил Страуструпа изобрести C ++:)

Отредактировано

Другое «классическое» решение состоит в том, чтобы иметь отдельный struct, который имеет размеры и положение любого объекта:

struct geometry {
    int x, y, width, height;
}

Вы можете сохранить эту структуру в начале любого объекта struct и использовать приведение, чтобы получить к ней доступ:

struct frame {
    struct geometry geo;
    // more stuff
};

struct frame frame = {....};
rendered((void*)&frame, ...);

В рендере:

void renderer (void *obj, ...) {
    ...
    struct geometry *geo = (struct geometry *)obj;
    geo->x ...
}

Последнийподход может быть несколько небезопасным.Чтобы сделать его на 100% безопасным, отделите геометрию от информации, специфичной для объекта, и передайте ее средству визуализации в виде двух отдельных структур.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...