Simulare de șabloane în C (pentru un tip de date de coadă) (Programare, C, Șabloane)

Rontogiannis Aristofanis a intrebat.

Încerc să implementez un queue structură folosind C. Implementarea mea este foarte simplă; coada poate conține doar ints și nimic altceva. Mă întrebam dacă aș putea simula C++ șabloane în C(probabil prin utilizarea preprocesorului #define), astfel încât queue poate conține orice tip de date.

Notă: Nu vreau să folosesc void*. Cred că este un pic riscant și poate provoca cu ușurință erori bizare de execuție.

Comentarii

  • De ce să nu stocați pur și simplu pointeri la datele dvs. Căutați o validare la compilare ? –  > Por Denys Séguret.
  • V-ați gândit să folosiți pointer to void (void*)? –  > Por patseb.
  • Cel puțin puteți utiliza un typedef în loc de un #define. În ambele cazuri, sunteți blocat cu o singură instanțiere a fișierului dvs. șablon –  > Por Luca Martini.
  • Utilizați void* este calea corectă. Cereți să să simulați șabloane, deci de ce să faci o coadă de int când puteți avea o coadă de orice? –  > Por gliderkite.
  • @gliderkite, De ce să faci o coadă de orice când poți avea o coadă strictă de pure ints pentru a se potrivi cazului de utilizare, iar compilatorul știe acest lucru și vă poate ajuta să verificați dacă există erori? –  > Por Pacerier.
10 răspunsuri
Morwenn

Puteți folosi trucuri subtile și urâte pentru a crea acest tip de șabloane. Iată ce aș face eu:

Crearea unei liste cu șabloane

Macro pentru a defini lista

Aș crea mai întâi o macro – să o numim să zicem așa define_list(type) – care ar crea toate funcțiile pentru o listă de un anumit tip. Aș crea apoi o structură globală care să conțină pointeri de funcție pentru toate funcțiile listei și aș avea apoi un pointer la această structură globală în fiecare instanță a listei (observați cât de asemănător este cu o structură tabel de metode virtuale). Acest tip de lucru:

#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()

Interfață generică

Iată câteva macrocomenzi care apelează pur și simplu funcțiile listei prin intermediul pointerilor de funcție stocați:

#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)

Rețineți că, dacă utilizați aceeași structură pentru a proiecta alte colecții decât listele, veți putea utiliza ultimele funcții pentru orice colecție care stochează indicatorii buni.

Exemplu de utilizare

Și pentru a încheia, un mic exemplu de utilizare a noului nostru șablon de listă:

/* 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);
}

Puteți folosi această cantitate de trucuri dacă doriți cu adevărat să aveți un fel de șabloane în C, dar este destul de urât (folosiți doar C++, va fi mai simplu). Singurul cost suplimentar va fi un pointer în plus pentru fiecare instanță a structurii de date și, prin urmare, o indirectă în plus ori de câte ori apelați o funcție (nu se face nici un cast, nu trebuie să stocați void* pointeri, da o/). Sper că nu veți folosi asta niciodată :p

Limitări

Există, desigur, unele limitări, deoarece folosim simple macro-uri de înlocuire a textului, și nu șabloane reale.

Definiți o singură dată

Puteți defini fiecare tip doar o singură dată pe unitate de compilare, altfel programul dumneavoastră nu va reuși să se compileze. Acest lucru poate fi un dezavantaj major, de exemplu, dacă scrieți o bibliotecă și unele dintre antetele dvs. conțin unele define_ instrucțiuni.

Tipuri cu mai multe cuvinte

Dacă doriți să creați un tip List al cărui tip de șablon este alcătuit din mai multe cuvinte (signed char, unsigned long, const bar, struct foo…) sau al cărui tip de șablon este un pointer (char*, void*…), va trebui să typedef acel tip mai întâi.

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 */

Va trebui să recurgeți la același truc dacă doriți să creați liste imbricate.

Comentarii

  • Trucuri inteligente, dar eu nu aș folosi acest lucru, deoarece mi se pare că ascunde prea multe lucruri. –  > Por Pacerier.
  • Ei bine, șabloanele ascund aceleași lucruri și fac și mai multe lucruri în locul tău. Dar nici asta nu aș folosi-o într-un proiect real. Dacă am nevoie de șabloane, folosesc pur și simplu C++. –  > Por Morwenn.
  • Puteți explica dezavantajul menționat în paragraf: Define once? Ați vrut să spuneți dacă definește: is_empty, dimensiune, front, intră în conflict cu alte definiții? –  > Por 2501.
  • @2501 Dacă aveți mai multe incluziuni cu, de exemplu, define_list(int) în aceeași unitate de traducere, compilatorul va vedea toate funcțiile definite de macro (list_is_empty_int, list_push_front_int, etc…) de mai multe ori și va declanșa o eroare la compilare. –  > Por Morwenn.
  • Puteți rezolva problema cu tipurile cu mai multe cuvinte dacă macroul define acceptă doi parametri, unul fiind tipul și celălalt numele tipului container. Acest lucru rezolvă parțial și problema definirii o singură dată, deoarece acum aveți un spațiu de nume mai mare: ați stabilit un nume de tip container pentru o anumită utilizare și va fi normal să definiți acest lucru într-un antet specific. Încă un aspect: pentru containere mai complexe, probabil că are sens să se utilizeze funcții de ajutor non-inline care gestionează tipuri de date anonime de o anumită dimensiune și să se definească funcții de accesor inline utilizând tehnica pe care o arătați, care se ocupă de siguranța tipurilor. –  > Por michaeljt.
Christian Rau

Ei bine, singura posibilitate care îmi vine în minte sunt macrourile (#defines). Poate ceva de genul:

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)
{
    ...
}

Dacă funcționează (lucru de care nu sunt 100% sigur, nefiind un expert în preprocesoare), ar trebui să vă ofere structurile int_queue și float_queue, împreună cu funcțiile

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

Bineînțeles că va trebui să instanți tu însuți „șablonul” pentru toate tipurile de care ai nevoie, dar acest lucru se rezumă la repetarea blocului de 5 linii din queue.h. Implementarea efectivă trebuie scrisă o singură dată. Bineînțeles că puteți rafina acest lucru și mai mult, dar ideea de bază ar trebui să fie clară.

În acest fel veți obține cel puțin șabloane de coadă perfect sigure din punct de vedere al tipurilor, deși nu veți avea confortul unor interfețe care să corespundă complet (funcțiile trebuie să poarte numele tipului, deoarece C nu acceptă funcții supraîncărcate).

Comentarii

  • Cred că acest lucru ar putea fi îmbunătățit cu încă un pas cu tehnica stackoverflow.com/a/202511/16673 pentru a scăpa de includerile și nedefinițiile repetate. –  > Por Suma.
  • Am dezvoltat puțin mai mult această idee într-o postare pe blog despre programarea generică în C, inclusiv o discuție despre cum să convingi funcțiile să se comporte într-o manieră generică: abissell.com/2014/01/16/… –  > Por Andrew Bissell.
  • Pentru cei încă interesați de această întrebare, soluția mai curată pe care am găsit-o este arnold.uthar.net/index.php?n=Work.TemplatesC –  > Por michaelmeyer.
  • @Suma Prefer această soluție pentru că în acest mod modulul inclus nu este o singură directivă uriașă de preprocesor. Dacă aveți o eroare în el, compilatorul va depista linia exactă, și nu doar va indica linia în care macroul este extins. Desigur, niciodată GCC nu a îmbunătățit mult acest lucru, dar compilatoarele mai vechi nu vor ajuta prea mult. –  > Por Calmarius.
  • De asemenea, modulul de șablon ar trebui să #undef propriii parametri, pentru a evita dezordinea. –  > Por Calmarius.
Alex F

Implementați o coadă care să conțină date void* și interpretați acest void* ca pointer la orice tip, sau chiar la un tip primitiv precum int.

Folosirea #define este posibilă, dar gândiți-vă la depanare, dacă ceva nu este în regulă…

Dmitry

M-am întrebat mult timp despre acest lucru, dar acum am un răspuns definitiv pe care oricine îl poate înțelege; deci, iată!

Când făceam cursul de Structuri de date, a trebuit să citesc cartea lui Standish despre Structuri de date, Algoritmi în C; a fost dureros; nu avea generici, era plină de notații sărace și o grămadă de mutații de stare globală acolo unde nu aveau nici un motiv să fie acolo; știam că adoptarea stilului său de cod înseamnă să îmi stric toate proiectele viitoare, dar știam că există o cale mai bună, așa că iată, calea mai bună:

Iată cum arăta înainte de a mă atinge de el (de fapt, l-am atins oricum pentru a-l formata într-un mod în care oamenii să poată citi, cu plăcere); este foarte urât și greșit pe mai multe planuri, dar îl voi enumera pentru referință:

#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.
",
            result
        );
    }

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

    return 0;
}

