Traccia della lezione Lez10: Lucido 4: Spezzare la dichiarazione è particolarmente utile quando vi sono più variabili dello stesso tipo. Altrimenti, per ognuna bisognerebbe riportare l'intera pappardella (con tutti i pericoli di errore e la scarsa modularità e modificabilità che ne conseguono). Inoltre, vedremo che è possibile copiare una variabile di tipo struttura in un'altra con un assegnamento, ma solo se le due variabili sono state definite con un tipo definito dall'utente. Se sono state definite esplicitamente, il compilatore non è in grado di confrontare le due definizioni e rendersi conto che sono identiche (si veda più avanti il commento sui codici associati al lucido 7). Lucido 5: Si noti che entrambi i modi per definire tipi struttura terminano con ; perché sono istruzioni: dicono al compilatore di annotare l'esistenza di un nuovo nome di tipo. Nel caso dell'istruzione typedef questo è semplice da ricordare, nel caso del tag di struttura è facile dimenticarlo perché non c'è nessuna "istruzione" visibile. Lucido 6: Con "locali al blocco dove sono dichiarati" si intende che per leggere o scrivere un campo dovremo indicare sia il nome del campo sia quello della variabile che lo contiene, e quindi sarà possibile usare nomi uguali per campi di variabili di tipo diverso, dato che ci penserà il nome della variabile a chiarire a che cosa ci stiamo riferendo. Ovviamente, campi con lo stesso nome di strutture diverse possono essere anche di tipo diverso. Lucido 7: Anche le parentesi quadre usate per accedere agli elementi di un vettore sono un operatore ad altissima priorità. Un oggetto che può "apparire a sinistra in un assegnamento" si dice tecnicamente "l-value" (da "left-side value"). Sono "l-value" le variabili, gli elementi dei vettori, i campi delle strutture. Ne vedremo altri. Il codice STRUCT1.C illustra l'impossibilità pratica di usare la definizione base di struttura, senza typedef o tag di struttura: la funzione StampaMeteo(), che ha un parametro di tipo struttura, produce: - un messaggio di avvertimento nel prototipo (structure defined inside parms \ anonymous struct declared inside parameter list \ its scope is only this definition or declaration, which is probably not what you want) che segnala il fatto che tale dichiarazione non è visibile fuori dalla funzione stessa, e quindi non coincide con la dichiarazione dei dati che vengono passati alla funzione - un messaggio di avvertimento nella definizione della funzione, con lo stesso scopo - un messaggio di errore nella chiamata alla funzione (incompatible type for argument 1 of `StampaMeteo'), per indicare che "meteo_oggi" non è riconosciuto come struttura dello stesso tipo dei dati di StampaMeteo(), anche se la definizione è identica lettera per lettera. Il compilatore non è in grado di riconoscere questa identità. - un messaggio di errore nell'assegnamento di "meteo_ieri" a "meteo_oggi" per indicare che le due variabili non sono riconosciute dello stesso tipo (lo sarebbero solo se le avessimo dichiarate insieme, separate da una virgola). Il compilatore infatti non è in grado di riconoscere questa identità. - un messaggio di errore (conflicting types) nella definizione di StampaMeteo, che risulterebbe avere dati di tipo diverso rispetto al prototipo. Il compilatore infatti non è in grado di riconoscere la loro identità. Il codice STRUCT2.C illustra la soluzione facendo uso di typedef. Si noti che il typedef sta prima dei prototipi, fuori dal main. In effetti, l'istruzione typedef potrebbe stare nel main, ma allora il nome di tipo "dati_meteo" sarebbe locale al main, cioè utilizzabile solo al suo interno e non si potrebbe usare nella funzione StampaMeteo, che è fuori dal main. Esercizio: si costruisca la soluzione che fa uso del tag di struttura (vedi STRUCT3-SOL.C). Lucido 8: "Costruire funzioni che operano su strutture combinando funzioni che operano su sottostrutture" significa che, una volta realizzate delle funzioni che manipolano oggetti di tipo "persona", è possibile usarle per costruire funzioni che manipolino oggetti di tipo "studente", senza dover riscrivere tutto da zero. Per esempio, si può costruire una funzione StampaPersona: void StampaPersona (persona p) { StampaStringa(p.nome); StampaStringa(" "); StampaStringa(p.cognome); } e poi usarla per costruire una funzione StampaStudente: void StampaStudente (studente s) { StampaPersona(s.identita); StampaStringa(" "); StampaIntero(s.matricola) ; } Lucido 9: Per chiarirsi gli esempi, conviene disegnare a parte un vettore "classe" di 100+1 elementi di tipo "studente". Il tipo "studente" descrive una struttura composta da una matricola (long) e un'identità di tipo "persona", che a sua volta è una struttura, composta da due vettori statici di caratteri, rispettivamente "nome" e "cognome". Si osservi che, avendo definito la struttura "studente" con l'istruzione typedef, si può usare "studente" come un tipo. Usando il tag di struttura, si sarebbe dovuto usare "struct studente". Esempio 1: data una variabile "studente1" di tipo "studente", si vuole accedere al nome, cioè al campo "nome" della sottostruttura "identita" della struttura "studente1". Il punto ha associatività da sinistra a destra, per cui si entra ordinatamente passando da una struttura a una sottostruttura. Esempio 2: data la variabile "studente1" di tipo "studente", si vuole costruirne le iniziali. Quindi si vuole accedere agli elementi in posizione 0 del campo "nome" e del campo "cognome" della sottostruttura "identita" della struttura "studente1". Punto e parentesi quadre hanno uguale priorità: l'associatività indica di procedere da sinistra a destra. Esempio 3: data la variabile classe di tipo vettore di studente (con 100+1 elementi, da 0 a 100), si vuole accedere alla matricola dello studente di indice 12: campo "matricola" della struttura "studente" dell'elemento di indice 12 del vettore "classe". Punto e parentesi quadre hanno uguale priorità: l'associatività indica di procedere da sinistra a destra. Si disegnino gli alberi di valutazione delle espressioni, in base alle precedenze fra operatori. Si ricordi che l'associatività è da sinistra a destra, per cui i punti più a sinistra si valutano prima e stanno più vicini alle foglie dell'albero. Questo consente di passare ordinatamente da una struttura alle sue sottostrutture. Lucido 10: L'assegnamento può essere applicato solo a strutture dello stesso tipo. Come anticipato, strutture con definizioni separate, anche se identiche lettera per lettera, non sono dello stesso tipo. Anche per questo conviene adottare un nome simbolico per la struttura con l'istruzione "typedef". Esempio: le variabili data1 e data2, i cui tipi sono definiti separatamente struct { int giorno; int mese; } data1; struct { int giorno; int mese; } data2; non si possono assegnare una all'altra (data1 = data2), perché sono di tipi diversi, anche se hanno definizioni identiche. Per renderle assegnabili, si possono dichiarare insieme struct { int giorno; int mese; } data1, data2; o meglio si può usare una definizione di tipo con i tag di struttura struct data { int giorno; int mese; }; struct data data1; struct data data2; oppure con typedef typedef struct { int giorno; int mese; } data; data data1; data data2; Si presti moltissima attenzione all'assegnamento di strutture a strutture. - per i campi elementari non ci sono problemi - per quelli di tipo struttura si applica l'operatore ricorsivamente - per i campi che siano vettori statici (dichiarati con una lunghezza predefinita), l'assegnamento fa la copia corretta. Questo è inatteso, dato che i vettori non si possono assegnare direttamente. Eppure succede. - per le strutture a puntatori (che non abbiamo ancora visto), la cosa diventa addirittura pericolosa, perché l'assegnamento copia i puntatori e non gli oggetti puntati, per cui si creano strutture diverse che puntano lo stesso oggetto. Non si possono usare gli operatori di confronto ("==", "!=") fra strutture.