Benvenuti alla quindicesima lezione del nostro corso completo di programmazione in C, se avete avuto l’ardore di seguirci fino a questo punto ormai avrete preso familiarità con il C e la sua sintassi.
In questa lezione vedremo tre costrutti: enum, union e struct che ci permetteranno di scrivere codice più comprensibile e più vicino al modo umano di vedere le cose. Questi costrutti sono forse “gli antenati” della moderna programmazione ad oggetti, vedremo infatti che utilizzandoli ci si può distaccare dal concetto di semplice variabile ed iniziare a ragionare in termini di “entità” più complesse.
enum
Le enum ci vengono incontro quando abbiamo a che fare con una lista di valori.
Facciamo un esempio: supponiamo di aver scritto un programa che, a seconda del giorno della settimana, ci mostri un messaggio personalizzato:
in pseudocodice:
if (oggi = Lunedì)
messaggio = "Dai, alzati il week end è finito"
else if (oggi = martedì )
messaggio = "Alzati campione!"
else if (oggi = mercoledì )
messaggio = "Già mezza settimana! inizia a programmare il prossimo weekend"
…..e così via....
Dovremmo decidere però come memorizzare l’elenco dei giorni della settimana….volendo si potrebbero memorizzare come stringhe di testo ma questo è un grosso problema, primo perché le stringhe di testo in C vanno trattate con cautela (li vedremo la prossima lezione) e poi è sempre possibile digitare male, nell’esempio precedente “martedì” e “mercoledì” li ho scritti con le iniziali minuscole.. scartate le stringhe allora dobbiamo assegnare un codice numerico, decidiamo ad esempio che per noi 0 significa “Lunedì” e modifichiamo il codice scivendo, sempre in pseudocodice:
if ( oggi = 0 )
messaggio = "Dai, alzati il week end è finito"
… e così via....
Ma quanto è chiaro questo test? Quando ci saremo dimenticati di aver associato mentalmente 0 a “Lunedì” riusciremo a capire il nostro codice? Per un inglese sarebbe più ragionevole supporre che 0 sia associato a “Sunday”?
Ecco quindi che nascono le enum. Utilizzando questo costrutto:
enum _GiorniSettimana{
Lunedì,
Martedì,
Mercoledì,
Giovedì,
Venerdì,
Sabato,
Domenica
};
abbiamo definito un nuovo tipo di dato, utilizzabile così come utilizziamo int, char e via dicendo, ma che può assumere solo i valori elencati all’interno della enum.
In questo modo il nostro codice sarà sicuramente più chiaro.
Metto qui il codice completo di un piccolo esempio che ho scritto utilizzando la libreria time.h, forse il codice non sarà comprensibile a tutti, soprattutto la funzione today() ma quello che conta è che siamo riusciti ad inserire all’interno del nostro programma, in maniera semplice, il concetto di giorno della settimana.
#include
#include
#include
enum GiornoSettimana{
Domenica,
Lunedi,
Martedi,
Mercoledi,
Giovedi,
Venerdi,
Sabato,
};
enum GiornoSettimana today() {
time_t timer;
timer=time(NULL);
struct tm *currentTime = localtime(&timer);
return currentTime->tm_wday;
}
int main(void) {
enum GiornoSettimana giornodioggi = today();
if (giornodioggi == Domenica) {
printf("Oggi è Domenica!!");
}
else if (giornodioggi == Lunedi) {
printf("Dai, alzati il week end è finito");
}
else if (giornodioggi == Martedi) {
printf("Alzati campione!");
}
else if (giornodioggi == Mercoledi) {
printf("Già mezza settimana! inizia a programmare il prossimo weekend");
}
else if (giornodioggi == Giovedi) {
printf("Domani è venerdì :) ");
}
else if (giornodioggi == Venerdi) {
printf("Venerdì..lo senti il profumo del Sabato??");
}
else if (giornodioggi == Sabato) {
printf("Inizia il fine settimana!!");
}
return 0;
}
Come si può notare l’enum ha proprio definito un nuovo tipo di dato, ed è possibile dichiarare una variabile di questo nuovo tipo scrivendo semplicemente:
enum GiornoSettimana nome_variabile
Ma come funziona l’enum? L’idea di base è molto semplice, ad ogni valore della enumerazione viene assegnato un intero a partire da 0, quindi a basso livello il confronto viene sempre fatto con gli interi, ma ad alto livello noi vediamo nomi più significativi come “lunedì, martedì etc”.
UNION
Le union definiscono, così come le enum, un tipo di dato. La particolarità della union è che contiene più variabili, anche di dimensioni diverse ma tutte sono memorizzate alla stessa allocazione di memoria quindi occupa lo spazio occupato dalla più grande di esse. Questo, in pratica, significa che la union è come un “contenitore” che può essere utilizzato per contenere più tipi di variabili, ma ne può essere utilizzato solo uno alla volta.
Riporto, per comodità, un esempio prelevato da wikipedia (link)
#include
union numero{
int valX;
int valY;
};
int main(){
union numero Punto; // Definisce la variabile Punto di tipo union numero
Punto.valX=50; // Inizializza il membro valX dell'unione al valore 50
Punto.valY=100; // Inizializza il membro valY dell'unione al valore 100
printf("Il valore di X vale %d mentre il valore di Y vale %d\n",Punto.valX,Punto.valY); // Stampa sullo schermo i membri dell'unione''
return 0;
}
Per accedere alla variabile contenuta all’interno della union si utilizza il “.” e come si può notare eseguendo il programma l’utilizzo della variabile “valY” all’interno della union ha sovrascritto il valore della variabile “valX”.
Ci possono essere però utilizzi più complessi del costrutto union, ad esempio per contenere alternativamente un intero oppure un puntatore a caratteri.
union identificativo{
int id_int;
char *id_char;
};
La union la si può pensare come l’antesignana del polimorfismo dei nuovi linguaggi ad oggetti, perché si può utilizzare una variabile in più modi a seconda della necessità. (ok forse è un pò forzato :P).
STRUCT
Lo ammetto, il costrutto struct è il mio preferito. Serve per definire un nuovo tipo di variabile, aggregando altri tipi di variabili ed eventualmente altre strutture.
Questo ci permette di staccarci dalla logica delle singole variabili ed iniziare a pensare in temrmini di “oggetti” (non storcano il naso i puristi della OOP).
Partiamo dall’esempio classico: scrivere una funzione che restituisca la distanza euclidea tra due punti nello spazio r2, in altri termini “date le misure dei cateti di un triangolo rettangolo, restituire la misura dell’ipotenusa”.
Se non usassimo le strutture il programma sarebbe pressapoco così:
#include
#include
#include
float distance(float x1, float y1, float x2, float y2) {
float d;
d = sqrt( pow(x1 - x2, 2) + pow(y1 - y2,2));
return d;
}
int main(void) {
float punto1x, punto1y, punto2x, punto2y, distanza;
punto1x = 0;
punto1y = 0;
punto2x = 1;
punto2y = 1;
distanza = distance(punto1x, punto1y, punto2x, punto2y);
printf("La distanza è: %2f",distanza);
return 0;
}
L’esempio sopra funziona, non c’è che dire.. ma come esprimiamo il fatto che “punto1x” e “punto2y” sono in qualche modo correlati, che sono due caratteristiche che nella nostra testa appartengono allo stesso concetto di “punto”? E che dire della funzione che calcola la distanza? Per noi la distanza viene calcolata tra due entità che sono “punti” non tra 4 numeri a virgola mobile…
Risolviamo tutto semplicemente definendo un nuovo tipo di dato con il costrutto struct così definito:
struct Point {
float x;
float y;
};
In questo modo il programma può essere riscritto semplicemente in questo modo:
#include
#include
#include
struct Point {
float x;
float y;
};
float distance(struct Point p1, struct Point p2) {
float d;
d = sqrt( pow(p1.x - p2.x, 2) + pow(p1.y - p2.y,2));
return d;
}
int main(void) {
struct Point p1, p2;
float distanza;
p1.x = 0;
p1.y = 0;
p2.x = 1;
p2.y = 1;
distanza = distance(p1, p2);
printf("La distanza è: %2f",distanza);
return 0;
}
In questo modo il programma ha assunto maggiore chiarezza e, soprattutto, è più simile al modo di pensare dell’uomo.
Ma supponiamo adesso di calcolare la distanza tra due cerchi, non vogliamo certo ricadere nell’errore precedente, quindi ragioniamo subito in termini di strutture e visto che sappiamo che le strutture possono essere annidate definiamo il nostro cerchio in questo modo:
struct Circle {
float radius;
struct Point center;
};
e la funzione per calcolare la distanza può essere tranquillamente definita in questo modo:
float distance(struct Circle c1, struct Circle c2) {
float d;
d = distance(c1.center, c2.center);
return d;
}
Alzi la mano chi voleva inserire nuovamente il calcolo dentro questa funzione!
Meno 10 punti !!
Mai scrivere lo stesso codice due volte!
Aspetti Comuni “.” o “->”
Le union e le struct hanno molto in comune, specie nell’accesso ai loro membri interni. Negli esempi che ho proposto ho sempre utilizzato l’operatore “.” ma esiste anche un secondo metodo che prevede l’utilizzo dell’operatore “->”.
La differnza è che il punto si utilizza quando abbiamo dichiarato una variabile di tipo struct o union, mentre “->” si utilizza quando abbiamo dichiarato un puntatore ad una struttura o union.
Ricordando quanto abbiamo detto per i puntatori e le funzioni, scriviamo una funzione che serva per “spostare” un cerchio.
void moveCircle(struct Circle *c) {
c->center.x = c->center.x + 10;
}
che possiamo richiamare tramite:
struct Circle c1;
c1.radius = 5;
c1.center.x = 2;
c1.center.y = 5;
printf("Il cerchio è centrato nel punto: %f %f\n",c1.center.x, c1.center.y);
moveCircle(&c1);
printf("Il cerchio è centrato nel punto: %f %f\n",c1.center.x, c1.center.y);
typedef
Un aspetto che riguarda anche le enum è invece la possibilità di evitare di dover ogni volta scrivere enum/union/struct nel dichiarare le nostre variabili.
Ad esempio dichiarando la struttura “Circle”, quando vogliamo un tipo di dato di tipo “Circle” siamo costretti a scrivere:
struct Circle c1;
Questo si può ovviare con il costrutto typedef, che permette di creare un vero e proprio tipo di dato a tutti gli effetti. Scrivendo quindi:
typedef struct Circle {
float radius;
struct Point center;
};
possiamo dichiarare le nostre nuove variabili semplicemente scrivendo:
Circle c1;
//così come si fa con
int j;
Dichiarazione automatica
In alcuni casi potremmo incontrare una dichiarazione di struct/enum/union leggemente diversa da quella che ho dato io, ad esempio potremmo trovare:
typedef struct Circle {
float radius;
struct Point center;
} circle;
In questo caso quello che avviene è che stiamo dichiarando una variabile di tipo “Circle” direttamente durante la definizione del tipo. Io non amo molto questo modo di fare, soprattutto perché la definizione dei tipi la faccio con scope globale e se dichiarassi una variabile in quel punto questa sarebbe una variabile globale, che cerco di evitare quanto più possibile.
Alla prossima!!
Letture consigliate:
Il linguaggio C. Principi di programmazione e manuale di riferimento (Accademica)
Brian W. Kernighan – Dennis M. Ritchie
Editore: Pearson | Lingua: Italiano | Brossura: 313 pagine
Prezzo Listino: EUR 27,00
Prezzo Promozione: EUR 22,95 con Spedizione gratuita
C. Corso completo di programmazione
Paul J. Deitel – Harvey M. Deitel
Editore: Apogeo | Lingua: Italiano | Brossura: 640 pagine
Prezzo Listino: EUR 39,00
Prezzo Promozione: EUR 33,15 con Spedizione gratuita










9 Responses to “15. Costrutti enum, union e struct”
4 Giugno 2011
peppesono uno dei tanti supestiti
4 Giugno 2011
ignaziocdai, non demordete!!
5 Giugno 2011
EnricoWow che roba! La difficoltà aumenta esponenzialmente!
Grazie infinite anche se lo ripeto a tutte le lezioni prof:-)
17 Giugno 2011
MassimoComplimenti!! Io studio a ingegneria elettronica ma ho fatto molto prima a ripassarmi tutto qua che sulle dispense!! Spiegato tutto davvero in modo ottimo. Devo ammettere che avevo pochi dubbi su queste cose ma quei pochi sono stati risolti splendidamente 😉
18 Giugno 2011
SpartanMa è già finito il corso ?
21 Giugno 2011
peppequando continui con la prossima lezione???????
27 Giugno 2011
MabSi è interrotto il corso?
27 Giugno 2011
ignaziocno, no ho pubblicato un post a riguardo…
5 Luglio 2011
giuseppepuoi spiegare qui come mai non continua il corso?????????non ho trovato il post che hai pubblicato.ciao e grazie.