Lezione 14: Tipi Aggregati e Selezione
Soluzioni di Esercizi
Esercizio 13.1 Scrivere una funzione int **alloctrimatrix(int n, int val)
che ritorna una matrice triangolare di interi, allocata dinamicamente, riempita con il valore val.
#include <stdlib.h>
/* Ritorna una matrice triangolare con n righe, allocata dinamicamente,
* riempita con il valore val. */
int **alloctrimatrix(int n, int val) {
int **T = malloc(n*sizeof(int *));
for (int i = 0 ; i < n ; i++) {
T[i] = malloc((n - i)*sizeof(int));
for (int j = 0 ; j < n - i ; j++)
T[i][j] = val;
}
return T;
}
Esercizio 13.2 Scrivere una funzione void freetrimatrix(int **M, int n)
che disalloca la matrice M
, precedentemente allocata tramite la funzione alloctrimatrix()
.
/* Libera la memoria di una matrice M con n righe precedentemente
* allocata tramite alloctrimatrix(). */
void freetrimatrix(int **M, int n) {
for (int i = 0 ; i < n ; i++)
free(M[i]);
free(M);
}
Tipi Aggregati
Finora, ci siamo occupati di tipi semplici, definiti da un valore singolo. Possiamo dimostrare che questi tipi sono sufficienti per scrivere tutti programmi possibili. Allo stesso tempo, sarebbe conveniente avere variabili che contengono una collezione di informazioni che sono in relazione tra l'un l'altra. In C, possiamo estendere i tipi definiti dal linguaggio con tipi aggregati definiti da una collezione di campi, usando la sintassi:
struct <nome_tipo> {
<tipo_campo_1> <nome_campo_1>;
<tipo_campo_2> <nome_campo_2>;
...
<tipo_campo_n> <nome_campo_k>;
};
Questa dichiarazione, definisce un nuovo tipo struct <nome_tipo>
. Questi tipi possono essere utilizzati per memorizzare una varieta' di tipi diversi, come ad esempio i dati relativi ad un "articolo" o una "persona":
struct Articolo {
int codice;
float prezzo;
char categoria[10];
};
struct Persona {
char nome[20];
char cognome[40];
char codice_fiscale[16];
};
Le variabili di tipi aggregati sono dichiarate usando la sintassi:
struct <nome_tipo> <nome_variabile>;
e possono essere inizializzate usando una sintassi simile agli array
struct <nome_tipo> <nome_variabile> =
{ <valore_campo_1>, <valore_campo_2>, ..., <valore_campo_k> };
Sulle variabili di tipi aggregati il C definisce l'operatore di assegnamento, che copia tutti i valori della struttura, e l'operatore di uguaglianza, che ritorna vero se tutti i campi sono uguali. Si accede ai campi di un tipo aggregato usando l'operatore .
, con la sintassi
<nome_variabile>.<nome_campo_i>
Per i tipi aggregati sizeof(<tipo>)
e' sempre uguale o superiore alla somma delle dimensione dei campi. (Ci si puo' chiedere perche' non e' uguale, e la spiegazione piu' semplice e' che ci sono limitazioni/ottimizzazione in hardware per variabili di dimensione multiple di 4, 8 o 16 bytes).
Infine, si possono dichiarare puntatori a struct
con la stessa sintassi introdotta in precedenza. Per accedere ad elementi di un tipo aggregato usandone un puntatore, si puo' deferenziare il puntatore o usare l'operatore ->
come
<variabile_puntatore_struct>-><nome_campo>
Esempi
Ecco un esempio semplice dell'uso di struct
.
#include <stdio.h>
struct Carta {
char seme; //cuori 'c', quadri 'q', fiori 'f', picche 'p'
int valore; //1,2,3,4,5,6,7,8,9,10,11,12,13
};
int main() {
struct Carta c = {'c', 3}; //tre di cuori
c.seme = 'p';
c.valore = 10; //dieci di picche
struct Carta c2 = c; //assegnamento di struct
printf("seme %c valore %d\n", c2.seme, c2.valore);
}
Sinonimi di Tipi
Il C permette di definire sinonimi per tipi, cioe' nome ausiliari di tipi. In generale si usano questi sinonimi per dare un significato piu' concreto ai tipi, quindi migliorare la leggibilita' dei programmmi, o per evitare l'uso delle parole struct
. La sintassi per i sinonimi dei tipi e' quella per dichiarare una variabile, ma preceduta da typedef
:
typedef <tipo> <nome>;
Ad esempio:
typedef char *Stringa;
typedef char CodiceFiscale[16]; // CodiceFiscale è array di 16 char
typedef struct Carta Carta;
Carta c; // equivale a struct Carta c;
Esempi
Ecco un programma piu' complesso che include l'uso di struct
e typedef
.
#include <stdio.h>
#include <stdlib.h>
// rappresenta una carta da gioco
typedef struct Carta {
char seme;
int valore;
} Carta;
// numero carte in un mazzo
#define NUMCARTE 52
// tipo di un mazzo di carte
typedef Carta Mazzo[NUMCARTE];
// inizializza il mazzo di carte M
void inizmazzo(Mazzo M) {
char semi[4] = {'c','q','f','p'};
for (int i = 0 ; i < NUMCARTE ; i++) {
M[i].seme = semi[i % 4];
M[i].valore = 1 + (i % 13);
}
}
// stampa il mazzo di carte M
void stampamazzo(Mazzo M) {
for (int i = 0 ; i < NUMCARTE ; i++) {
printf("%2d%c ", M[i].valore, M[i].seme);
if ((i % 13) == 12) printf("\n");
}
}
// scambia le carte c1 e c2
void scambiacarte(Carta *c1, Carta *c2) {
Carta c = *c1;
*c1 = *c2;
*c2 = c;
}
// numero scambi per mescolare un mazzo di carte
#define NUMSCAMBI 100
// Mescola il mazzo M effettuando NUMSCAMBI scambi di carte "casuali"
void mescolamazzo(Mazzo M) {
for (int k = 0 ; k < NUMSCAMBI ; k++) {
int i = rand() % NUMCARTE;
int j = rand() % NUMCARTE;
scambiacarte(&M[i], &M[j]);
}
}
int main() {
Mazzo M;
inizmazzo(M);
printf("Mazzo iniziale:\n");
stampamazzo(M);
mescolamazzo(M);
printf("Mazzo mescolato:\n");
stampamazzo(M);
}
Istruzione di Selezione
Ci sono casi in cui si deve determinare se una espressione ha un valore fra una (lunga) lista di possibilita'. Questo si puo' scrivere usando istruzione di selezione con la sintassi
if(<espressione> == <valore_1>) {
<istruzioni_1>;
} else if (<espressione> == <valore_2>) {
<istruzioni_2>;
}
...
} else if (<espressione> == <valore_k>) {
<istruzioni_k>;
} else {
<istruzioni_default>;
}
Considerando la notevole quantita' di ripetizioni nella sintassi precedente, il C definisce una sintassi "scorciatoia" per questi casi:
switch(<espressione>) {
case <valore_1>:
<istruzioni_1>;
break;
case <valore_2>):
<istruzioni_2>;
break;
...
case <valore_k>):
<istruzioni_k>;
break;
default:
<istruzioni_default>;
}
Ad esempio:
//stampa il mazzo di carte M (versione alternativa)
void stampamazzo(Mazzo M) {
for (int i = 0 ; i < NUMCARTE ; i++) {
switch (M[i].valore) {
case 2: case 3: case 4: case 5: case 6:
case 7: case 8: case 9: case 10:
printf("%2d", M[i].valore);
break;
case 1: printf(" A"); break;
case 11: printf(" J"); break;
case 12: printf(" Q"); break;
case 13: printf(" K"); break;
}
printf(" di ");
switch (M[i].seme) {
case 'c': printf("cuori"); break;
case 'q': printf("quadri"); break;
case 'f': printf("fiori"); break;
case 'p': printf("picche"); break;
}
printf("\n");
}
}
Esercizio 14.1 Scrivere una funzione int controlla(Mazzo M)
che ritorna vero se e solo se il mazzo M
contiene tutte le carte.
Esercizio 14.2 Definire una struct Studente
con campi: matricola
, nome
, cognome
, num_esami
. Scrivere una funzione int cerca(Studente stu[], int n, char *c)
che ritorna l'indice dello studente con cognome dato dalla stringa c
nell'array di Studente
di dimensione n
. Se non c'è nessun studente con quel cognome, ritorna -1.