PDA

View Full Version : Controllo input in C/C++: alcune domande.


uomoserio
03-06-2007, 11:23
Ciao a tutti, avrei bisogno di qualche delucidazione sulla gestione dell'input da tastiera in C/C++.
Di seguito è il classico giochino "indovina il numero", sulla base del quale avrei alcune domande da farvi per gestire le immissioni da tastiera da parte dell'utente:

#include <cstdlib>
#include <iostream>
#include <ctime>
using namespace std;

unsigned int insNumero();
void gioco();

int main()
{
char again;
do {
gioco();
do{
printf("\n\nVuoi giocare ancora? [s/n] ");
again=getchar();
fflush(stdin);
if (again!='s' && again!='n') printf("\nScelta non consentita. Premi 's' o 'n'");
} while (again!='s' && again!='n');
} while (again=='s');

return 0;
}

unsigned int insNumero()
{
int numInserito;
do{
printf("\nInserisci il numero: ");
scanf("%u", &numInserito);
if (numInserito==0 || numInserito > 100) printf("\nDevi inserire un numero da 1 a 100!!!\n");
fflush(stdin);
} while(numInserito==0 || numInserito > 100);
return numInserito;
}

void gioco()
{
unsigned int numMagico;
unsigned int numIndovina;
srand(time(NULL));
numMagico=(rand()%100)+1;
printf("\n\nIndovina il numero magico da 1 a 100\n");
do {
numIndovina=insNumero();
if (numIndovina < numMagico) printf("\nHai inserito un numero troppo basso.\n");
else if (numIndovina > numMagico) printf("\nHai inserito un numero troppo alto.\n");
else {
printf("COMPLIMENTI!!!\n");
printf("Il numero magico era proprio: %u", numMagico);
}
} while (numIndovina != numMagico);
return;
}

Domanda 1: nella funzione insNumero(), ho usato un metodo spicciolo per controllare che il valore immesso sia compreso fra 1 e 100, così se l'utente immette dei caratteri invece di numeri il controllo comunque lo blocca (altrimenti il programma si impalla di brutto!). Ma se io avessi voluto verificare a priori il tipo immesso dall'utente (caratteri o numeri) come dovevo fare?

Domanda 2: per evitare i soliti problemi con i caratteri rimanenti nel buffer di immissione, dopo ogni inserimento da parte dell'utente ho usato fflush(stdin) per svuotare il buffer ed evitare che all'immisione successiva venissero presi eventuali caratteri residui. Questo è un sistema corretto? Oppure esiste un sistema migliore per controllare l'eccedenza del buffer di stdin? Inoltre la funzione fflush(stdin) l'ho trovata cercando nei forum....ma possibile che nessun manuale di C/C++, fra le decine che possiedo, si degni di spiegare queste cose? Eppure il problema della ingestibilità del buffer di input è un problema molto diffuso, a quanto leggo in giro....

Domanda 3: per ora il programma è scritto in C, usando le funzioni standard printf e scanf. Ma se io avessi voluto rimanere sul C++ e usare cin per le immissioni dell'utente, in che modo si utilizza esattamente? So che la forma generale con l'operatore estrattore è "cin >> variabile", ma in che modo posso formattare l'input (come ad esempio"%u" in scanf) per essere sicuro che mi restituisca un valore e non un carattere?

Grazie per la pazienza. :)

andbin
03-06-2007, 12:05
Domanda 1: nella funzione insNumero(), ho usato un metodo spicciolo per controllare che il valore immesso sia compreso fra 1 e 100, così se l'utente immette dei caratteri invece di numeri il controllo comunque lo blocca (altrimenti il programma si impalla di brutto!). Ma se io avessi voluto verificare a priori il tipo immesso dall'utente (caratteri o numeri) come dovevo fare?Avresti dovuto usare funzioni a più basso livello e non standard, es. getch() o le API Win32 se stai lavorando su Windows.

Domanda 2: per evitare i soliti problemi con i caratteri rimanenti nel buffer di immissione, dopo ogni inserimento da parte dell'utente ho usato fflush(stdin) per svuotare il buffer ed evitare che all'immisione successiva venissero presi eventuali caratteri residui. Questo è un sistema corretto? Oppure esiste un sistema migliore per controllare l'eccedenza del buffer di stdin? Inoltre la funzione fflush(stdin) l'ho trovata cercando nei forum....ma possibile che nessun manuale di C/C++, fra le decine che possiedo, si degni di spiegare queste cose? Eppure il problema della ingestibilità del buffer di input è un problema molto diffuso, a quanto leggo in giro....fflush(stdin) non va molto bene. Secondo le specifiche, fflush va usato solo con stream di output, perché con stream di input il comportamento è indefinito (sebbene in alcuni ambienti fa proprio quello che vuoi, cioè pulire il buffer di input).
Ci sono altri modi per eliminare i caratteri in più:
- Fare un ciclo while(getchar() != '\n');
- Evitare scanf :D e usare fgets/sscanf

