Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

[C] Conferme esercizio su vettori

Linguaggi e sistemi

Moderatori: Foto UtentePaolino, Foto Utentefairyvilje

0
voti

[41] Re: [C] Conferme esercizio su vettori

Messaggioda Foto Utentetecfil » 31 ago 2013, 22:53

Ciao Foto UtenteDirtyDeeds

Complimenti! :ok: :mrgreen: sei una fonte infinita per quanto riguarda la programmazione in C :mrgreen:

Grazie
Ciaoo :)
Il colmo per un elettricista? Essere isolato :D
Avatar utente
Foto Utentetecfil
327 1 5 6
Stabilizzato
Stabilizzato
 
Messaggi: 440
Iscritto il: 2 lug 2013, 21:41

3
voti

[42] Re: [C] Conferme esercizio su vettori

Messaggioda Foto UtenteDirtyDeeds » 1 set 2013, 10:17

Alura, il programmino in [40] non gestisce nessun errore. In particolare se uno immette una stringa che non sia un numero, quello che capita è che la scanf non converte nessun campo e tutti i caratteri immessi rimangono nel buffer di stdin. Rimanendo in stdin, vengono riletti ogni volta dalle successive scanf e il programma termina senza che sia stato memorizzato un solo numero. Per evitare ciò, dobbiamo controllare che la scanf abbia effettivamente letto un numero, verificando che il valore di ritorno sia 1.

All'interno del ciclo for, quindi, bisogna mettere un altro ciclo che continui a richiedere lo stesso elemento fino a che non viene effettivamente immesso un numero. Ecco allora un possibile codice, che si preoccupa anche di controllare che il numero inserito sia compreso tra VAL_MIN e VAL_MAX:

Codice: Seleziona tutto
// getbuf.c
#include <stdio.h>

#define BUF_SZ 5
#define VAL_MIN -100
#define VAL_MAX 100

int main()
{
    int buf[BUF_SZ] = {0};

    for (size_t i = 0; i < BUF_SZ; ++i) {
        int nr = 1; // Numero di campi letti da scanf: ok quando nr == 1
        do {
            printf("buf[%zd] = ", i);
            fflush(stdout);
            if (nr < 1) // Se nr < 1 elimina i caratteri rimasti in stdin
                scanf("%*[^\n] ");
        } while ((nr = scanf("%d", buf+i)) < 1 || buf[i] < VAL_MIN || buf[i] > VAL_MAX);
    }

    return 0;
}


Commenti:
1) Se una lettura non va a buon fine, perché per esempio uno ha scritto "Ciao!", prima di una nuova lettura bisogna eliminare i caratteri precedentemente immessi. Questo è fatto dall'istruzione:

Codice: Seleziona tutto
scanf("%*[^\n] ");


La sequenza "[^\n]" specifica un negated scanset che dice alla scanf di leggere tutti i caratteri diversi dal '\n': l'asterisco che precede tale specifica dice alla scanf che tali caratteri non vanno memorizzati. La scanf, quindi, si "ingoia" tutti i caratteri precedentemente immessi in stdin, tranne il '\n' finale. Quest'ultimo viene fatto rimuovere dallo spazio che c'è alla fine della stringa di formato "%*[^\n] " (si poteva anche scrivere "%*[^\n]\n" o "%*[^\n]\t").

