C общего программирования - PullRequest
13 голосов
/ 26 декабря 2009

Прежде всего, я начинающий программист (еще многому научиться). В одном из моих небольших школьных проектов я написал стек для структуры. Теперь у меня немного другая структура, и мне нужен стек для этого тоже. Должен ли я написать другую структуру данных [стек] (очень похожую на исходную), или попытаться достичь какого-то общего программирования ...

Знаете ли вы какие-либо хорошие общие стратегии программирования (онлайн-ресурсы в конечном итоге) на C? Я использовал Google, но я не нашел ничего удовлетворительного, так как большинство результатов о стратегиях C ++.

Спасибо!

ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ: После некоторого чтения и опыта, в конце концов я нашел два решения для моей проблемы. Я задокументировал их здесь: http://andreinc.net/2010/09/30/generic-data-structures-in-c/. Эта статья может содержать ошибки или неточности, но она суммирует то, что я знаю до сих пор.

Ответы [ 7 ]

15 голосов
/ 26 декабря 2009

Поиск общих черт и создание абстракций - один из самых ценных навыков для программиста. Поскольку вы все еще учитесь, я бы посоветовал вам сделать следующее:

(1) Реализуйте стек для этой другой структуры. Да, это двойная работа, но на вашем этапе каждая рабочая программа имеет значение. Накапливает опыт.

(2) Сравните программы. Какие части у них общего? Какие части отличаются? Ваша цель - отделить части, которые являются общими, от частей, которые отличаются. Какие средства используют эти две группы для общения? Части, которые у них общие, попадают в одну часть вашей системы (stack.h / stack.c), а части, которые отличаются, - в свои собственные файлы (account.h / c, person.h / c и т. Д.) , И часть, в которой вы их объединяете, должна включать в себя stack.h и параметризующую сущность.

(3) Попытайтесь найти все возможные способы, которые вы знаете, что язык предлагает, которые вы можете использовать для реализации функциональности абстрактной структуры. Сначала всегда кажется, что есть только один путь, но для каждой нетривиальной проблемы всегда есть несколько подходов. В случае стека, используя стандартный C, например, zou может использовать указатели void, вы можете использовать макросы препроцессора, вы должны изучить вставку токенов, вы можете использовать указатели на функции плюс указатели структуры и т. Д.

(4) Реализуйте как можно больше из них. Опять же, это для обучения. В С так много ловушек, и чем раньше вы столкнетесь с ними, тем лучше.

(5) После того, как вы перечислили и реализовали все эти различные подходы, вы должны оценить их: какой из них был наиболее простым в использовании? Какой из них был проще всего реализовать? Какой из них самый быстрый? Какой из них легче всего отладить?

13 голосов
/ 26 декабря 2009

Я не очень много хакерски C, но я думаю, что путь к этому - void*.

Итак, просто перепишите ваш стек push / pop void* вместо some_struct*. Ваша задача - поддерживать правильность типов, но это просто цена, которую вы платите за использование такого низкоуровневого * языка программирования.

* Не означает, что это плохо.

10 голосов
/ 26 декабря 2009

Я считаю, что абстракция в основном в глазах программиста. Хороший программист может видеть шаблон в простых утверждениях, даже на низкоуровневом языке, таком как C. Языки и их синтаксис, безусловно, могут помочь, но то, как в итоге написаны операторы и выражения, несколько отличает хороших программистов от плохих , Тем не менее, как это поможет вам? Что ж, моя цель - познакомиться с конструкциями в C, чтобы вы знали их, когда видите их, и void*, как упоминает Кевин Монтроуз, является распространенным. Стратегии, которые я считаю хорошими, это думать о stdlib, как там все решается? и отражать в отличном коде, когда вы видите некоторые. Т.е. общий шаблон в stdlib должен иметь ноль (0) для представления ОК. Или подумайте, насколько хорошо файловый дескриптор работает со всеми функциями read, write и т. Д. Независимо от их происхождения (сокет, файл, канал и т. Д.). Этот SO вопрос ( ссылка ) содержит несколько хороших ссылок на отличный код для чтения.

alt text
(источник: skitch.com )

Изображение из Thinking Forth , великая старая книга по программированию независимо от языка.

2 голосов
/ 26 декабря 2009

Я привел здесь пример общего программирования на C. Хотя это не алгоритм структуры данных, а своего рода, он может дать вам некоторые подсказки по этому вопросу. Я надеюсь, что это может помочь вам.

Алгоритм ниже представляет собой общую реализацию алгоритма сортировки пузырьков. Я использую указатели void и использую данные для работы на уровне байтов. Также я использую функцию внешнего сравнения, которая является единственным кодом, который знает тип сортируемых данных. Эта функция сравнения передается в качестве параметра алгоритму сортировки.

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

    static int compare(void *menor, void *maior)
    {
       int *pMenor = (int *)menor;
       int *pMaior = (int *)maior;

       return ( *pMenor > *pMaior );
    }

    void bubleSort(void *base, int bWidth, int len, int (*func)(void *key, void *data))
    {
       int i, j;
       char *k=0;
       char *bPtr = (char *)base;         //Points to the beginning of the array 
       char *pi, *pj;

       k = (char *)malloc(bWidth);        //Creates a new var with the size of the data type

       if(!k)
          return;

       for(i = 0; i < len; i++)
       {
          pi = (bPtr + (i*bWidth));

          for(j=i+1; j < len; j++)
          {
             pj = (bPtr+(j*bWidth));

             if( func((void *)pi, (void *)pj) )
             {
                memcpy(k, pi, bWidth);
                memcpy(pi,pj, bWidth);
                memcpy(pj, k, bWidth);
             }
          }
       }

       free(k);
    }

int main()
{
   int vet[5] = {4, 1, 3, 5, 2};
   bubleSort((void *)vet, sizeof(int), 5, compare);

   return 0;
}
2 голосов
/ 26 декабря 2009

Для производственного кода я обычно предпочитаю C ++. Даже если вы не планируете использовать OO, дженерики и метапрограммирование, вы можете использовать C ++ как лучший C (в этом случае просто получить std :: stack бесплатно).

Если вам нужно использовать C, постарайтесь сделать его простым и сделать прагматичный выбор, исходя из ваших конкретных обстоятельств. Например, если вы точно знаете, что ваш стек ограничен каким-то небольшим пределом, а данные, которые вы храните, просты, то ваш код стека может быть таким же простым, как stack[tos++] = x; и return stack[--tos], без необходимости многоразовой библиотеки. Ответы, предлагающие библиотеку, основанную на void*, также подходят при других обстоятельствах. Std :: stack в C ++ в значительной степени решает эту проблему раз и навсегда; С не совсем дает тебе такую ​​роскошь.

1 голос
/ 26 декабря 2009

Хотя большая часть советов здесь звездная (я призываю вас попробовать много разных методов, чтобы набраться опыта), я рекомендую использовать C ++. Используйте шаблон стека, чтобы создать реализацию, затем используйте extern "C", чтобы создать набор функций C api для его использования.

вам понадобится

  o constructor to create the object
  o a destructor to destroy the object
  o a push function
  o a pop function
  o an "is_empty" function.

Последние 3 функции принимают указатель на объект в качестве первого (void *) параметра. Внутри функции этот указатель будет приведен к типу объекта стека, а затем использован в нормальная мода на с ++.

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

1 голос
/ 26 декабря 2009

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

Лучшее, что вы можете сделать, - написать свои структуры данных, чтобы всегда использовать void*, и оставлять каждое выделение / освобождение / приведение к вызывающей стороне. Хотя это грязно и подвержено ошибкам.

...