23.1 I/O di caratteri
Un file puo' essere visto come un flusso di caratteri. Un file di testo e' una sequenza di caratteri che formano le linee del testo: alla fine di ogni linea e' presente il carattere newline. Esistono due funzioni della libreria standard: getchar e putchar. La prima legge il prossimo carattere in input e lo restituisce sotto forma di intero, la seconda stampa un carattere in output. I formati sono:
questi sono i prototipi di queste due funzioni e si leggono cosi': getchar e' una funzione che non accetta parametri (o meglio accetta parametri di tipo void) e ritorna un intero; putchar e' una funzione che accetta un intero e ritorna un intero. Su queste definizioni si discutera' ampiamente in seguito: per ora vediamo un semplice esempio:
in questo programma viene letto un carattere dall'input (standard input, cioe' tastiera). Viene valutata l'espressione del costrutto while: se risulta vera viene eseguito il corpo altrimenti termina. L'operatore di relazione '!=' significa 'diverso da'. EOF sta per End Of File, cioe' fine del file ed e' un valore particolare che viene ritornato dalla funzione getchar() quando viene raggiunta la fine del file, cioe' quando non ci sono piu' dati in input. Era possibile dichiarare la variabile c di tipo char, ma tale variabile non sarebbe stata abbastanza grande da contenere anche il valore particolare EOF. EOF e' un valore intero definito nell'header file 'stdio.h' che non puo' essere confuso con nessun altro carattere. Quindi fino a quando il valore contenuto nella variabile c sara' diverso da EOF (cioe' fino a quando ci sono dati in input) viene stampato in output il valore di c (tramite la funzione putchar (c)) e letto un nuovo carattere dall'input. Una volta compilato il programma e' possibile provarlo, ricordandosi che il carattere EOF viene prodotto dalla pressione contemporanea dei tasti Ctrl e Z. Provando il programma, il meccanismo della funzione getchar() puo' sembrare un po' misterioso, in quanto digitando piu' caratteri e premendo invio tali caratteri vengono ristampati nuovamente in blocco e non uno per uno come ci si aspetterebbe. Il motivo e' semplice: la funzione getchar() bufferizza (orrendo anglo-italiano!) i caratteri letti in input ma non cede il controllo al programma che l'ha invocata fino a quando non viene premuto il tasto invio. Bufferizzare significa memorizzare i caratteri letti in un'area di memoria temporanea (il buffer appunto). Facciamo un esempio: supponiamo di lanciare il programma e di digitare i caratteri '1', '2' e '3'. Mentre si digitano tali caratteri non accade nulla, ma non appena si preme invio tali caratteri vengono riprodotti a video. Infatti la prima getchar() legge il primo carattere e lo scrive in un buffer, legge il secondo e lo scrive nel buffer, legge il terzo e lo scrive nel buffer, legge il quarto (il tasto invio) e cede il controllo al programma, ma non prima di aver ritornato il valore del primo carattere inserito nel buffer, cioe' '1'. Tale carattere (ricordiamo che un carattere e' un intero) viene collocato all'interno della variabile 'c' mediante l'istruzione di assegnamento 'c = getchar ()'. Tale istruzione significa infatti: prendi il valore intero ritornato dalla funzione getchar e collocalo nella variabile intera 'c'. Poiche' il tasto invio non e' il carattere EOF, viene eseguito il ciclo di while ed invocata la funzione putchar(c). La funzione putchar(c) legge la variabile 'c' passatagli in input, contenente il carattere, lo stampa a video (standard output) e cede il controllo al programma. Dopo la funzione putchar(c) viene invocata nuovamente la funzione getchar(). Quest'ultima funzione questa volta non legge dal file di input (input standard, cioe' la tastiera) ma dal buffer. Una volta letto il carattere dal buffer assegna tale valore alla variabile 'c', cede il controllo al programma e tale carattere viene stampato a video dalla funzione putchar(). Il ciclo si ripete fino a quando il buffer non viene esaurito, a quel punto la funzione getchar() riprende la lettura dal file di input. Queste iterazioni continuano fino a quando non vengono premuti i tasti CTRL e Z (EOF). A questo punto la funzione getchar() cede il controllo al programma e viene valutata nuovamente la condizione del while che questa volta risulta falsa ed il programma termina. Vediamo una forma piu' compatta di questo programma:
questo programma produce lo stesso risultato del programma precedente ma sono state eliminate alcune righe. Qualsiasi espressione di assegnamento viene valutata attribuendogli un determinato valore: quello della parte sinistra dopo l'assegnamento. In altre parole se si digita il carattere '1' e si preme invio, la funzione getchar() ritorna il carattere '1' che viene assegnato alla variabile 'c'. Percio' in questo caso l'espressione 'c = getchar()' equivale a 1 che risulta diverso da EOF percio' il ciclo continua. Se si digita il carattere 'a', la variabile 'c' conterra' 'a' (o meglio il valore numerico del carattere 'a' nella codifca della macchina, ad es. ASCII) percio' l'espressione sara' equivalente ad 'a' che essendo diverso da EOF consente una nuova iterazione del ciclo di while. Sintetizzando: poiche' qualsiasi espressione di assegnamento quando viene valutata equivale ad un valore, e' possibile usare tale espressione all'interno di una condizione (all'interno di un while, di un for o di una if ad esempio). Le parentesi dell'espressione '(c = getchar ())' sono obbligatorie, poiche' l'operatore di relazione '!=' ha precedenza rispetto all'operatore di assegnamento '='. Per convenzione il valore di una espressione puo' essere 1 se e' vera oppure 0 se e' falsa. Senza tali parentesi verrebbe prima valutata l'espressione 'getchar () =! EOF' e poi verrebbe assegnato il valore risultante alla variabile 'c'. Tale variabile conterrebbe sempre '1' per qualsiasi carattere digitato (essendo vera la condizione getchar() != EOF) e '0' per il carattere EOF (essendo falsa la condizione getchar() != EOF). 23.2 Conteggio di righe, parole e caratteri
Il programma che segue conta le righe, le parole ed i caratteri dell'input:
Le prime due righe sono delle espressioni costanti, dove IN e OUT sono i valori che lo switch 'state' puo' assumere durante l'elaborazione. Uno switch (un interruttore di tipo deviatore) o flag (bandierina), e' una variabile che indica uno stato appunto. In questo caso lo switch 'state' puo' assumere lo stato IN oppure OUT. Nella prima riga della funzione main() vengono dichiarate le variabili di tipo int 'c', 'nl', 'nw', 'nc' e state. Le variabili 'nl', 'nw' e 'nc' sono dei contatori, cioe' delle variabili usate per totalizzare il numero di righe (nl), il numero di parole (nw) ed il numero di caratteri (nc) letti. La variabile 'state' quando assume il valore IN significa che ci troviamo all'interno di una parola, mentre quando assume il valore OUT significa che ci troviamo all'esterno. Come prima cosa lo switch state viene inizializzato ad OUT perche' non ci troviamo all'interno di una parola. La riga 'nl = nw = nc = 0' consente di inizializzare a 0 le variabili nl, nw ed nc. Infatti le assegnazioni sono associative da destra a sinistra ed una espressione di assegnamento quando viene valutata equivale ad un valore numerico. Tenendo a mente queste due regole generali vediamo che, il primo assegnamento partendo da destra (nc = 0) assegna 0 alla variabile nc. Ma poiche' tale espressione di assegnamento una volta valutata corrisponde al valore dell'operando di sinistra, tale espressione equivale a 0. Continuando a valutare l'espressione da destra verso sinistra troviamo che ad nw viene assegnato il valore dell'espressione valutata prima, cioe' 0. Quindi nw vale zero e l'espressione vale 0. Infine troviamo che ad nl viene assegnato il valore dell'espressione appena valutata, cioe' 0. In pratica la stessa espressione di assegnamento poteva essere scritta cosi': nl = (nw = (nc = 0)). All'interno del corpo del while e' presente l'istruzione di incremento nc. L'operatore di incremento significa incremento di una unita'. Questo operatore viene messo vicino ad una variabile e puo' essere prefisso o postfisso, cioe' a sinistra della variabile o alla sua destra. Se si utilizza l'operatore prefisso la variabile viene prima incrementata e poi usata mentre se si utilizza l'operatore postfisso la variabile viene prima usata e poi incrementata. In questo programma e' indifferente usare la notazione prefissa o postfissa ma in altri casi l'uso di una notazione piuttosto di un'altra provoca effetti molto diversi. Nella riga successiva all'istruzione di incremento viene usato il costrutto 'if'. Tale costrutto credo sia alla base di tutti i linguaggi di programmazione e lo si puo' trovare nel C, nel Perl, nel Cobol, nel PHP, nel Javascript in pratica ovunque. L'espressione 'if' valuta una condizione che se risulta vera, provoca l'esecuzione di altre istruzioni. Nel costrutto 'if' e' presente la parola opzionale 'else' che significa altrimenti. La forma generale e':
Se e' vera la condizione contenuta tra parentesi tonde viene eseguita l'istruzione 1, in caso contrario viene eseguita l'istruzione 2. Dopo una 'else' e' possibile usare una ulteriore 'if' per verificare una condizione ulteriore. Ad esempio:
nella prima if viene verificata la condizione 'c == '\n'' dove '\n' rappresenta la solita sequenza di escape che indica il carattere newline. L'operatore di relazione '==' confronta l'uguaglianza tra la variabile 'c' ed il carattere newline. Attenzione a non confondere l'operatore di assegnamento '=' con l'operatore di relazione '==' infatti mentre il primo assegna un valore ad una variabile, il secondo confronta due entita'. La condizione '(c == ' ' || c == '\n' || c == '\t')' contiene l'operatore logico '||' che corrisponde all' OR. Gli operatori logici (o booleani) sono operatori utilizzati nella logica booleana e sono 3: AND, OR e NOT. L'operatore AND corrisponde ai simboli '&&' e l'operatore OR corrisponde ai simboli '||' (il carattere '|' si chiama 'pipe' e nella tastiera corrisponde al carattere sopra al carattere '\'). L'operatore NOT corrisponde al simbolo '!'. In C esistono molti operatori e possono essere unari, binari e ternari. Gli operatori unari agiscono su un solo operando (come il NOT), gli operatori binari agiscono su due operandi e gli operatori ternari agiscono su tre operandi. Gli operatori AND e OR sono binari. Attenzione a non confondere l'espressione 'operatore binario' con 'operatore bit a bit': la prima indica un operatore che agisce su due operandi mentre la seconda indica un operatore che agisce su uno o piu' operandi e tali operandi sono dei bit. La logica degli operatori e' illustrata nelle cosidette tabelle della verita'. Tabella dell' operatore AND:
Tabella dell'operatore OR (inclusivo):
Tabella dell'operatore XOR (esclusivo):
Tabella dell'operatore NOT:
La tabella AND si legge in questo modo: se entrambe le condizioni sono vere allora il risultato della condizione genereale e' vero. Se ad esempio la varibile 'a' vale '5' e la variabile 'b' vale '7' allora la condizione: '(a > 0) && (b > 0)' risulta vera in quanto entrambe le variabili sono maggiori di zero. La tabella OR invece si legge in questo modo: se almeno una o entrambe le condizioni sono vere allora la condizione generale e' vera. Ad esempio se la variabile 'a' vale '5' e la variabile 'b' vale '-7' allora la condizione: '(a > 0) || (b > 0)' e' vera. Esistono due tipi di OR: quello inclusivo e quello esclusivo. L'OR appena visto e' inclusivo in quanto include anche la possibilita' che entrambe le condizioni siano vere, cioe' se sono vere entrambe, allora la condizione e' vera. Ad esempio se 'a' vale '5' e 'b' vale '7' allora la condizione '(a > 0) || (b > 0)' e' vera. In realta' esiste un altro operatore che si chiama OR esclusivo o XOR che viene applicato nelle operazioni bit a bit (operazioni bitwise) che esclude la possibilita' che entrambe le condizioni siano vere. Con l'operatore XOR se entrambe le condizioni sono vere allora il risultato sara' falso (prima riga della tabella XOR). Questo operatore sara' trattato in seguito. La tabella NOT e' la piu' semplice: se la condizione e' vera allora il risultato sara' falso e viceversa se la condizione e' falsa allora il risultato sara' vero. Tornando alla condizione '(c == ' ' || c == '\n' || c == '\t')' il risultato sara' vero se la variabile 'c' conterra' il valore 'spazio' oppure il valore 'newline' oppure il carattere di tabulazione. Se almeno una delle condizioni sara' vera allora il risultato sara' vero e di conseguenza il carattere contenuto nella variabile 'c' sara' un carattere esterno ad una parola pertanto verra' valorizzato lo switch 'state' con il valore OUT (esterno). Se viceversa il risultato sara' falso il carattere sara' interno ad una parola, percio' verra' incrementato il contatore delle parole nw e verra' impostato lo switch 'state' con il valore IN (interno alla parola). Quando verra' incontrato il successivo carattere di una stessa parola, poiche' lo switch 'state' conterra' il valore IN, non sara' piu' vera la condizione '(state == OUT)' e quindi non verra' incrementato nuovamente il contatore delle parole. Inizio della guida Il primo programma in C Indice I vettori Copyright (c) 2002-2003 Maurizio Silvestri |