2) Problema risolto? Non proprio: il programma sopra non si comporta ancora come richiesto dalle specifiche iniziali. In particolare:

  • Accetta più di un numero per ogni riga:
    Codice: Seleziona tutto
    >getbuf
    buf[0] = 1 2 3 4 5
    buf[1] = buf[2] = buf[3] = buf[4] =
  • Se vengono immessi numeri non interi il controllo degli errori non è particolarmente funzionale:
    Codice: Seleziona tutto
    >getbuf
    buf[0] = 75.36
    buf[1] = buf[1] =

    Cosa succede? La scanf prende 75 e lascia .36 in stdin, senza dare errore; quando viene richiesto buf[1], viene letto .36 che genera errore e attiva una nuova richiesta di buf[1]. Dal punto di vista dell'utente sarebbe meglio che 75.36 venisse scartato in toto.
  • Provate a salvare in un file in.txt solo tre numeri:
    Codice: Seleziona tutto
    1
    2
    3

    e poi a eseguire
    Codice: Seleziona tutto
    >getbuf < in.txt

    Il programma entra in loop nell'attesa di un quarto elemento che non arriverà mai. Non molto bello.
  • Supponiamo che VAL_MIN e VAL_MAX, invece di essere -100 e 100, fossero uguali al minimo e al massimo intero rappresentabile, INT_MIN e INT_MAX:
    Codice: Seleziona tutto
    // getbuf.c
    #include <stdio.h>
    #include <limits.h>

    #define BUF_SZ 5
    #define VAL_MIN INT_MIN
    #define VAL_MAX INT_MAX

    int main()
    {
        int buf[BUF_SZ] = {0};

        for (size_t i = 0; i < BUF_SZ; ++i) {
            int nr = 1; // Numero di campi letti da scanf: ok quando nr == 1
            do {
                printf("buf[%zd] = ", i);
                fflush(stdout);
                if (nr < 1) // Se nr < 1 elimina i caratteri rimasti in stdin
                    scanf("%*[^\n] ");
            } while ((nr = scanf("%d", buf+i)) < 1 || buf[i] < VAL_MIN || buf[i] > VAL_MAX);
        }

        return 0;
    }

    e proviamo ad immettere
    Codice: Seleziona tutto
    >getbuf
    buf[0] = 222222222222222222222222222222222222222222222222222222222222222222222222
    buf[1] =

    Come si vede dalla richiesta per buf[1], il programma ha accettato il numero 222222222222222222222222222222222222222222222222222222222222222222222222 senza colpo ferire, pur essendo un numero sicuramente maggiore in INT_MAX. Anche questo non è bello.

Alla prossima "puntata" cercheremo di rimediare anche a questi problemi. Per farlo, però, dovremo abbandonare la scanf.
It's a sin to write sin instead of \sin (Anonimo).
...'cos you know that cos ain't \cos, right?
You won't get a sexy tan if you write tan in lieu of \tan.
Take a log for a fireplace, but don't take log for \logarithm.
Avatar utente
Foto UtenteDirtyDeeds
55,9k 7 11 13
G.Master EY
G.Master EY
 
Messaggi: 7012
Iscritto il: 13 apr 2010, 16:13
Località: Somewhere in nowhere

3
voti

[43] Re: [C] Conferme esercizio su vettori

Messaggioda Foto UtenteDirtyDeeds » 1 set 2013, 20:17

Chiudiamo allora il discorso, cercando di produrre un codice sufficientemente resistente agli errori dell'utente. Come scritto nel messaggio precedente, dobbiamo abbandonare la scanf e trovare un'altra strategia: bisogna fare in modo che il programma legga l'input dell'utente per linee, cioè tutti i caratteri da lui immessi fino al '\n'; ma non solo: bisogna anche fare in modo che se si verifica un errore di input (p.es. viene raggiunta la fine del file), il programma si interrompa, anche avendo memorizzato un numero di valori inferiore alla dimensione del buffer.

Per la lettura per linee possiamo usare la funzione di libreria fgets:

Codice: Seleziona tutto
char *fgets(char * str, int num, FILE * stream);


Questa funzione legge al più num caratteri da stream (nel nostro caso stdin) e li memorizza nel buffer str, appendendo un carattere nullo; in caso di errore ritorna NULL.

Abbiamo bisogno allora di un buffer per la linea immessa e dobbiamo decidere come dimensionarlo. Un opzione, più complessa, potrebbe essere quella di realizzare un meccanismo con un buffer che si dimensioni automaticamente per la lunghezza della linea di ingresso, in modo da poter leggere anche linee molto lunghe. Questa soluzione è però abbastanza complessa da implementare (una soluzione già pronta ma non standard è la getline) e noi ci accontenteremo di una soluzione più semplice (e drastica): fissiamo la lunghezza del buffer di linea e tutti i caratteri oltre questa lunghezza massima li ignoriamo.

Una volta letta una linea, convertiamo tale linea in un intero usando la funzione strtol, che permette un buon controllo degli errori, e se tutto va a buon fine, compreso il controllo dei limiti, memorizziamo il risultato in buf.

Un codice che implementa questo discorso è il seguente:

Codice: Seleziona tutto
// getbuf.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define BUF_SZ 5
#define LINE_SZ 128
#define VAL_MIN -100
#define VAL_MAX 100