Domanda 3: per ora il programma è scritto in C, usando le funzioni standard printf e scanf. Ma se io avessi voluto rimanere sul C++ e usare cin per le immissioni dell'utente, in che modo si utilizza esattamente? So che la forma generale con l'operatore estrattore è "cin >> variabile", ma in che modo posso formattare l'input (come ad esempio"%u" in scanf) per essere sicuro che mi restituisca un valore e non un carattere?In effetti hai fatto un po' un mix ... hai usato roba del "C" (scanf, printf) ma poi hai incluso roba del C++. Evita questi mix.

ndakota
03-06-2007, 12:15
in effetti iostream e namespace std li potrebbe anche togliere no?

uomoserio
03-06-2007, 14:41
Avresti dovuto usare funzioni a più basso livello e non standard, es. getch() o le API Win32 se stai lavorando su Windows.

Per ora faccio cose in console, sto appena imparando, niente API Win32.
Ma getch() e sua sorella getche() servono per leggere caratteri da stdin senza buffer, giusto? Come posso usarlo per verificare se l'utente digita dei caratteri invece che dei numeri? E poi mi sembra che getch() restituisca un tipo char, come lo uso per inserire valori in variabili int? :confused:

fflush(stdin) non va molto bene. Secondo le specifiche, fflush va usato solo con stream di output, perché con stream di input il comportamento è indefinito (sebbene in alcuni ambienti fa proprio quello che vuoi, cioè pulire il buffer di input).
Ci sono altri modi per eliminare i caratteri in più:
- Fare un ciclo while(getchar() != '\n');
- Evitare scanf :D e usare fgets/sscanf

Hai ragione, in effetti non avevo pensato al ciclo while per svuotare il buffer. Semplice e veloce, grazie :)
Da quello che ho letto fgets e sscanf servono per le stringhe....dovrei usare quelli per ricevere numeri dal stdin? Ma poi hanno comunque il problema del buffer, giusto? Quindi dovrei in ogni caso svuotarlo, vero? :confused:

In effetti hai fatto un po' un mix ... hai usato roba del "C" (scanf, printf) ma poi hai incluso roba del C++. Evita questi mix.

Per il mix col C++ ti riferisci all'uso di <iostream> con "using namespace std"? In effetti originariamente avevo scritto il programma usando cin e cout, ma poi li ho sostituiti perchè non riuscivo a capire come gestirli. :(

marco.r
03-06-2007, 16:05
Ciao a tutti, avrei bisogno di qualche
Domanda 1: nella funzione insNumero(), ho usato un metodo spicciolo per controllare che il valore immesso sia compreso fra 1 e 100, così se l'utente immette dei caratteri invece di numeri il controllo comunque lo blocca (altrimenti il programma si impalla di brutto!). Ma se io avessi voluto verificare a priori il tipo immesso dall'utente (caratteri o numeri) come dovevo fare?

Se vuoi usare gli stream del C++, puoi fare qualcosa di simile al seguente:

#include <iostream>
using namespace std;

/* ... */
int n;
cin >> n
if ( cin.fail() ) // ho inserito dei caratteri
{
cin.clear();
while( !isdigit( cin.peek() ) ) cin.ignore();
}

Questo fa si' che in caso di errore vengano ignorati tutti i caratteri fino alla prima cifra.
Se preferisci che venga ignorata tutta la riga, allora puoi fare nel modo seguente

#include <iostream>
using namespace std;

/* ... */
int n;
cin >> n
if ( cin.fail() ) // ho inserito dei caratteri
{
cin.clear();
while( cin.peek() != '\n' ) cin.ignore();
}

Vedi tu quale sia piu' adatto al tuo caso


Domanda 2: per evitare i soliti problemi con i caratteri rimanenti nel buffer di immissione, dopo ogni inserimento da parte dell'utente ho usato fflush(stdin) per svuotare il buffer ed evitare che all'immisione successiva venissero presi eventuali caratteri residui. Questo è un sistema corretto? Oppure esiste un sistema migliore per controllare l'eccedenza del buffer di stdin? Inoltre la funzione fflush(stdin) l'ho trovata cercando nei forum....ma possibile che nessun manuale di C/C++, fra le decine che possiedo, si degni di spiegare queste cose? Eppure il problema della ingestibilità del buffer di input è un problema molto diffuso, a quanto leggo in giro....

No, flush va bene solo con l'output. Se anche funzionasse (ovvero svuotando il buffer di input) non puoi avere la certezza che questo contenga una sola linea di testo


