Guida di C (base)

Input da tastiera

Giunti a questo punto i nostri programmi sono in grado di stampare a schermo più o meno tutto ciò che vogliamo; tuttavia in tutto ciò l'utente, durante l'esecuzione, rimane spettatore, poichè non ha modo di interagire col programma.

In questo capitolo vedremo come utilizzare getchar() e scanf(), le due funzioni che la libreria stdio ci mette a disposizione per leggere ed utilizzare i dati inseriti dall'utente.

getchar()

Partiamo dalla più semplice: la funzione getchar(), come suggerisce il nome, interrompe il flusso di esecuzione del programma, in attesa che l'utente inserisca un carattere con la tastiera (Un Invio, od un qualsiasi carattere seguito da Invio).

#include <stdio.h>

main()
{
	printf("Con che lettera inizia il tuo nome? ");
	getchar();
	printf("mi fa piacere...\n");
}
compiliamo ed eseguiamo:
[dario@localhost c]$ ./a.out
Con che lettera inizia il tuo nome? D
mi fa piacere...
[dario@localhost c]$ 

Il risultato però non è ancora interessante: l'utente inserisce una lettera, ma il programma non riesce ad utilizzarla successivamente... Tanto valeva non inserirla del tutto!

Sarebbe molto meglio se si potesse memorizzare il valore inserito da qualche parte, per poi utilizzarlo in qualche modo... Non a caso abbiamo da poco parlato di variabili:

#include <stdio.h>

main()
{
	char c;
	printf("Con che lettera inizia il tuo nome? ");
	c = getchar();
	printf("Il tuo nome inizia per '%c'!\n",c);
}
[dario@localhost c]$ ./a.out
Con che lettera inizia il tuo nome? D
Il tuo nome inizia per 'D'!
[dario@localhost c]$ 

Già più interessante: ora l'utente può interagire con i nostri programmi e modificarne l'esito

Soffermiamoci però sull'uso di getchar() che è stato fatto: abbiamo assegnato alla variabile c (di tipo char) un'intera funzione (getchar()). Questo vuole forse dire che getchar(), al termine della sua esecuzione, assume il valore di un'espressione contenente il carattere appena letto? Per il momento ci accontentiamo di un semplice , fiduciosi nel fatto che in futuro esploreremo questo meccanismo più nel dettaglio.

Il buffer di lettura

Si può verificare facilmente che nulla impedisce di immettere più lettere, laddove getchar() se ne aspetta una sola. Vediamo cosa succede se proviamo a farlo col programma che abbiamo appena compilato:

[dario@localhost c]$ ./a.out
Con che lettera inizia il tuo nome? Dario
Il tuo nome inizia per 'D'!
[dario@localhost c]$ 

Il funzionamento è identico. Si potrebbe ipotizzare che getchar() banalmente consideri solo la prima di tutte le lettere immesse, scartando le altre

In realtà non è così. Se getchar() dovesse controllare l'input ogni volta, utilizzerebbe più risorse di quante sono necessarie per fare quello che deve fare, e questo non rispecchia la filosofia del C!

Quello che getchar() realmente fa è di leggere il primo di tutti i caratteri che si trovano in uno spazio di sistema detto buffer di lettura.

Qui la shell conserva in sequenza tutti i caratteri immessi dall'utente, che mano a mano sono letti dal programma. Quando il buffer di lettura è vuoto, la shell attende che l'utente immetta del testo (una sequenza di caratteri seguita da un Invio) per riempirlo.

Verifichiamo la veridicità di ciò con un esperimento:

#include <stdio.h>

main()
{
	char c;

	printf("Inserisci la prima lettera: ");
	c = getchar();
	printf("La prima lettera è '%c'!\n",c);

	printf("Inserisci la seconda lettera: ");
	c = getchar();
	printf("La seconda lettera è '%c'!\n",c);

	printf("Inserisci la terza lettera: ");
	c = getchar();
	printf("La terza lettera è '%c'!\n",c);
}

Il funzionamento "tradizionale" è abbastanza intuitivo: per tre volte l'utente immette una lettera ed il programma la stampa subito dopo. Ma se quello che abbiamo detto prima è vero allora, inserendo più lettere in una volta, dovremmo riempire il buffer di lettura a sufficienza per i getchar() successivi:

[dario@localhost c]$ ./a.out 
Inserisci la prima lettera: abc
La prima lettera è 'a'!
Inserisci la seconda lettera: La seconda lettera è 'b'!
Inserisci la terza lettera: La terza lettera è 'c'!
[dario@localhost c]$ 

