Lezione 16
Discussione esercizio 28: Per implementare la funzione input_dipendente() si possono usare delle funzioni (ad es. per leggere una linea dallo standard input) che sono di applicabilità più generale e che conviene raggruppare nel modulo delle funzioni di utilità. Ecco quindi l'interfaccia delle funzioni di utilità:
/******************************** FILE util.h *********************************/
#ifndef _UTIL_H
#define _UTIL_H

/* Stampa la stringa prompt (se non NULL) e legge dallo standard input una linea 
 * di caratteri memorizzandola nell'array line. I caratteri oltre i primi maxlen 
 * sono letti e ignorati. L'array line deve avere dimensione >= maxlen + 1 
 * perche' dopo l'ultimo carattere viene inserito il carattere di fine stringa 
 * ('\0'). Ritorna il numero di caratteri letti. */
int input_line(char *prompt, char line[], int maxlen);

/* Legge dallo standard input un int e lo ritorna. Tutti i caratteri dopo le
 * cifre dell'intero fino al newline compreso, sono letti e ignorati. */
int input_int();

/* Legge dallo standard input un char e lo ritorna. Tutti i caratteri dopo il
 * primo fino al newline compreso, sono letti e ignorati. */
char input_char();

#endif  /* _UTIL_H */
L'implementazione (nel file util.c) è lasciata come esercizio. Adesso possiamo implementare la funzione input_dipendente():
#include <stdio.h>
#include "dipendente.h"
#include "util.h"

/* Chiede all'utente di inserire i dati di un dipendente e ritorna una struct 
 * Dipendente riempita con tali dati. */
Dipendente input_dipendente() {
    Dipendente nuovo;
    input_line("Nome: ", nuovo.nome, MAXL_NOM);
    input_line("Cognome: ", nuovo.cognome, MAXL_COG);
    printf("Nascita (g/m/a): ");
    Data *dn = &(nuovo.nascita);
    scanf("%d/%d/%d", &(dn->g), &(dn->m), &(dn->a));
    printf("Ruolo (%d = IMP, %d = DIR, %d = DIRSUP): ", IMP, DIR, DIRSUP);
    switch (input_int()) {
        case IMP: nuovo.ruolo = IMP; break;
        case DIR: nuovo.ruolo = DIR; break;
        case DIRSUP: nuovo.ruolo = DIRSUP; break;
        default: nuovo.ruolo = IMP;
    }
    switch (nuovo.ruolo) {
        case IMP:
            printf("Livello: ");
            nuovo.info.liv = input_int();
            break;
        case DIR:
            input_line("Sezione: ", nuovo.info.sez, MAXL_SEZ);
            break;
        case DIRSUP:
            input_line("Dipartimento: ", nuovo.info.dip, MAXL_DIP);
            break;
    }
    return nuovo;
}

Discussione esercizio 29:
#include <stdio.h>
#include "dipendente.h"

/* Stampa i dati del dipendente d */
void stampadip(Dipendente d) {
    printf("Nome: %s  Cognome: %s\n", d.nome, d.cognome);
    printf("Data di nascita: %d/%d/%d\n", d.nascita.g, d.nascita.m, d.nascita.a);
    switch (d.ruolo) {
        case IMP:
            printf("Impiegato - livello: %d\n", d.info.liv);
            break;
        case DIR:
            printf("Dirigente - sezione: %s\n", d.info.sez);
            break;
        case DIRSUP:
            printf("Dirigente sup. - dipartimento: %s\n", d.info.dip);
            break;
    }
}

Passiamo ora all'implementazione del modulo che gestisce l'interazione con l'utente, usando anche le funzioni appena definite:
/**************************** FILE archivio_main.c ****************************/
#include "util.h"
#include <stdio.h>
#include <ctype.h>

void inizio();      //predispone l'archivio
void aggiungi();    //aggiunge un nuovo dipendente
void stampa();      //stampa tutti i dipendenti
void ricerca();     //ricerca un dipendente
void fine();        //conclude le operazioni sull'archivio

int main() {
    inizio();
    int quit = 0;
    do {
        printf("A) Aggiungi\n");
        printf("S) Stampa\n");
        printf("R) Ricerca\n");
        printf("F) Fine\n");
        switch (toupper(input_char())) {
            case 'A': aggiungi(); break;
            case 'S': stampa(); break;
            case 'R': ricerca(); break;
            case 'F': quit = 1; break;
            default: printf("Scelta non valida\n");
        }
    } while (!quit);
    fine();
}

#include "archivio_mem.h"
#include <stdlib.h>
#include <string.h>

Dipendente input_dipendente() {...}

void stampadip(Dipendente d) {...}

void inizio() {
    printf("ARCHIVIO DIPENDENTI\n\n");
    open_archivio();
}

void aggiungi() {
    add_dipendente(input_dipendente());
}

void stampa() {
    int i, nd = num_dipendenti();
    printf("Numero dipendenti: %d\n", nd);
    for (i = 0 ; i < nd ; i++) {
        printf("------------------------------------\n");
        stampadip(get_dipendente(i));
    }
    printf("------------------------------------\n");
}