int main()
{
    int buf[BUF_SZ] = {0};
    char line[LINE_SZ] = {0};

    size_t n_stored = 0;
    while (n_stored < BUF_SZ &&
           (printf("buf[%zd] = ", n_stored),
            fflush(stdout),
            fgets(line, LINE_SZ, stdin))) {

        /* Rimuove il '\n' e tronca una linea eccedente LINE_SZ-1 caratteri */
        size_t line_len = strlen(line);
        if (line_len > 0) {
            if (line[line_len-1] == '\n')
                line[line_len-1] = 0;
            else {
                int c;
                while ((c = getchar()) != EOF && c != '\n')
                    ;
            }
        }

        /* Converte la linea letta in un intero */
        /* In caso di errori, non aggiorna buf e torna indietro */
        char *endp;
        errno = 0;
        long val = strtol(line, &endp, 10);
        if (*endp ||
            endp == line ||
            errno == ERANGE ||
            val < VAL_MIN ||
            val > VAL_MAX)
            continue;

        /* L'input è andato a buon fine, aggiorna buf */
        buf[n_stored++] = val;
    }

    /* Per controllo, stampa gli elementi memorizzati */
    printf("\n%zd elementi memorizzati\n", n_stored);
    for (size_t i = 0; i < n_stored; ++i)
        printf("buf[%zd] = %d\n", i, buf[i]);

    return 0;
}


Commenti:
1) L'istruzione

Codice: Seleziona tutto
   
while (n_stored < BUF_SZ &&
           (printf("buf[%zd] = ", n_stored),
            fflush(stdout),
            fgets(line, LINE_SZ, stdin))) {


dice che il ciclo while deve essere eseguito se

Codice: Seleziona tutto
n_stored < BUF_SZ && fgets(line, LINE_SZ, stdin) != NULL


Ho sfruttato l'operatore virgola per far sì che prima della chiamata alla fgets, venisse stampato il messaggio voluto.

2) La fgets, memorizza anche il carattere '\n' finale. Se la linea immessa, compreso il '\n', è più lunga di LINE_SZ-1, il '\n' non viene memorizzato. Allora, se c'è un '\n', dobbiamo eliminarlo e se non c'è dobbiamo eliminare l'input in eccesso, come detto sopra. Ciò è fatto dal seguente frammento di codice:

Codice: Seleziona tutto
       
        size_t line_len = strlen(line);
        if (line_len > 0) {
            if (line[line_len-1] == '\n')
                line[line_len-1] = 0;
            else {
                int c;
                while ((c = getchar()) != EOF && c != '\n')
                    ;
            }
        }


3) Per ciò che riguarda la strtol, l'istruzione

Codice: Seleziona tutto
if (*endp ||
            endp == line ||
            errno == ERANGE ||
            val < VAL_MIN ||
            val > VAL_MAX)


controlla che: i) tutti i caratteri siano numeri; ii) che la linea non sia vuota; iii) che il numero immesso sia memorizzabile in un long; iv) che il numero sia nel range [VAL_MIN,VAL_MAX] (INT_MIN <= VAL_MIN <= VAL_MAX <= INT_MAX). Se una di queste condizioni non è verificata, si riparte da capo.

E con questo ho finito: ovviamente il codice sopra è solo una possibile soluzione, ed è probabile che ce ne siano di migliori (e non è detto che sia esente da bug); ciò che mi premeva far vedere, però, è come anche un semplice compito come "memorizza 5 numeri interi" possa non essere così banale se si vuole tener conto di possibili "effetti collaterali" ;-)
It's a sin to write sin instead of \sin (Anonimo).
...'cos you know that cos ain't \cos, right?
You won't get a sexy tan if you write tan in lieu of \tan.
Take a log for a fireplace, but don't take log for \logarithm.
Avatar utente
Foto UtenteDirtyDeeds
55,9k 7 11 13
G.Master EY
G.Master EY
 
Messaggi: 7012
Iscritto il: 13 apr 2010, 16:13
Località: Somewhere in nowhere

0
voti

[44] Re: [C] Conferme esercizio su vettori

Messaggioda Foto Utentetecfil » 1 set 2013, 21:53

Ciao Foto UtenteDirtyDeeds

Complimenti per l'ottima spiegazione! Non avrei mai pensato che dietro ad un semplice "inserisci un numero"
ci sia da tenere conto di tutti questi passaggi!

Nemmeno durante il corso ci hanno spiegato così in dettaglio tutta la procedura e tutte le variabili che possono influenzare il salvataggio di un dato!

Grazie ancora per l'ottima risposta, ne terrò conto nei prossimi programmi :ok:

Ciaoo!
Il colmo per un elettricista? Essere isolato :D
Avatar utente
Foto Utentetecfil
327 1 5 6
Stabilizzato
Stabilizzato
 
Messaggi: 440
Iscritto il: 2 lug 2013, 21:41

Precedente

Torna a PC e informatica

Chi c’è in linea

Visitano il forum: Nessuno e 32 ospiti