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.