Есть ли в C что-то вроде шаблонов C ++? Если нет, то как повторно использовать структуры и функции для разных типов данных? - PullRequest
6 голосов
/ 14 сентября 2011

Я хочу написать связанный список, в котором в поле данных могут храниться любые встроенные или определяемые пользователем типы.В C ++ я бы просто использовал шаблон, но как мне сделать это в C?

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

Ответы [ 8 ]

2 голосов
/ 14 сентября 2011

Есть причина, по которой люди используют языки, отличные от C ....: -)

В C ваша структура данных будет работать с void* членами, и вы будете приводить, где бы вы ни использовалиих правильных типов.Макросы могут помочь с этим шумом.

1 голос
/ 14 сентября 2011

Вот опция, которая очень гибкая, но требует большой работы.

В своем узле списка сохраните указатель на данные как void *:

struct node {
  void *data;
  struct node *next;
};

Затем вы создадите набор функций для каждого типа, которые будут обрабатывать такие задачи, как сравнение, назначение, дублирование и т. Д.:

// create a new instance of the data item and copy the value
// of the parameter to it.
void *copyInt(void *src)
{
  int *p = malloc(sizeof *p);
  if (p) *p = *(int *)src;
  return p;
}

void assignInt(void *target, void *src)
{
  // we create a new instance for the assignment
  *(int *)target = copyInt(src);
}

// returns -1 if lhs < rhs, 0 if lhs == rhs, 1 if lhs > rhs
int testInt(void *lhs, void *rhs)
{
  if (*(int *)lhs < *(int *)rhs) return -1;
  else if (*(int *)lhs == *(int *)rhs) return 0;
  else return 1;
}

char *intToString(void *data)
{
  size_t digits = however_many_digits_in_an_int();
  char *s = malloc(digits + 2); // sign + digits + terminator
  sprintf(s, "%d", *(int *)data);
  return s;
}

Затем вы можете создать тип списка с указателями на этифункции, такие как

struct list {
  struct node *head;
  void *(*cpy)(void *);          // copy operation
  int   (*test)(void *, void *); // test operation
  void  (*asgn)(void *, void *); // assign operation
  char *(*toStr)(void *);        // get string representation
  ...
}

struct list myIntList;
struct list myDoubleList;

myIntList.cpy = copyInt;
myIntList.test = testInt;
myIntList.asgn = assignInt;
myIntList.toStr = intToString;

myDoubleList.cpy = copyDouble;
myDoubleList.test = testDouble;
myDoubleList.asgn = assignDouble;
myDoubleList.toStr = doubleToString;
...

Затем, когда вы передаете список операции вставки или поиска, вы вызываете функции из объекта списка:

void addToList(struct list *l, void *value)
{
  struct node *new, *cur = l->head;
  while (cur->next != NULL && l->test(cur->data, value) <= 0)
    cur = cur->next;
  new = malloc(sizeof *new);
  if (!new)
  {
    // handle error here
  }
  else
  {
    new->data = l->cpy(value);
    new->next = cur->next;
    cur->next = new;
    if (logging)
    {
      char *s = l->toStr(new->data);
      fprintf(log, "Added value %s to list\n", s);
      free(s);
    }
  }
}

...
i = 1;
addToList(&myIntList, &i);
f = 3.4;
addToList(&myDoubleList, &f);

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

Есть недостатки.Во-первых, вы не можете использовать константы в качестве параметров функции (например, вы не можете сделать что-то простое, например, addToList(&myIntList, 1);) - вам нужно все присвоить переменной first и передать адреспеременной (именно поэтому вам нужно создавать новые экземпляры элемента данных, когда вы добавляете его в список; если вы просто назначаете адрес переменной, каждый элемент в списке будет указывать на один и тот же объект, которыйможет больше не существовать в зависимости от контекста).

Во-вторых, вы выполняете lot управления памятью;Вы не просто создаете новый экземпляр узла списка, но вы также должны создать новый экземпляр члена данных.Вы должны помнить, чтобы освободить элемент данных перед освобождением узла.Затем вы создаете новый экземпляр строки каждый раз, когда хотите отобразить данные, и вы должны помнить, чтобы освободить эту строку, когда закончите с ней.

Наконец, это решение выбрасывает безопасность типов прямо в окно и во встречное движение (после того, как оно загорелось).Функции делегатов рассчитывают на you , чтобы типы были прямыми;ничто не мешает вам передать адрес переменной double одной из int функций обработки.

Между управлением памятью и тем фактом, что вы должны выполнять вызов функции практически для каждой операции,производительность будет страдать.Это не быстрое решение.

Конечно, это предполагает, что каждый элемент в списке имеет одинаковый тип;если вы хотите хранить элементы разных типов в том же списке, то вам нужно будет сделать что-то другое, например связать функции с каждым узлом, ачем список в целом.

1 голос
/ 14 сентября 2011

С некоторой осторожностью вы можете сделать это, используя макросы, которые создают и управляют структурами. Одним из наиболее хорошо проверенных примеров этого является библиотека очереди BSD. Он работает на каждой платформе, которую я пробовал (Unix, Windows, VMS) и состоит из одного заголовочного файла (без файла C).

У него, к сожалению, несколько сложный в использовании недостаток, но он сохраняет столько же безопасности типов, сколько и в C.

Файл заголовка находится здесь: http://www.openbsd.org/cgi-bin/cvsweb/src/sys/sys/queue.h?rev=1.34;content-type=text%2Fplain,, а документация по его использованию здесь: http://www.openbsd.org/cgi-bin/man.cgi?query=queue.