Acest program face mai multe lucruri într-un stil îngrozitor de prost; În special, setează un macro global care este folosit doar într-un singur domeniu de aplicare, dar apoi persistă poluând orice cod mai departe; foarte rău și provoacă la scară largă poluarea domeniului global al API-ului Windows la scară largă.

Mai mult, acest program transmite argumentul ca array fără o structură care să-l conțină; cu alte cuvinte, array-ul este mort la sosire odată ce ajunge la funcția Find; nu mai știm dimensiunea array-ului, așa că acum avem main și Find care depind de un macro global, foarte rău.

Există două modalități brute de a elimina această problemă, dar de a păstra în continuare codul simplu; prima modalitate este de a crea un struct global care definește array-ul ca fiind un array de 100 de numere întregi; în acest fel, trecerea struct-ului va păstra lungimea array-ului în el. A doua modalitate este de a trece lungimea tabloului ca argument al lui find și de a folosi #define doar pe linia de dinaintea creării tabloului și #undef imediat după aceea, deoarece domeniul de aplicare va ști în continuare dimensiunea tabloului prin sizeof(A)/sizeof(A[0]), care nu are niciun cost suplimentar în timpul execuției, compilatorul va deduce 100 și îl va lipi în el.

Pentru a rezolva această problemă într-un al treilea mod, am realizat un antet care joacă frumos pentru a crea array-uri generice; este un tip de date abstract, dar aș dori să îl numesc o structură de date automată.

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

