Вопрос по c, templates – Моделирование шаблонов в C (для типа данных очереди)

52

Я & APOS; я пытаюсь реализоватьqueue структура с использованием C. Моя реализация очень проста; очередь может содержать толькоintи ничего больше. Мне было интересно, смогу ли я симулироватьC++ шаблоны вC(возможно, с помощью препроцессора#define) так что мойqueue может содержать любой тип данных.

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

Вы думали об использовании указателя на void (void *)? patseb
Почему бы просто не сохранить указатели на ваши данные? Вы ищете проверку во время компиляции? Denys Séguret
использованиеvoid* это правильный путь. Вы проситеsimulate templatesтак зачем делать очередьint когда у тебя могла быть очередь чего угодно? gliderkite
По крайней мере, вы можете использоватьtypedef вместо#define, В обоих случаях вы застряли в единственном экземпляре вашегоtemplate Luca Martini
@gliderkite, Зачем делать очередь из всего, если у вас может быть строгая очередь из чистогоints, чтобы соответствовать вашему варианту использования, и компилятор знает это и может помочь вам проверить ошибки? Pacerier

Ваш Ответ

9   ответов
1

но теперь у меня есть определенный ответ, который каждый может понять; так вот!

Когда я проходил курс Data Structures, мне приходилось читать книгу Standish по структурам данных, алгоритмам на C; это было больно; у него не было дженериков, он был полон плохих обозначений и целой кучи глобальных мутаций состояния, где он не имел права быть там; Я знал, что принятие его стиля кода означало использование всех моих будущих проектов, но я знал, что есть лучший способ, так что вот, лучший путь:

Вот как это выглядело до того, как я прикоснулся к нему (на самом деле я все равно прикоснулся к нему, чтобы отформатировать его так, чтобы люди могли его читать, добро пожаловать); это действительно ужасно и неправильно на многих уровнях, но я перечислю это для справки:

#include <stdio.h>

#define MaxIndex 100

int Find(int A[])
{
    int j;

    for (j = 0; j < MaxIndex; ++j) {
        if (A[j] < 0) {
            return j;
        }
    }

    return -1;
}

int main(void)
{
    // reminder: MaxIndex is 100.
    int A[MaxIndex];

    /**
     * anonymous scope #1
     *     initialize our array to [0..99],
     *     then set 18th element to its negative value(-18)
     *     to make the search more interesting.
     */
    {
        // loop index, nothing interesting here.
        int i;

        // initialize our array to [0..99].
        for (i = 0; i < MaxIndex; ++i) {
            A[i] = i * i;
        }

        A[17]= -A[17];
    }

    /**
     * anonymous scope #2
     *     find the index of the smallest number and print it.
     */
    {
        int result = Find(A);

        printf(
            "First negative integer in A found at index = %d.\n",
            result
        );
    }

    // wait for user input before closing.
    getchar();

    return 0;
}

Эта программа делает несколько вещей в ужасно плохом стиле; В частности, он устанавливает глобальный макрос, который используется только в пределах одной области, но затем продолжает загрязнять любой код; очень плохо, и приводит к масштабному загрязнению Windows API в глобальном масштабе.

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

Есть два способа решить эту проблему, но при этом сохранить код простым; первый способ - создать глобальную структуру, которая определяет массив как массив из 100 целых чисел; таким образом, прохождение структуры сохранит длину массива в нем. Второй способ - передать длину массива в качестве аргумента find и использовать только #define строку перед созданием массива и #undef сразу после этого, так как область видимости по-прежнему будет знать размер массива через sizeof (A) / sizeof (A [0]), который имеет 0 накладных расходов времени выполнения, компилятор выведет 100 и вставит его.

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

SimpleArray.h

/**
 * Make sure that all the options needed are given in order to create our array.
 */
#ifdef OPTION_UNINSTALL
    #undef OPTION_ARRAY_TYPE
    #undef OPTION_ARRAY_LENGTH
    #undef OPTION_ARRAY_NAME    
#else 
    #if (!defined OPTION_ARRAY_TYPE) || !defined OPTION_ARRAY_LENGTH || (!defined OPTION_ARRAY_NAME)
        #error "type, length, and name must be known to create an Array."
    #endif

    /** 
     * Use the options to create a structure preserving structure for our array.
     *    that is, in contrast to pointers, raw arrays.
     */
    struct {
        OPTION_ARRAY_TYPE data[OPTION_ARRAY_LENGTH];
    } OPTION_ARRAY_NAME;

    /**
     * if we are asked to also zero out the memory, we do it.
     * if we are not granted access to string.h, brute force it.
     */
    #ifdef OPTION_ZERO_MEMORY
        #ifdef OPTION_GRANT_STRING
            memset(&OPTION_ARRAY_NAME, 0, OPTION_ARRAY_LENGTH * sizeof(OPTION_ARRAY_TYPE));
        #else
            /* anonymous scope */
            {
                int i;
                for (i = 0; i < OPTION_ARRAY_LENGTH; ++i) {
                    OPTION_ARRAY_NAME.data[i] = 0;
                }
            }
        #endif
        #undef OPTION_ZERO_MEMORY
    #endif
#endif

По сути, этот заголовок - это то, как должен выглядеть каждый заголовок структуры данных C, если вы вынуждены использовать препроцессор C (в отличие от PHP / Templating toolkit / ASP / вашего собственного встраиваемого языка сценариев, будь то lisp).

Давайте возьмем это для вращения:

#include <stdio.h>

int Find(int A[], int A_length)
{
    int j;

    for (j = 0; j < A_length; ++j) {
        if (A[j] < 0) {
            return j;
        }
    }

    return -1;
}

int main(void)
{
    // std::array<int, 100> A;
    #define OPTION_ARRAY_TYPE int
    #define OPTION_ARRAY_LENGTH 100
    #define OPTION_ARRAY_NAME A
    #include "SimpleArray.h"    

    /**
     * anonymous scope #1
     *     initialize our array to [0..99],
     *     then set 18th element to its negative value(-18)
     *     to make the search more interesting.
     */
    {
        // loop index, nothing interesting here.
        int i;

        // initialize our array to [0..99].
        for (i = 0; i < (sizeof(A.data) / sizeof(A.data[0])); ++i) {
            A.data[i] = i * i;
        }

        A.data[17]= -A.data[17];
    }

    /**
     * anonymous scope #2
     *     find the index of the smallest number and print it.
     */
    {
        int result = Find(A.data, (sizeof(A.data) / sizeof(A.data[0])));

        printf(
            "First negative integer in A found at index = %d.\n",
            result
        );
    }

    // wait for user input before closing.
    getchar();

    // making sure all macros of SimpleArray do not affect any code
    // after this function; macros are file-wide, so we want to be 
    // respectful to our other functions.
    #define OPTION_UNINSTALL
    #include "SimpleArray.h"

    return 0;
}

ВНИМАНИЕ, мы изобрели наивный std :: array в чистом препроцессоре C и C! Мы использовали макросы, но мы не злые, потому что мы убираем за собой! Все наши макросы не определены в конце нашей области видимости.

Существует проблема; мы больше не знаем размер массива, если мы не делаем(sizeof(A.data) / sizeof(A.data[0])), Это не требует дополнительных затрат для компилятора, но не подходит для детей; и не макросы, но мы работаем внутри коробки; позже мы можем использовать более дружественный препроцессор, такой как PHP, чтобы сделать его дружественным для детей.

Чтобы решить эту проблему, мы можем создать служебную библиотеку, которая будет действовать как метод на нашем «бесплатном» компьютере. структура данных массива.

SimpleArrayUtils.h

/**
 * this is a smart collection that is created using options and is 
 *      removed from scope when included with uninstall option.
 *
 * there are no guards because this header is meant to be strategically
 *     installed and uninstalled, rather than kept at all times.
 */
#ifdef OPTION_UNINSTALL
    /* clean up */
    #undef ARRAY_FOREACH_BEGIN
    #undef ARRAY_FOREACH_END
    #undef ARRAY_LENGTH
#else
    /** 
     * array elements vary in number of bytes, encapsulate common use case 
     */
    #define ARRAY_LENGTH(A) \
        ((sizeof A.data) / (sizeof A.data[0]))

    /**
     * first half of a foreach loop, create an anonymous scope,
     * declare an iterator, and start accessing the items. 
     */
    #if defined OPTION_ARRAY_TYPE
        #define ARRAY_FOREACH_BEGIN(name, iter, arr)\
            {\
                unsigned int iter;\
                for (iter = 0; iter < ARRAY_LENGTH(arr); ++iter) {\
                    OPTION_ARRAY_TYPE name = arr.data[iter];
    #endif

    /** 
     * second half of a foreach loop, close the loop and the anonymous scope 
     */
    #define ARRAY_FOREACH_END \
            }\
        }
#endif

Это довольно многофункциональная библиотека, которая в основном экспортирует

ARRAY_LENGTH :: Все, что с полем данных - & gt; ИНТ

и если мы все еще определили OPTION_ARRAY_SIZE или переопределили его, заголовок также определяет, как выполнить цикл foreach; что мило

Теперь давайте сойдем с ума:

SimpleArray.h

/**
 * Make sure that all the options needed are given in order to create our array.
 */
#ifdef OPTION_UNINSTALL
    #ifndef OPTION_ARRAY_TYPE
        #undef OPTION_ARRAY_TYPE
    #endif

    #ifndef OPTION_ARRAY_TYPE    
        #undef OPTION_ARRAY_LENGTH
    #endif

    #ifndef OPTION_ARRAY_NAME    
        #undef OPTION_ARRAY_NAME    
    #endif

    #ifndef OPTION_UNINSTALL
        #undef OPTION_UNINSTALL
    #endif
#else 
    #if (!defined OPTION_ARRAY_TYPE) || !defined OPTION_ARRAY_LENGTH || (!defined OPTION_ARRAY_NAME)
        #error "type, length, and name must be known to create an Array."
    #endif

    /** 
     * Use the options to create a structure preserving structure for our array.
     *    that is, in contrast to pointers, raw arrays.
     */
    struct {
        OPTION_ARRAY_TYPE data[OPTION_ARRAY_LENGTH];
    } OPTION_ARRAY_NAME;

    /**
     * if we are asked to also zero out the memory, we do it.
     * if we are not granted access to string.h, brute force it.
     */
    #ifdef OPTION_ZERO_MEMORY
        #ifdef OPTION_GRANT_STRING
            memset(&OPTION_ARRAY_NAME, 0, OPTION_ARRAY_LENGTH * sizeof(OPTION_ARRAY_TYPE));
        #else
            /* anonymous scope */
            {
                int i;
                for (i = 0; i < OPTION_ARRAY_LENGTH; ++i) {
                    OPTION_ARRAY_NAME.data[i] = 0;
                }
            }
        #endif
        #undef OPTION_ZERO_MEMORY
    #endif
#endif

SimpleArrayUtils.h

/**
 * this is a smart collection that is created using options and is 
 *      removed from scope when included with uninstall option.
 *
 * there are no guards because this header is meant to be strategically
 *     installed and uninstalled, rather than kept at all times.
 */
#ifdef OPTION_UNINSTALL
    /* clean up, be mindful of undef warnings if the macro is not defined. */
    #ifdef ARRAY_FOREACH_BEGIN
        #undef ARRAY_FOREACH_BEGIN
    #endif

    #ifdef ARRAY_FOREACH_END
        #undef ARRAY_FOREACH_END
    #endif

    #ifdef ARRAY_LENGTH
        #undef ARRAY_LENGTH
    #endif
#else
    /** 
     * array elements vary in number of bytes, encapsulate common use case 
     */
    #define ARRAY_LENGTH(A) \
        ((sizeof A.data) / (sizeof A.data[0]))

    /**
     * first half of a foreach loop, create an anonymous scope,
     * declare an iterator, and start accessing the items. 
     */
    #if defined OPTION_ARRAY_TYPE
        #define ARRAY_FOREACH_BEGIN(name, iter, arr)\
            {\
                unsigned int iter;\
                for (iter = 0; iter < ARRAY_LENGTH(arr); ++iter) {\
                    OPTION_ARRAY_TYPE name = arr.data[iter];
    #endif

    /** 
     * second half of a foreach loop, close the loop and the anonymous scope 
     */
    #define ARRAY_FOREACH_END \
            }\
        }
#endif

main.c

#include <stdio.h>

// std::array<int, 100> A;
#define OPTION_ARRAY_TYPE int
#define OPTION_ARRAY_LENGTH 100
#define OPTION_ARRAY_NAME A
#include "SimpleArray.h"  
#define OPTION_UNINSTALL
#include "SimpleArray.h"  

int Find(int A[], int A_length)
{
    int j;

    for (j = 0; j < A_length; ++j) {
        if (A[j] < 0) {
            return j;
        }
    }

    return -1;
}

int main(void)
{
    #define OPTION_ARRAY_NAME A
    #define OPTION_ARRAY_LENGTH (sizeof(A.data) / sizeof(A.data[0]))
    #define OPTION_ARRAY_TYPE int

    #include "SimpleArray.h"

    /**
     * anonymous scope #1
     *     initialize our array to [0..99],
     *     then set 18th element to its negative value(-18)
     *     to make the search more interesting.
     */
    {
        #include "SimpleArrayUtils.h"

        printf("size: %d.\n", ARRAY_LENGTH(A));

        ARRAY_FOREACH_BEGIN(item, i, A)
            A.data[i] = i * i;
        ARRAY_FOREACH_END

        A.data[17] = -A.data[17];


        // uninstall all macros.
        #define OPTION_UNINSTALL
        #include "SimpleArrayUtils.h"
    }

    /**
     * anonymous scope #2
     *     find the index of the smallest number and print it.
     */
    {
        #include "SimpleArrayUtils.h"        
        int result = Find(A.data, (sizeof(A.data) / sizeof(A.data[0])));

        printf(
            "First negative integer in A found at index = %d.\n",
            result
        );

        // uninstall all macros.
        #define OPTION_UNINSTALL
        #include "SimpleArrayUtils.h"
    }

    // wait for user input before closing.
    getchar();

    // making sure all macros of SimpleArray do not affect any code
    // after this function; macros are file-wide, so we want to be 
    // respectful to our other functions.
    #define OPTION_UNINSTALL
    #include "SimpleArray.h"

    return 0;
}

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

Я подчеркиваю силу PHP здесь, потому что мало кто видел его вне контекста документов HTML; но вы можете использовать его в C-документах или любых других текстовых файлах. Вы можете использовать Templating Toolkit, чтобы в скриптах был любой язык, который вам нравится; и эти языки будут намного лучше, чем препроцессор C, потому что они имеют пространства имен, переменные и фактические функции; это облегчает их отладку, так как вы отлаживаете настоящий скрипт, который генерирует код; не препроцессор C, который является адом для отладки, в основном из-за фамильярности (кто в здравом уме тратит часы, чтобы поиграть и познакомиться с препроцессором C? немногие это делают).

Вот пример этого с помощью PHP:

SimpleArray.php

<?php
    class SimpleArray {
        public $length;
        public $name;
        public $type;

        function __construct($options) {
            $this->length = $options['length'];
            $this->name = $options['name'];
            $this->type = $options['type'];
        }

        function getArray() {
            echo ($this->name . '.data');
        }

        function __toString() {            
            return sprintf (
                "struct {\n" .
                "    %s data[%d];\n" .
                "} %s;\n"
                ,
                $this->type,
                $this->length,
                $this->name
            );          
        }
    };
?>

main.php

#include <stdio.h>
<?php include('SimpleArray.php'); ?>

int Find(int *A, int A_length)
{
    int i;

    for (i = 0; i < A_length; ++i) 
    {
        if (A[i] < 0) {
            return i;
        }
    }

    return -1;
}

int main(int argc, char **argv)
{
    <?php 
        $arr = new SimpleArray(array(
            'name' => 'A',
            'length' => 100,
            'type' => 'int'
        ));
        echo $arr;
    ?>

    printf("size of A: %d.\n", <?php echo($arr->length); ?>);

    /* anonymous scope */
    {
        int i;

        for (i = 0; i < <?php echo($arr->length)?>; ++i) {
            <?php $arr->getArray(); ?>[i] = i * i;
        }   
        <?php $arr->getArray(); ?>[17] = -<?php $arr->getArray()?>[17];
    }

    int result = Find(<?php $arr->getArray();?>, <?php echo $arr->length; ?>);
    printf(
        "First negative integer in A found at index = %d.\n",
        result
    );

    getchar();       

    return 0;
}

бежатьphp main.php > main.c

затем

gcc main.c -o main
./main

Это очень похоже на Objective C, потому что это, по сути, то, что делает цель C, за исключением того, что имеет тенденцию связывать «макросы»; времени компиляции до фактического времени выполнения (как будто php был доступен во время выполнения во время работы C, и, в свою очередь, ваш C может общаться с php, а php может общаться с C, за исключением того, что php - это язык smalltalkish с множеством квадратных скобок) , Основное различие заключается в том, что цель C, насколько мне известно, не имеет способа сделать «статичным» конструирует, как мы сделали здесь; его объекты на самом деле выполняются, и поэтому доступ к ним намного дороже, но они гораздо более гибкие и сохраняют структуру, в то время как C структурирует до байтов, как только заголовок покидает область видимости (тогда как объекты могут быть отражены обратно в исходное состояние. состояние с использованием внутренних теговых союзов) ...

почему PHP? Подобные препроцессоры проще и обычно делаются с помощью AWK. Я предлагаю вам ознакомиться с «Объектно-ориентированным программированием на ANSI C». Аксель Шрайнер.Found Free Here Я думаю, что вы будетеreally наслаждайся этим!
Тем не менее, я признаю, что никогда не использовал awk, спасибо, что напомнили мне об этом.
Я полностью согласен со всем, что вы сказали. Я немного забрал PHP, но я думаю, что лямбда-калькулятор - лучший вариант для препроцессора. Я только предложил книгу ooc для глав препроцессора, остальная часть книги (хотя и очень интересная) здесь не применима. ООК - одна из моих любимых малоизвестных книг, я рада знакомству с кем-то, кто ее прочитал
моя точка зрения такова, что любое другое лямбда-исчисление подходит лучше, чем текущий препроцессор c; будь то Perl, Javascript, ASP или AWK, они все ладят с C лучше, чем текущий. Я говорю php из-за его доступности, но объективно, из любого другого языка, включая c, если сам компилятор c экспортируется с помощью dll / .so, а не исполняемого файла, что допускает «поведение, аналогичное eval». с помощью compile / getsym / getprocaddress, который будет эффективен так же, как и любое другое лямбда-исчисление, только немного дольше времени компиляции.
я также хорошо знаю объектно-ориентированный C, я читал эту книгу, и она эффективно переизобретает COM, который изоморфен (хотя и уродлив, с небольшим переименованием и переупорядочением, равнозначен) любому другому способу взаимодействия между временем компиляции и временем выполнения обнаружение объектов по требованию из общих модулей (эффективно равнозначно чтению файла js / perl / lisp / etc из текста, который переводится в словарь из текста в функции и его вычисление), если я правильно помню, ooc пишет свой собственный эффективный эквивалент Microsoft IDL, которую он мог бы просто использовать препроцессором, чтобы избежать этого.
42

да шаблонов. Вот что я хотел бы сделать:

Creation of a templated list Macro to define the list

Сначала я создал бы макрос - позвольте ему называть этоdefine_list(type) - это создаст все функции для списка данного типа. Затем я создал бы глобальную структуру, содержащую указатели функций на все функции списка, и затем имел бы указатель на эту глобальную структуру в каждом экземпляре списка (обратите внимание, насколько он похож натаблица виртуальных методов). Такая вещь:

#define define_list(type) \
\
    struct _list_##type; \
    \
    typedef struct \
    { \
        int (*is_empty)(const struct _list_##type*); \
        size_t (*size)(const struct _list_##type*); \
        const type (*front)(const struct _list_##type*); \
        void (*push_front)(struct _list_##type*, type); \
    } _list_functions_##type; \
    \
    typedef struct _list_elem_##type \
    { \
        type _data; \
        struct _list_elem_##type* _next; \
    } list_elem_##type; \
    \
    typedef struct _list_##type \
    { \
        size_t _size; \
        list_elem_##type* _first; \
        list_elem_##type* _last; \
        _list_functions_##type* _functions; \
    } List_##type; \
    \
    List_##type* new_list_##type(); \
    bool list_is_empty_##type(const List_##type* list); \
    size_t list_size_##type(const List_##type* list); \
    const type list_front_##type(const List_##type* list); \
    void list_push_front_##type(List_##type* list, type elem); \
    \
    bool list_is_empty_##type(const List_##type* list) \
    { \
        return list->_size == 0; \
    } \
    \
    size_t list_size_##type(const List_##type* list) \
    { \
        return list->_size; \
    } \
    \
    const type list_front_##type(const List_##type* list) \
    { \
        return list->_first->_data; \
    } \
    \
    void list_push_front_##type(List_##type* list, type elem) \
    { \
        ... \
    } \
    \
    _list_functions_##type _list_funcs_##type = { \
        &list_is_empty_##type, \
        &list_size_##type, \
        &list_front_##type, \
        &list_push_front_##type, \
    }; \
    \
    List_##type* new_list_##type() \
    { \
        List_##type* res = (List_##type*) malloc(sizeof(List_##type)); \
        res->_size = 0; \
        res->_first = NULL; \
        res->_functions = &_list_funcs_##type; \
        return res; \
    }

#define List(type) \
    List_##type

#define new_list(type) \
    new_list_##type()
Generic interface

Вот некоторые макросы, которые просто вызывают функции списка через указатели хранимых функций:

#define is_empty(collection) \
    collection->_functions->is_empty(collection)

#define size(collection) \
    collection->_functions->size(collection)

#define front(collection) \
    collection->_functions->front(collection)

#define push_front(collection, elem) \
    collection->_functions->push_front(collection, elem)

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

Example of use

И в заключение, небольшой пример того, как использовать наш новый шаблон списка:

/* Define the data structures you need */
define_list(int)
define_list(float)

int main()
{
    List(int)* a = new_list(int);
    List(float)* b = new_list(float);

    push_front(a, 5);
    push_front(b, 5.2);
}

Вы можете использовать это количество уловок, если вы действительно хотите иметь какие-то шаблоны в C, но это довольно уродливо (просто используйте C ++, это будет проще). Единственными дополнительными затратами будут еще один указатель на экземпляр структуры данных и, таким образом, еще одно косвенное обращение всякий раз, когда вы вызываете функцию (преобразование не выполняется, вам не нужно хранитьvoid* указатели, ага \ о /). Надеюсь, вы никогда не будете использовать это: p

Limitations

Конечно, есть некоторые ограничения, так как мы используем простые текстовые макросы замены, а не реальные шаблоны.

Define once

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

Multi-word types

Если вы хотите создатьList чей тип шаблона состоит из нескольких слов (signed char, unsigned long, const bar, struct foo...) или чей тип шаблона является указателем (char*, void*...), тебе придетсяtypedef этот тип первым.

define_list(int) /* OK */
define_list(char*) /* Error: pointer */
define_list(unsigned long) /* Error: several words */

typedef char* char_ptr;
typedef unsigned long ulong;
define_list(char_ptr) /* OK */
define_list(ulong) /* OK */

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

Можете ли вы объяснить недостаток, упомянутый в пункте:Define once? Вы имели в виду, если определяет:is_empty, size, front, конфликт с другими определен?
Умные трюки, но я бы этим не воспользовался, потому что кажется, что он скрывает слишком много вещей.
@ 2501 Если у вас есть несколько включений с, например,define_list(int) в том же модуле перевода компилятор увидит все функции, определенные макросом (list_is_empty_int, list_push_front_intи т. д.) несколько раз и вызывают ошибку во время компиляции.
Ну, шаблоны скрывают одно и то же и делают еще больше вместо вас. Но я бы не стал использовать это в реальном проекте. Если мне нужны шаблоны, я просто использую C ++.
Вы можете решить проблему с многословными типами, указав макросу определение двух параметров: один тип и одно имя типа контейнера. Это также частично решает проблему определения одного раза, поскольку теперь у вас есть увеличенное пространство имен: вы задаете имя типа контейнера для конкретного использования, и будет естественно определить его в определенном заголовке. Еще один момент: для более сложных контейнеров, вероятно, имеет смысл использовать не встроенные вспомогательные функции, которые обрабатывают анонимные типы данных заданного размера, и определять встроенные функции доступа, используя метод, который вы демонстрируете, который заботится о безопасности типов.
0

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

http://en.cppreference.com/w/c/language/generic

к несчастью_Generic превосходно, когда есть только несколько типов для обработки, но не очень помогает, когда функции должны обрабатывать почтиevery type  как шаблоны делают. Я пытался улучшить свой подход с_Generic, но он не помещался нигде в коде.
-1
#define q(t)                                    \
  typedef struct _q_##t {t v; struct q_##t *next} q_##t;

q(char);
q(int);

int              main(void)
{
  q_char         qc;
  q_int          qi;

  qc.v = 'c';
  qc.next = (void *) 0;

  qi.v = 42;
  qi.next = (void *) 0;

  return 0;
}

3

которая может позволить вам создать экземпляр (через препроцессор) и использоватьmultiple вводит в тот же файл C(Careful, it uses объединение токенов):

#include <stdio.h>

#define DEFINE_LL_NODE(CONCRETE_TYPE) \
  struct node_of_ ## CONCRETE_TYPE \
    { \
      CONCRETE_TYPE data; \
      struct node_of_ ## CONCRETE_TYPE *next; \
    };

#define DECLARE_LL_NODE(CONCRETE_TYPE,VARIABLE_NAME) \
  struct node_of_ ## CONCRETE_TYPE VARIABLE_NAME;

/* Declarations for each type.  */
DEFINE_LL_NODE(int)
DEFINE_LL_NODE(char)

int main (void)
{
  /* Declaration of instances of each type.  */
  DECLARE_LL_NODE (int, foo)
  DECLARE_LL_NODE (char, bar)

  /* And you can then use these instances.  */
  foo.data = 1;
  foo.next = NULL;

  bar.data = 'c';
  bar.next = NULL;
}

Если я предварительно обработать его сcpp, Я получил:

struct node_of_int { int data; struct node_of_int *next; };

struct node_of_char { char data; struct node_of_char *next; };

int main (void)
{
  struct node_of_int foo;
  struct node_of_char bar;

  foo.data = 1;
  foo.next = ((void *)0);

  bar.data = 'c';
  bar.next = ((void *)0);
}
Коротко и сладко.
1

если тыreally хочу сделать это, это может быть решено простымtypedef:

typedef int data_t;

struct queue
{
  data_t* data;
}

Теперь вы можете использоватьdata_t во всех местах, а не на равнинеints. Обратите внимание, что вы не сможете использовать несколько типовat once (по крайней мере, я не вижу способа, как это конкретное поведение шаблонов C ++ может быть смоделировано в простом C).

@RondogiannisAristophanes Извините за недоразумение. Прочитав другие ответы здесь, я вижу, что то, что вы ожидали, намного сложнее, чем то, что я предлагал;)
Ну, это не то, что я Я ищу. Rontogiannis Aristofanis
Это выглядит как довольно ограниченное представление о том, что делают шаблоны ... Я бы никогда не использовал шаблон вместо простого typedef .....
6

содержащую данные void *, и интерпретируйте этот void * как указатель на любой тип или даже примитивный тип, например, int.

Использование #define возможно, но подумайте об отладке, если что-то не так ...

0

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

Это означает, что вам нужно учитыватьvoid * Типовые решения, которые ослабляют проверку типа С. Чтобы попытаться исправить ослабленную проверку типов, рассмотрите возможность встраивания & quot; типа & quot; поле в вашей структуре, которое является «назначаемым один раз при построении»; строка, которая представляет тип не void *. Тогда вы можете исправить недостаток проверки типов в функциях, связанных с поддержкой структуры. То есть если такая вещь даже важна для тебя.

относительно"The issue is that the pre-processor doesn't understand C's typing system. "Но разве это не преимущество препроцессора? Предполагается, что он может переводить X в Y независимо от того, что X.
Это может быть преимуществом для некоторых сценариев, но когда вы пытаетесь применить системы ввода (как это делают интерфейсы, классы и абстрактные классы в иерархии типов), отсутствие системы ввода не является преимуществом.
Есть ли обходные пути, чтобы заставить их расширяться в два раза?
Теоретически, вы можете пропустить их через препроцессор дважды. это может привести к довольно забавной разметке, так как вам придется обработать вывод; но на самом деле проблема заключается не в том, сколько раз препроцессор работает с текстом. Проблема состоит в том, что препроцессор не понимает систему печати C. Она была разработана так, чтобы быть простой «заменой текста», система шаблонов C ++ работает с системой типов, поэтому она знает, какие фрагменты текста являются ключевыми словами языка, и имеет некоторое представление о структуре языка во время расширения.
22

Ну, единственная возможность, которая приходит мне в голову, это макросы (#defineс). Может быть что-то вроде:

queue.h:

#define TYPE int
#define TYPED_NAME(x) int_##x
#include "queue_impl.h"
#undef TYPE
#undef TYPED_NAME

#define TYPE float
#define TYPED_NAME(x) float_##x
#include "queue_impl.h"
#undef TYPE
#undef TYPED_NAME
...

queue_impl.h:

//no include guard, of course
typedef struct
{
    TYPE *data;
    ...
} TYPED_NAME(queue);

void TYPED_NAME(queue_insert) (TYPED_NAME(queue) *queue, TYPE data)
{
    ...
}

Если это работает (в чем я не уверен на 100%, не являясь таким экспертом препроцессора), это должно дать вам структуруint_queue а такжеfloat_queueвместе с функциями

void int_queue_insert(int_queue *queue, int data);
void float_queue_insert(float_queue *queue, float data);

Конечно, вам придется выполнить создание экземпляра & quot; шаблона & quot; самостоятельно для всех типов, которые вам нужны, но это равносильно повторению 5-строчного блока вqueue.h, Фактическая реализация должна быть написана только один раз. Конечно, вы можете уточнить это еще больше, но основная идея должна быть ясной.

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

Я думаю, что это может быть улучшено еще один шаг с техникойstackoverflow.com/a/202511/16673 избавиться от повторяющихся включений и неопределенностей.
Для тех, кто все еще интересуется этим вопросом, более чистое решение, которое я нашел,arnold.uthar.net/index.php?n=Work.TemplatesC
Также модуль шаблона должен #undef иметь собственные параметры, чтобы избежать беспорядка.
Я развил эту идею чуть дальше в посте блога по универсальному программированию на C, включая некоторые обсуждения того, как заставить функции вести себя в общем виде:abissell.com/2014/01/16/…
@ Suma Я предпочитаю это решение, потому что таким образом включенный модуль не является единственной огромной директивой препроцессора. Если у вас есть ошибка, компилятор определит точную строку, а не просто укажет на строку, где развернут макрос. Конечно, GCC никогда этого не улучшал, но более старые компиляторы не сильно помогли.

Похожие вопросы