Domanda 3: per ora il programma è scritto in C, usando le funzioni standard printf e scanf. Ma se io avessi voluto rimanere sul C++ e usare cin per le immissioni dell'utente, in che modo si utilizza esattamente? So che la forma generale con l'operatore estrattore è "cin >> variabile", ma in che modo posso formattare l'input (come ad esempio"%u" in scanf) per essere sicuro che mi restituisca un valore e non un carattere?

Nel tuo caso, vedi la prima riga: basta usare una variabile del tipo opportuno, ven,tuali input non previsti settano lo stato del canale su "fail". In generale non puoi formattare l'input allo stesso modo in cui usi una scanf, ci si puo' arrivare vicino leggendo l'input in variabili del tipo opportuno, ed ignorando i caratteri che non ti interessano.

uomoserio
03-06-2007, 16:26
Se vuoi usare gli stream del C++, puoi fare qualcosa di simile al seguente:

#include <iostream>
using namespace std;

/* ... */
int n;
cin >> n
if ( cin.fail() ) // ho inserito dei caratteri
{
cin.clear();
while( !isdigit( cin.peek() ) ) cin.ignore();
}

Questo fa si' che in caso di errore vengano ignorati tutti i caratteri fino alla prima cifra.
Se preferisci che venga ignorata tutta la riga, allora puoi fare nel modo seguente

#include <iostream>
using namespace std;

/* ... */
int n;
cin >> n
if ( cin.fail() ) // ho inserito dei caratteri
{
cin.clear();
while( cin.peek() != '\n' ) cin.ignore();
}

Vedi tu quale sia piu' adatto al tuo caso

Grazie! :)
E' esattamente quello che cercavo. Sapevo che cin adatta l'input alla variabile che lo deve contenere, ma non conoscevo tutte le sottofunzioni peek, fail, clear e ignore.
A dire il vero avevo visto queste cose qui:
http://www.cplusplus.com/reference/iostream/istream/
ma non ci ho capito un gran ché, forse perchè sono agli inizi! :D


No, flush va bene solo con l'output. Se anche funzionasse (ovvero svuotando il buffer di input) non puoi avere la certezza che questo contenga una sola linea di testo

In effetti me lo avete già detto in tanti, però a me funziona. Poi l'ho trovato scritto in molti forum, come soluzione di svuotamento del buffer.
Comunque ora che mi hai dato la soluzione migliore la sostituisco senza dubbio.

Grazie di tutto. :)

uomoserio
04-06-2007, 15:18
No, flush va bene solo con l'output. Se anche funzionasse (ovvero svuotando il buffer di input) non puoi avere la certezza che questo contenga una sola linea di testo

Ok, come suggerito da andbin, per svuotare il buffer posso usare:
while (getchar()!='\n')
che va benissimo, in effetti. Ma per usare esclusivamente le funzioni di I/O del C++, ho sostituito getchar() con cin.get() nel ciclo while, però in caso di errore non funziona. Perchè?
Prendi questo codice:
#include <iostream>
using namespace std;

int main()
{
int x;
cout << "Inserisci un numero: ";
cin >> x;
if (cin.fail()) cout << "\nErrore";
else cout << "\nHai digitato: " << x;
while (cin.get()!='\n');
cin.get();
return 0;
}

Se io immetto un numero, procede tutto bene.
Ma se io, invece, inserisco uno o più caratteri, il programma stampa correttamente il messaggio "Errore" (sfruttando la funzione cin.fail() ), però poi si pianta nel ciclo while di svuotamento.
Da quello che ho capito dal comportamento, in caso di errore nello stream di input non esiste più '\n' e il ciclo while diventa infinito! :(

Quindi come risolvo? Cosa devo cercare invece di '\n'? :confused:

marco.r
04-06-2007, 18:27
#include <iostream>
using namespace std;

int main()
{
int x;
cout << "Inserisci un numero: ";
cin >> x;
if (cin.fail()) cout << "\nErrore";
else cout << "\nHai digitato: " << x;
while (cin.get()!='\n');
cin.get();
return 0;
}

Quindi come risolvo? Cosa devo cercare invece di '\n'? :confused:

A naso direi che ti sei dimenticato di impostare su "pulito" lo stato del canale con cin.clear() prima del ciclo while, per cui le chiamate successive a cin.get() falliscono tutte.

uomoserio
04-06-2007, 18:43
A naso direi che ti sei dimenticato di impostare su "pulito" lo stato del canale con cin.clear() prima del ciclo while, per cui le chiamate successive a cin.get() falliscono tutte.

Cavolo, hai ragione!! :muro:
Infatti adesso funziona, e in questo modo sono riuscito anche a trasformare il giochino del numero in modo da usare soltanto istruzioni C++ e abbandonare quelle standard del C.
Grazie infinite! :) :D