Acest antet este, în esență, ceea ce ar trebui să arate fiecare antet de structură de date C dacă sunteți forțat să folosiți preprocesorul C (spre deosebire de PHP/Templating toolkit/ASP/propriul dvs. limbaj de scripting încorporabil, fie el lisp).

Haideți să o luăm la o tură:

#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.
",
            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;
}

Iată că am inventat un std::array naiv în C pur și preprocesor C! Am folosit macro-uri, dar nu suntem răi, pentru că facem curățenie după noi înșine! Toate macrourile noastre sunt undefd la sfârșitul domeniului nostru de aplicare.

Există o problemă; nu mai știm dimensiunea array-ului, decât dacă facem (sizeof(A.data) / sizeof(A.data[0])). Acest lucru nu are niciun cost suplimentar pentru compilator, dar nu este potrivit pentru copii; nici macrourile nu sunt bune pentru copii, dar lucrăm în limitele legii aici; mai târziu putem folosi un preprocesor mai prietenos, cum ar fi PHP, pentru a face acest lucru potrivit pentru copii.

Pentru a rezolva acest lucru, putem crea o bibliotecă utilitară care acționează ca metode asupra structurii noastre de date de tip array „liber”.

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

Aceasta este o bibliotecă destul de bogată în funcții, care în principiu exportă

ARRAY_LENGTH :: Orice lucru cu câmp de date -> int

și dacă mai avem definit OPTION_ARRAY_SIZE sau dacă l-am redefinit, antetul definește, de asemenea, cum să facem o buclă foreach; ceea ce este drăguț.

Acum hai să o luăm razna:

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.
", 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.
",
            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;
}

După cum puteți vedea, acum avem puterea de a exprima abstracțiuni libere (compilatorul le înlocuiește pentru noi), plătim doar pentru ceea ce avem nevoie (structurile), iar restul este aruncat și nu poluează domeniul global.

Subliniez aici puterea PHP pentru că puțini au văzut-o în afara contextului documentelor HTML; dar o puteți folosi în documente C sau în orice alte fișiere text. Puteți folosi Templating Toolkit pentru a pune în macro-uri orice limbaj de scripting doriți; și aceste limbaje vor fi mult mai bune decât preprocesorul C, deoarece au spații de nume, variabile și funcții reale; acest lucru le face mai ușor de depanat, deoarece depanați scriptul real care generează codul; nu preprocesorul C, care este infernal de depanat, în mare parte din cauza familiarității (cine, în deplinătatea facultăților mintale, petrece ore întregi pentru a se juca și a se familiariza cu preprocesorul C? puțini o fac).

Iată un exemplu de a face acest lucru cu 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 {
" .
                "    %s data[%d];
" .
                "} %s;
"
                ,
                $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.
", <?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.
",
        result
    );

    getchar();       

    return 0;
}

executați php main.php > main.c

apoi

gcc main.c -o main
./main

