2009-03-19 17 views
64

Ho poco più che competenze di livello C per principianti e vorrei sapere se ci sono degli "standard" di fatto per strutturare un'applicazione piuttosto complessa in C. Anche quelli basati su GUI.Come devo strutturare progetti complessi in C?

Ho sempre utilizzato il paradigma OO in Java e PHP e ora che voglio imparare C temo di poter strutturare le mie applicazioni nel modo sbagliato. Sono in perdita su quali linee guida seguire per avere modularità, disaccoppiamento e secchezza con un linguaggio procedurale.

Avete letture da suggerire? Non sono riuscito a trovare nessun framework di applicazione per C, anche se non utilizzo i framework ho sempre trovato buone idee sfogliando il loro codice.

+2

La filosofia Unix è piuttosto utile per organizzare grandi progetti: [http://www.faqs.org/docs /artu/ch01s06.html](http://www.faqs.org/docs/artu/ch01s06.html) – newDelete

risposta

2

Ti suggerisco di controllare il codice di qualsiasi progetto C open source, come ... hmm ... kernel Linux o Git; e vedi come lo organizzano.

2

La regola del numero per un'applicazione complessa: dovrebbe essere facile da leggere.

Per semplificare l'applicazione complessa, utilizzo Divide and conquer.

43

La chiave è la modularità. Questo è più facile da progettare, implementare, compilare e mantenere.

  • Identifica i moduli nella tua app, come le classi in un'app OO.
  • Interfaccia separata e implementazione per ogni modulo, inserire solo l'interfaccia necessaria per gli altri moduli. Ricorda che non c'è spazio dei nomi in C, quindi devi rendere tutto unico nelle tue interfacce (ad es. Con un prefisso).
  • Nascondere le variabili globali nell'implementazione e utilizzare le funzioni di accesso per lettura/scrittura.
  • Non pensare in termini di ereditarietà, ma in termini di composizione. Come regola generale, non cercare di imitare C++ in C, questo sarebbe molto difficile da leggere e mantenere.

Se avete tempo per l'apprendimento, date un'occhiata a come un app Ada è strutturata, con la sua obbligatoria package (modulo di interfaccia) e package body (implementazione del modulo).

Questo è per la codifica.

Per la manutenzione (ricordarsi che si codifica una volta, ma si mantengono più volte) Suggerisco di documentare il codice; Doxygen è una buona scelta per me. Suggerisco anche di costruire una potente suite di test di regressione, che ti permetta di refactoring.

10

Gli GNU coding standards si sono evoluti nel corso di un paio di decenni. Sarebbe una buona idea leggerli, anche se non li segui alla lettera. Pensare ai punti sollevati in essi ti offre una base più solida su come strutturare il tuo codice.

+4

Non piacciono a tutti, da http://lxr.linux.no/linux+v2.6.29/Documentation/CodingStyle: "Prima di tutto, suggerirei di stampare una copia degli standard di codifica GNU e NON leggerla, masterizzali, è un grande gesto simbolico". Non li ho letti da molti anni, ma Linus ha alcune obiezioni valide. – hlovdal

+0

@hlovdal: Naturalmente a nessuno piace uno standard di codifica particolare, ecco perché ci sono più di uno standard per casi d'uso simili. La parte importante è che tu sia coerente nei tuoi progetti, che almeno qualche standard sia seguito, piuttosto che incoerenza di fatto. – TechZilla

27

È un malinteso comune che le tecniche OO non possano essere applicate in C. La maggior parte può - è solo che sono leggermente più ingombranti rispetto alle lingue con sintassi dedicata al lavoro.

Uno dei fondamenti della progettazione di un sistema robusto è l'incapsulamento di un'implementazione dietro un'interfaccia. FILE* e le funzioni che funzionano con esso (fopen(), fread() ecc.) Sono un buon esempio di come l'incapsulamento può essere applicato in C per stabilire le interfacce. (Naturalmente, dato che C manca degli specificatori di accesso, non si può far rispettare il fatto che nessuno dà una sbirciata all'interno di uno struct FILE, ma solo un masochista lo farebbe.)

Se necessario, il comportamento polimorfico può essere ottenuto in C mediante tabelle di puntatori di funzione. Sì, la sintassi è brutto, ma l'effetto è lo stesso di funzioni virtuali:

struct IAnimal { 
    int (*eat)(int food); 
    int (*sleep)(int secs); 
}; 

/* "Subclass"/"implement" IAnimal, relying on C's guaranteed equivalence 
* of memory layouts */ 
struct Cat { 
    struct IAnimal _base; 
    int (*meow)(void); 
}; 

int cat_eat(int food) { ... } 
int cat_sleep(int secs) { ... } 
int cat_meow(void) { ... } 

/* "Constructor" */ 
struct Cat* CreateACat(void) { 
    struct Cat* x = (Cat*) malloc(sizeof (struct Cat)); 
    x->_base.eat = cat_eat; 
    x->_base.sleep = cat_sleep; 
    x->meow = cat_meow; 
} 

struct IAnimal* pa = CreateACat(); 
pa->eat(42);      /* Calls cat_eat() */ 

((struct Cat*) pa)->meow();  /* "Downcast" */ 
+10

Un codificatore C puro si perderebbe leggendo questo codice ... – mouviciel

+14

@mouviciel: Spazzatura! La maggior parte dei programmatori C capisce i puntatori di funzione (o almeno dovrebbero), e non c'è nulla che vada oltre a questo qui. Almeno su Windows, i driver di dispositivo e gli oggetti COM forniscono entrambe le loro funzionalità in questo modo. –

+8

Il mio punto non riguarda l'incompetenza, riguarda la complicazione non necessaria. I puntatori di funzione sono comuni per un codificatore C (ad es. Callback), l'ereditarietà no. Preferisco che un codificatore C++ che codifica in C usi il suo tempo per imparare C piuttosto che costruire classi pseudo C++. Detto questo, il tuo approccio potrebbe essere utile in alcuni casi. – mouviciel

3

Se si sa come strutturare il codice in Java o C++, quindi è possibile seguire gli stessi principi con il codice C. L'unica differenza è che non hai il compilatore al tuo fianco e devi fare tutto manualmente con attenzione.

Poiché non ci sono pacchetti e classi, è necessario iniziare progettando attentamente i moduli. L'approccio più comune è creare una cartella di origine separata per ciascun modulo. È necessario fare affidamento sulle convenzioni di denominazione per differenziare il codice tra diversi moduli. Ad esempio, prefisso tutte le funzioni con il nome del modulo.

Non è possibile avere classi con C, ma è possibile implementare facilmente "Tipi di dati astratti". Si crea un file .C e .H per ogni tipo di dati astratto. Se preferisci puoi avere due file header, uno pubblico e uno privato. L'idea è che tutte le strutture, le costanti e le funzioni che devono essere esportate passino al file di intestazione pubblico.

Anche gli strumenti sono molto importanti. Uno strumento utile per C è lint, che può aiutarti a trovare cattivi odori nel codice. Un altro strumento che puoi utilizzare è Doxygen, che può aiutarti a generare documentation.

2

L'incapsulamento è sempre la chiave per uno sviluppo di successo, indipendentemente dal linguaggio di sviluppo.

Un trucco che ho usato per incapsulare i metodi "privati" in C è quello di non includere i loro prototipi nel file ".h".

12

Tutte buone risposte.

Vorrei solo aggiungere "minimizzare la struttura dei dati". Questo potrebbe anche essere più semplice in C, perché se C++ è "C con classi", OOP sta cercando di incoraggiarti a prendere ogni nome/verbo nella tua testa e trasformarlo in una classe/metodo. Questo può essere molto dispendioso.

Ad esempio, si supponga di disporre di una serie di letture della temperatura in punti nel tempo e di visualizzarle come un grafico a linee in Windows. Windows ha un messaggio PAINT e, quando lo si riceve, è possibile eseguire il looping della matrice eseguendo le funzioni LineTo, ridimensionando i dati mentre si procede per convertirli in coordinate pixel.

Quello che ho visto troppe volte è che, poiché il grafico è costituito da punti e linee, le persone costruiranno una struttura di dati costituita da oggetti punto e oggetti linea, ognuno capace di disegnare Stesso, e quindi renderlo persistente, su la teoria che è in qualche modo "più efficiente", o che potrebbero, solo forse, essere in grado di passare il mouse su parti del grafico e visualizzare i dati numericamente, quindi costruiscono metodi negli oggetti per affrontarli, e , ovviamente, comporta la creazione e l'eliminazione di ancora più oggetti.

Così si finisce con un'enorme quantità di codice che è leggibile in modo non trascurabile e si limita a dedicare il 90% del tempo alla gestione degli oggetti.

Tutto questo viene fatto nel nome di "buona pratica di programmazione" ed "efficienza".

Almeno in C il modo semplice ed efficiente sarà più evidente e la tentazione di costruire piramidi meno forti.

+0

Ama la tua risposta ed è totalmente vero. OOP deve morire! –

+0

@JoSo: utilizzo OOP, ma in minima parte. –

+0

Per me "minimally" (anche se non so cosa significhi per te) in realtà non conta come OOP, che è Object * oriented * programming - objects di default. –

2

Suggerirei di leggere un libro di testo C/C++ come primo passo. Ad esempio, C Primer Plus è un buon riferimento.Guardando attraverso gli esempi ti daremo un'idea su come mappare il tuo java OO su un linguaggio più procedurale come C.

Problemi correlati