Q.E.D. Notiamo inoltre che, dalla seconda riga in poi, le frasi non vanno a capo, perchè manca l'Invio dell'utente. Lascio alla fantasia dello studente sperimentare come si comporta il programma variando l'input (ad esempio aumentando o diminuendo il numero di lettere inserite).

scanf()

Vediamo ora come utilizzare scanf() per leggere dati numerici (e non solo) introdotti dall'utente.

Si può facilmente intuire che scanf() è imparentata concettualmente con printf(): possiamo in qualche modo considerarla la sua parallela per quanto riguarda la lettura dei dati.

Come printf(), infatti, scanf() va chiamata con una stringa di controllo seguita da uno o più argomenti:

scanf("%c",&c);

Questa istruzione è del tutto equivalente a getchar(): legge un carattere e lo mette nella variabile c. Attenzione è essenziale anteporre il simbolo & (e commerciale, o ampersand) al nome della variabile. Il perchè lo vedremo dopo il rituale esempio pratico:

#include <stdio.h>

main()
{
	int anni;

	printf("Quanti anni hai? ");
	scanf("%d",&anni);

	printf("Adesso hai %d anni...\n...l'anno prossimo ne avrai %d!\n",anni,anni+1);
}

A voi compilazione ed esecuzione...

Il simbolo &: l'indirizzo di una variabile

La domanda sorge spontanea: perchè ci dev'essere una funzione che fa riferimento alle variabili in modo diverso da tutte le altre? La risposta sta nel fatto che scanf() in realtà usa le variabili in modo diverso dalle altre.

Mi spiego meglio: fino ad ora di una variabile a noi è sempre interessato solo il suo "contenuto": il valore. Scrivere il nome di una variabile in un listato C equivale a scrivere un'espressione avente il valore contenuto nella variabile in questione. Capiamo bene però che, per una funzione come scanf(), è inutile conoscere il contenuto della variabile che andrà a sovrascrivere.

È necessario invece trasmettere in qualche modo la posizione in cui si trova questa variabile, in modo che la funzione sappia dove scrivere ciò che legge: l'espressione &c indica proprio l'indirizzo della variabile c.

È importante padroneggiare questi concetti, perchè il C non impedisce in nessun modo l'omissione del simbolo &:

 scanf("%d",n);	/* Attenzione! Manca il simbolo & davanti al nome della variabile */

Questa riga viene compilata correttamente; semplicemente, al posto dell'indirizzo di n, scanf() riceverà il suo valore, e tenterà quindi di scrivere il carattere letto in un indirizzo che non c'entra nulla con il vero indirizzo di c.

Ad esempio, se n fosse una variabile contenente il numero 12, e situata all'indirizzo 1024, scanf() riceverebbe '12' come indirizzo, e sovrascriverebbe ciò che realmente si trova all'indirizzo '12' (che potrebbe essere qualsiasi cosa!).

Conclusioni

In breve...

  • È possibile permettere all'utente di interagire coi nostri programmi, leggendo ciò che egli inserisce sulla console (la linea di comando).
  • Tutto ciò che viene inserito sulla linea di comando (fino alla pressione del tasto Invio) viene memorizzato nel buffer di lettura.
  • La funzione getchar() richiede al buffer di lettura un singolo carattere. Se il buffer è vuoto il programma attende che l'utente vi inserisca qualcosa.
  • Quando legge qualcosa, getchar() assume il valore di ciò che ha letto, e può quindi essere usata come una qualsiasi altra espressione.
  • La funzione scanf(), dal comportamento simile a printf() legge ogni tipo di dato (a seconda della stringa di controllo) e lo memorizza nella/e variabile/i il cui indirizzo è dato come parametro.
  • L'espressione &nome ha il valore dell'indirizzo della variabile nome.
  • Ogni tipo di dato può essere considerato un valore numerico. Per questo il C non impedisce di inserire, al posto dell'indirizzo della variabile, un qualsiasi altro numero (come può essere il contenuto di una variabile, o una qualsiasi espressione). Questo nel 90% dei casi (99% per i principianti) è un grosso errore.
  1. Introduzione
  2. Preparare l'ambiente
  3. Il primo programma
  4. La funzione printf()
  5. Le variabili
  6. Input da tastiera
  7. Esecuzione condizionale: if
  8. Cicli
  9. Esempi
  10. Bibliografia