Acest lucru seamănă foarte mult cu Objective C, pentru că în esență este ceea ce face Objective C, cu excepția faptului că tinde să lege „macrogramele” din timpul de compilare de un timp de execuție real (ca și cum php ar fi fost disponibil în timpul execuției în timp ce C rulează și, la rândul său, C poate vorbi cu php și php poate vorbi cu C, cu excepția faptului că php este un limbaj de tip smalltalkish cu multe paranteze pătrate). Principala diferență este că, din câte știu eu, Objective C nu are o modalitate de a realiza construcții „statice”, așa cum am făcut aici; obiectele sale sunt de fapt în timpul execuției și, ca atare, sunt mult mai scumpe la accesare, dar sunt mult mai flexibile și păstrează structura, în timp ce structurile C se reduc la octeți de îndată ce antetul părăsește domeniul de aplicare (în timp ce obiectele pot fi readuse la starea lor inițială folosind uniuni interne etichetate)…

Comentarii

  • de ce PHP? preprocesoarele de acest tip sunt mai ușoare și, de obicei, sunt realizate cu AWK… Vă sugerez să consultați „Object Oriented Programming In ANSI C” de Axel Schreiner. Se găsește gratuit aici Cred că veți într-adevăr bucurați-vă de ea! –  > Por rmoro.
  • ceea ce vreau să spun este că orice alt calcul lambda este mai potrivit decât actualul preprocesor c; fie că e vorba de perl, javascript, asp sau awk, toate se înțeleg mai bine cu c decât cel actual. Spun php din cauza accesibilității sale, dar în mod obiectiv orice alt limbaj, inclusiv c dacă compilatorul de c în sine este exportat de un dll/.so în loc de executabil, permițând un „eval-like behavior” prin compile/getsym/getprocaddress care ar fi efectiv la fel de bun ca orice alt lambda calculus, doar că timpul de compilare ar fi puțin mai lung. –  > Por Dmitry.
  • de asemenea, cunosc bine C-ul orientat pe obiecte, am citit acea carte, și este efectiv reinventarea COM, care este izomorfă (deși urâtă, cu puțină redenumire și reordonare, echivalentă) cu orice altă modalitate de interoperare între timpul de compilare și timpul de execuție, descoperirea obiectelor la cerere din modulele partajate (echivalentă efectiv cu citirea unui fișier js/perl/lisp/etc. din text care se evaluează într-un dicționar din text în funcții și evaluarea acestuia), Dacă îmi amintesc corect, ooc își scrie propriul echivalent efectiv al IDL-ului Microsoft, ceea ce ar fi putut folosi doar preprocesorul pentru a evita acest lucru. –  > Por Dmitry.
  • acestea fiind spuse, recunosc că nu am folosit niciodată awk, mulțumesc că mi-ai amintit de el. –  > Por Dmitry.
  • Sunt întru totul de acord cu tot ceea ce ați spus. Am fost luat puțin înapoi de PHP, dar cred că lambda calc este de departe o opțiune mai bună pentru un preprocesor. Am sugerat cartea ooc doar pentru capitolele despre preprocesor, restul cărții (deși foarte interesantă) nu este aplicabilă aici. OOC este una dintre cărțile mele preferate de c obscure, mă bucur să întâlnesc pe altcineva care a citit-o! –  > Por rmoro.
ArjunShankar

Iată o versiune care vă poate permite să instanțiați (prin preprocesor) și să utilizați mai multe tipuri în același fișier C (Atenție, folosește concatenarea simbolurilor):

#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;
}

Dacă îl preprocesez cu cpp, obțin:

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);
}

Gnosophilon

Dacă într-adevăr doriți să faceți acest lucru, ar putea fi rezolvat printr-un simplu typedef:

typedef int data_t;

struct queue
{
  data_t* data;
}

Acum puteți folosi data_t în toate locurile în loc de simplu ints. Rețineți, totuși, că nu veți putea utiliza mai multe tipuri în același timp (cel puțin, nu văd cum acest comportament specific al șabloanelor C++ poate fi simulat în C simplu).

Comentarii

  • Ei bine, nu asta este ceea ce caut eu. –  > Por Rontogiannis Aristofanis.
  • @RondogiannisAristofanis Scuze pentru neînțelegere. După ce am citit celelalte răspunsuri de aici, văd că ceea ce așteptai este ceva mai complicat decât ceea ce am oferit eu 😉 –  > Por Gnosophilon.
  • Aceasta pare a fi o viziune destul de limitată asupra a ceea ce fac șabloanele…eu nu aș folosi niciodată un șablon în locul unui simplu typedef….. –  > Por Assimilater.
Edwin Buck