Кроме того, нет, вы застряли с потерей безопасности типов (используя (void *) повсюду) или переходите на STL.

1 голос
/ 14 сентября 2011

Существуют разные подходы к этой проблеме:

  • с использованием типа данных void*: это означает, что у вас есть указатели на ячейки памяти, тип которых больше не указывается. Если вы получите такой указатель, вы можете явно указать, что внутри него: *(int*)(mystruct->voidptr) сообщает компилятору: посмотрите на область памяти mystruct->voidptr и интерпретируйте содержимое как int.

  • другая вещь может быть хитрыми директивами препроцессора. Однако обычно это очень нетривиальная проблема:

  • Я также нашел http://sglib.sourceforge.net/

Редактировать: Для уловки препроцессора:

 #include <stdio.h>

 #define mytype(t) struct { t val; }

 int main(int argc, char *argv[]) {
   mytype(int) myint;
   myint.val=6;
   printf ("%d\n", myint.val);
   return 0;
 }

Это будет простая оболочка для типов, но я думаю, что это может стать довольно сложным.

1 голос
/ 14 сентября 2011

Это менее удобно в C (есть причина, по которой C ++ называется C инкрементным), но это можно сделать с помощью общих указателей (void *), и приложение само обрабатывает управление типами.

Очень хорошая реализацияобщих структур данных в C можно найти в модулях ubiqx , источники, безусловно, стоит прочитать.

0 голосов
/ 20 октября 2014

Если вам нужен список, который может содержать элементы разных типов одновременно, например, int, за которым следуют три char *, за которыми следует struct tm, то использование void * для данных является решением.Но если вам нужно только несколько типов списков с одинаковыми методами, лучшее решение зависит от того, хотите ли вы избежать создания множества экземпляров почти идентичного машинного кода или просто не набирать исходный код.

Объявление структуры негенерировать любой машинный код ...

struct int_node {
    void *next;
    int data;
};

struct long_node {
    void *next;
    long data;
};

... и одна функция, которая использует параметр void * и / или возвращаемое значение, может обрабатывать их все.

struct generic_node {
    void *next;
};

void *insert(void *before_this, void *element, size_t element_sizes);
0 голосов
/ 14 сентября 2011

Я согласен с ответом JonathanPatschke , что вы должны посмотреть на sys / queue.h , хотя я никогда не пробовал сам, так как не на некоторых платформах, с которыми я работаю. Я также согласен с ответом Вики на использование Python .

Но я обнаружил, что пять или шесть очень простых макросов C удовлетворяют большинству моих потребностей в садовом разнообразии. Эти макросы помогают убрать уродливый, подверженный ошибкам код, без , засоряющий его скрытыми void *, которые разрушают безопасность типов. Вот некоторые из этих макросов:

#define ADD_LINK_TO_END_OF_LIST(add, head, tail)                    \
   if (!(head))                                                     \
      (tail) = (head) = (add);                                      \
   else                                                             \
      (tail) = (tail)->next = (add)

#define ADD_DOUBLE_LINK_TO_END_OF_LIST(add, head, tail)             \
   if (!(head))                                                     \
      (tail) = (head) = (add);                                      \
   else                                                             \
      (tail) = ((add)->prev = (tail), (tail)->next = (add))

#define FREE_LINK_IN_LIST(p, dtor) do {  /* singly-linked */        \
   void *myLocalTemporaryPtr = (p)->next;                           \
   dtor(p);                                                         \
   (p) = myLocalTemporaryPtr;} while (0)

#define FREE_LINKED_LIST(p, dtor) do {                              \
   while (p)                                                        \
      FREE_LINK_IN_LIST(p, dtor);} while (0)

// copy "ctor" (shallow)
#define NEW_COPY(p) memcpy(myMalloc(sizeof *(p)), p, sizeof *(p))

// iterator
#define NEXT_IN_LIST(p, list)  ((p) ? (p)->next : (list))

Так, например:

struct MyContact {
   char *name;
   char *address;
   char *telephone;
   ...
   struct MyContact *next; 
} *myContactList = 0, *myContactTail; // the tail doesn't need to be init'd
...
struct MyContact newEntry = {};
...
ADD_LINK_TO_END_OF_LIST(NEW_COPY(newEntry), myContactList, myContactTail);
...
struct MyContact *i = 0;
while ((i = NEXT_IN_LIST(i, myContactList))) // iterate through list
   // ...

Члены next и prev имеют жестко запрограммированные имена. Они не должны быть void *, что позволяет избежать проблем со строгим сглаживанием. Они do должны быть обнулены при создании элемента данных.

Аргумент dtor для FREE_LINK_IN_LIST обычно представляет собой функцию, подобную free, или (void), которая ничего не делает, или другой макрос , такой как:

#define MY_CONTACT_ENTRY_DTOR(p)                                    \
     do { if (p) {                                                  \
        free((p)->name);                                            \
        free((p)->address);                                         \
        free((p)->telephone);                                       \
        free(p);                                                    \
     }} while (0)

Так, например, FREE_LINKED_LIST(myContactList, MY_CONTACT_ENTRY_DTOR) освободит всех членов списка (с типизацией утки), возглавляемого myContactList.

Здесь есть one void *, но, возможно, его можно удалить с помощью gcc's typeof.

0 голосов
/ 14 сентября 2011

Я написал общий шаблон связанного списка в C с использованием препроцессора, но смотреть на него довольно ужасно, а сильно предварительно обработанный код не легко отладить.

В наши дни я думаю, что вам лучше использовать какой-нибудь другой инструмент для генерации кода, например Python / Cog: http://www.python.org/about/success/cog/

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