Stazione di monitoraggio 4 - Soluzione

La prima cosa da fare è realizzare la struttura che ospiterà i dati delle singole righe. Un esempio è il seguente:

struct misura {
    int aa, MM, gg;             // data
    int hh, mm, ss, ms;         // orario
    char id[MAX_ID_LUNG + 1];   // identificativo del dispositivo
    float temp;                 // temperatura
    int umid;                   // umidità
    float vel;                  // velocità del vento
};

NOTA: il nome dei campi è abbreviato per velocizzare la scrittura del codice. Ciononostante, gli identificatori utilizzati sono sufficientemente esplicativi da permetterne una immediata comprensione.

La parte più interessante di questo problema è la funzione che si occupa della lettura del file. Un esempio di funzione di lettura è la seguente:

struct misura *leggi_file(FILE *f, int *n)
{
    struct misura *m, *elenco, *tmp_ptr;
    char buf[1000];
    int nconv;        // numero di elementi convertiti
    int dim;

    /* inizializza a zero il numero di strutture caricate */
    *n = 0;
    /* dimensionamento e allocazione iniziale del vettore */
    dim = 4;
    elenco = malloc(dim * sizeof(*elenco));

    /* ad ogni iterazione, legge una riga fino alla fine del file */
    while (fgets(buf, sizeof(buf), f)) {
        /* punta all'elemento corrente da memorizzare */
        m = elenco + (*n);
        nconv = sscanf(buf, "%d-%d-%d %d:%d:%d.%d %s %f %d%% %f",
            &(m->aa), &(m->MM), &(m->gg),
            &(m->hh), &(m->mm), &(m->ss), &(m->ms),
            (m->id), &(m->temp), &(m->umid), &(m->vel));
        /* passa alla riga successiva se non ci sono esattamente 11
         * elementi da convertire */
        if (nconv != 11) continue;
        /* se si arriva qui, la riga è stata letta e memorizzata
         * correttamente; incremento il numero di righe lette */
        (*n)++;
        /* verifica se il vettore è riempito */
        if ((*n) >= dim) {
            /* raddoppia il valore della variabile */
            dim *= 2;
            /* ri-alloca lo spazio per il vettore */
            tmp_ptr = realloc(elenco, dim * sizeof(*elenco));
            /* controlla che la ri-allocazione abbia avuto successo */
            if (tmp_ptr == NULL) return NULL;
            /* se è tutto a posto, assegna il puntatore */
            elenco = tmp_ptr;
        }
    }
    /* riduce la dimensione del vettore fissandola pari a *n */
    elenco = realloc(elenco, (*n) * sizeof(*elenco));
    /* restituisce il vettore con i dati letti */
    return elenco;
}

Non c'è da spaventarsi della lunghezza, in quanto buona parte del codice è costituito da commenti. I commenti dovrebbero essere sufficienti per capire la logica del programma.

La funzione restituisce un puntatore al vettore allocato all'interno della funzione.

I parametri sono due:

  • il file f, già aperto in lettura prima di chiamare la leggi_file;
  • il puntatore all'intero n che serve a restituire il numero di righe lette tramite il passaggio di parametri per indirizzo.

Il puntatore elenco, all'interno della funzione leggi_file, viene assegnato tramite la malloc, che alloca una quantità di memoria pari a dim elementi di tipo struct misura. Il valore di dim è inizialmente pari a 4.

Il ciclo while legge dal file una riga ad ogni iterazione tramite la fgets.

Per quanto riguarda la condizione del ciclo while, ricorda che la fgets restituisce l'indirizzo del puntatore usato per contenere la stringa letta da file (quindi, in ogni caso, un puntatore diverso da NULL). Se non ci sono altre righe da leggere, la fgets restituisce NULL. Dal momento che la costante NULL equivale al numero 0, la condizione del ciclo while diventa 0 (cioè falsa) quando la lettura del file è terminata. E di conseguenza il ciclo while termina a sua volta.

Con la sscanf vengono estratti i valori dalla riga letta e assegnati ai vari campi della struttura corrente nel vettore.

La parte più interessante è il ridimensionamento del vettore in caso siano stati lette un numero di righe pari a dim. In tal caso il valore di dim viene raddoppiato con l'istruzione dim *= 2 (vedi S11.3.2 per la descrizione delle forme abbreviate di assegnamento). Dopodiché la realloc cambia la dimensione del vettore utilizzando la dimensione raddoppiata, assegnando l'indirizzo ottenuto a tmp_ptr. Se quest'ultimo vale NULL allora l'allocazione non ha avuto successo, e la funzione termina restituendo NULL, altrimenti si assegna tmp_ptr ad elenco e si passa a leggere la riga successiva.

Stampa invertita degli identificativi

La funzione che stampa gli identificativi in senso inverso è molto semplice e, ad esempio, può essere implementata come segue:

void stampa_identificativi(struct misura *elenco, int n)
{
    int i;
    for (i = n - 1; i >= 0; i--)
        printf("%s\n", (elenco + i)->id);
}

Essa riceve come argomenti il vettore delle strutture caricate da file e il numero di elementi nel vettore.

Effettua un ciclo for con contatore che parte dall'ultimo elemento del vettore (indice n - 1) e decrementa il contatore fino ad indirizzare l'elemento di indice 0 del vettore.