void ricerca() {
    char cognome[MAXL_COG+1];
    input_line("Cognome da ricercare: ", cognome, MAXL_COG);
    int i = 0, nd = num_dipendenti();
    while (i < nd) {
        Dipendente d = get_dipendente(i);
        if (strcmp(cognome, d.cognome) == 0) {
            stampadip(d);
            return;
        }
        i++;
    }
    printf("Non trovato\n");
}

void fine() {
    close_archivio();
    printf("ARCHIVIO CHIUSO\n");
}
Implementazione del modulo di memorizzazione (file archivio_mem.c) tramite array allocato dinamicamente.
/**************************** FILE archivio_mem.c *****************************/
/* Implementazione in memoria primaria tramite array allocato dinamicamente */
#include "archivio_mem.h"
#include <stdlib.h>


static Dipendente *archivio = NULL;
static int numDipendenti = 0;


void open_archivio() {}

void add_dipendente(Dipendente d) {
    archivio = realloc(archivio, (numDipendenti + 1)*sizeof(Dipendente));
    archivio[numDipendenti++] = d;
}

int num_dipendenti() {
    return numDipendenti;
}

Dipendente get_dipendente(int i) {
    return archivio[i];
}

void close_archivio() {
    if (archivio != NULL) {
        free(archivio);
        archivio = NULL;
        numDipendenti = 0;
    }
}
Discussione circa l'uso di  Variabili globali . Il qualificatore  static  per mantenere locale la definizione di una variabile o di una funzione.

Le  liste : struttura dati dinamica, utile per gestire insiemi (o sequenze) soggetti a molti inserimenti ed eliminazioni di elementi. In alcuni casi permette uno sfruttamento più efficace della memoria rispetto agli array: un array richiede un blocco di memoria contigua che se è molto grande e la memoria è frammentata potrebbe non essere disponibile, mentre una lista per gli stessi elementi richiede tanti blocchi piccoli che potrebbero esserci anche se la memoria è molto frammentata. Rappresentazione in memoria:
     ---------       ---------       ---------       ---------             --------- 
    |     |   |     |     |   |     |     |   |     |     |   |           |     |   |
    |     | •------>|     | •------>|     | •------>|     | •------> . . .|     | • |
    |     |   |     |     |   |     |     |   |     |     |   |           |     |   |
     ---------       ---------       ---------       ---------             ---------
L'accesso agli elementi in un array è molto più veloce che in una lista.
Operazioni fondamentali sulle liste, relativamente a liste di interi:
#include <stdlib.h>

typedef struct Elem {
    int          val;
    struct Elem *next;    //puntatore al prossimo elemento della lista o NULL
} Elem, *List;

/* Aggiunge un nuovo elemento con valore val in testa alla lista L (L è il puntatore 
 * al primo elemento della lista) e ritorna il puntatore alla lista modificata. */
List add_head(List L, int val) {
    Elem *newelem = malloc(sizeof(Elem));
    newelem->val = val;
    newelem->next = L;
    return newelem;
}

 /* Aggiunge un nuovo elemento con valore val in coda alla lista L e ritorna il 
  * puntatore alla lista così modificata. */
List add_tail(List L, int val) {
    Elem *newelem = malloc(sizeof(Elem));
    newelem->val = val;
    newelem->next = NULL;
    if (L == NULL) return newelem;
    Elem *e = L;
    while (e->next != NULL)
        e = e->next;
    e->next = newelem;
    return L;
}

/* Cerca nella lista L il primo elemento con valore val, se lo trova ritorna il 
 * puntatore all'elemento, altrimenti ritorna NULL. */
Elem *find(List L, int val) {
    Elem *e = L;
    while (e != NULL && e->val != val)
        e = e->next;
    return e;
}

/* Rimuove dalla lista L, se presente, il primo elemento con valore val e ritorna 
 * il puntatore alla lista così modificata. */
List remove(List L, int val) {
    if (L == NULL) return NULL;
    if (L->val == val) {
        List newHead = L->next;
        free(L);
        return newHead;
    }
    Elem *prec = L;
    while (prec->next != NULL && (prec->next)->val != val)
        prec = prec->next;
    if (prec->next != NULL) {
        Elem *del = prec->next;
        prec->next = (prec->next)->next;
        free(del);
    }
    return L;
}
Esercizio 30    Scrivere una funzione List insord(List L, int val) che inserisce un nuovo elemento con valore val nella lista ordinata (in senso crescente) L, mantenendo l'ordinamento. Ritorna il puntatore alla lista dopo l'inserimento. Ad es., se L è la lista 1 -> 3 -> 6 -> 8 e val = 5 allora la funzione modifica la lista così 1 -> 3 -> 5 -> 6 -> 8.
Esercizio 31    Scrivere una funzione List reverse(List L) che inverte la lista L e ritorna il puntatore alla lista invertita. Ad es., se L è la lista 1 -> 2 -> 3 -> 4 allora la funzione la trasforma così 4 -> 3 -> 2 -> 1.
Esercizio 32    Implementare il modulo di memorizzazione (file archivio_mem.c) tramite liste.