Nu poți obține cu adevărat un șablon de înaltă calitate asemănător cu cel din C cu ajutorul macrourilor din preprocesor; deoarece, acele macrouri se extind doar o singură dată, așa că în cel mai bun caz poți obține o structură de date care poate fi restilizată, dar care odată procesată este acel tip pentru întregul program.

Aceasta înseamnă că trebuie să luați în considerare void * soluții de tip, care slăbesc verificarea de tip din C. Pentru a încerca să remediați verificarea slăbită a tipurilor, luați în considerare încorporarea unui câmp „type” în structura dumneavoastră, care este un șir de caractere „asignat o dată la construcție” care reprezintă tipul non void*. Apoi, puteți îmbunătăți lipsa verificării tipurilor în cadrul funcțiilor legate de menținerea structurii. Asta în cazul în care un astfel de lucru este important pentru dumneavoastră.

Comentarii

  • Există vreo soluție pentru a le face să se extindă de două ori? –  > Por Pacerier.
  • Teoretic, le-ai putea trece prin preprocesor de două ori. ar putea fi o marcare destul de amuzantă, deoarece ar trebui să elaborezi ieșirea; dar, problema nu este numărul de ori de câte ori preprocesorul lucrează asupra textului. Problema este că preprocesorul nu înțelege sistemul de tastare al C. A fost conceput pentru a fi o simplă „înlocuire de text”, sistemul de modelare din C++ funcționează cu sistemul de tipărire, astfel încât știe ce fragmente de text sunt cuvinte cheie ale limbajului și are o idee despre structura limbajului în timpul expansiunii. –  > Por Edwin Buck.
  • În ceea ce privește „Problema este că preprocesorul nu înțelege sistemul de tipărire din C. „, dar nu acesta este avantajul unui preprocesor? Se presupune că este capabil să traducă X în Y indiferent de ceea ce este X. –  > Por Pacerier.
  • Ar putea fi un avantaj pentru anumite scenarii, dar atunci când încerci să impui sisteme de tipărire (așa cum fac interfețele, clasele și clasele abstracte într-o ierarhie de tipuri), faptul că nu ai un sistem de tipărire nu este un avantaj. –  > Por Edwin Buck.
Benoit
#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;
}

Dar nu sunt sigur că este ceea ce căutați…

Samuel Danielson

Folosiți una dintre macro-urile de generare a codului din alt răspuns și apoi terminați-o cu câteva macro-uri de supraîncărcare C11, astfel încât să nu trebuiască să vă murdăriți site-urile de apelare cu prea multe informații de tip.

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

Comentarii

  • Din păcate, _Generic este excelent atunci când există doar câteva tipuri de gestionat, dar nu prea ajută atunci când funcțiile trebuie să gestioneze aproape fiecare tip așa cum fac șabloanele. Am încercat să îmi îmbunătățesc abordarea cu _Generic, dar nu s-a potrivit nicăieri în cod. –  > Por Morwenn.
Igor Kouznetsov

Câteva comentarii bazate pe ceea ce am văzut în răspunsuri.

  1. Într-adevăr, modalitatea de a aborda acest lucru în C este de a te juca cu macro-urile #define și cu pointeri de funcție. Chestia este că șabloanele C++, la prima lor versiune, erau cam asta – o simplă modalitate de a copia niște cod și de a genera niște simboluri, un fel de „parametrizare” textuală a originalului. Cerul este limita în ceea ce privește modul în care vă puteți folosi imaginația în acest caz, dar aveți grijă, deoarece în multe privințe sunteți pe cont propriu în ceea ce privește verificarea tipurilor etc., nu vă așteptați la prea mult ajutor din partea compilatorului.

  2. A spune din când în când „de ce nu folosiți doar C++” este contraproductiv în acest caz. Întrebarea era în special despre cum să simulezi funcția atunci când nu o ai. Din experiența mea, trebuie să spun că am simulat odată șabloanele chiar și în C++. Îndrăzniți să ghiciți de ce? Pentru că eram la începutul anului 1990, exista C++, și o idee de șabloane în C++, dar aproape nicio implementare a acestora. Iată de ce. Ei bine, am făcut-o și în C înainte de asta. C++ a ușurat oarecum lucrurile doar pentru că, cel puțin, nu mai trebuia să simulezi metodele de clasă folosind pointeri de funcție, pentru că, ei bine, aveai suportul limbajului nativ pentru asta. Altfel, pe atunci, la fel ca în C, #define era singurul tău prieten pentru programarea a-la parametric.