[C] Prendere in input una stringa di due o più parole

smartmouse
Salve, in un programmino che sto facendo ad un certo punto chiedo all'input l'inserimento di una stringa e due interi, ovvero:

printf("Nome: ");
scanf("%s", &lista.nome);
printf("Cognome: ");
scanf("%s", &lista.cognome);
do {
printf("Eta': ");
scanf("%d", &lista.eta);
} while (lista.eta<=0);


Se ad esempio voglio inserire da tastiera un nome tipo Antonio Mario Rossi, quando inserisco "Antonio Mario" alla domanda del nome, mi chiede direttamente l'età e assegna il valore "Mario" al campo cognome della struttura lista. Sapete dirmi perchè ciò avviene e come risolvere il problema?

PS: Nella struttura lista il campo nome è un array di 20 caratteri.

Risposte
apatriarca
Puoi fare inserire due stringhe come in
printf("%19s%19s", lista.nome, lista.cognome);

smartmouse
Scusami, ma non ho capito...

apatriarca
Sì scusa, avevo fretta e avevo frainteso il tuo problema. Ti consiglio di dare un occhiata alla funzione fgets. In pratica al posto di scanf devi scrivere:

fgets(lista.nome, 20, stdin);

smartmouse
Non ha funzionato. Nell'output del programma non mi lascia inserire nulla da tastiera, salta subito a chiedermi il cognome...
Ho provato sia fgets che gets. Come si usano esattamente?
Ho provato a cercare su Google ma non ho ben capito in quali casi si usano...

apatriarca
Il codice
#include <stdio.h>

int main()
{
    char nome[20], cognome[20];
    unsigned eta;

    printf("Nome: ");
    fgets(nome, 20, stdin);

    printf("Cognome: ");
    fgets(cognome, 20, stdin);
    
    printf("Eta': ");
    scanf("%u", &eta);

    printf("Nome: %sCognome: %sEta': %u\n", nome, cognome, eta);

    return 0;
}

ha sul mio computer l'output:
Nome: Antonio Mario
Cognome: Rossi
Eta': 25
Nome: Antonio Mario
Cognome: Rossi
Eta': 25

È questo che hai l'intenzione di fare? fgets serve per leggere una riga di testo e viene anche incluso il carattere '\n' di a capo.

smartmouse
Niente da fare. Non vuole funzionare.
Ho provato il tuo codice e funziona senza problemi.

Tra un pò riprovo, confrontando i due codici e ti faccio sapere.
Grazie.

smartmouse
Ho modificato il tuo codice aggiungendo le parti che sto studiando e non funziona!
Ti mostro il codice:

#include

typedef struct persona {
char nome[20], cognome [20];
unsigned eta;
} elenco;

int main()
{
elenco lista[3];
int i;

for (i=0; i<3; i++) {
printf("Nome: ");
fgets(lista.nome, 20, stdin);

printf("Cognome: ");
fgets(lista.cognome, 20, stdin);

printf("Eta': ");
scanf("%u", &lista.eta);
}

for (i=0; i<3;i++) {
printf("Nome: %sCognome: %sEta': %u\n", lista.nome, lista.cognome, lista.eta);
}

return 0;
}


Per favore guarda cosa succede nell'output...
Grazie.

apatriarca
Il problema, al quale avrei dovuto già pensare, è che l'istruzione che legge il numero lascia il carattere di a capo nel buffer di lettura, per cui fgets legge questo carattere invece che la riga successiva come dovrebbe. La soluzione sarebbe quella di ignorare la riga corrente. L'unica soluzione che mi viene in mente in questo momento è quella di scrivere un codice come:
while (getchar() != '\n') {}

subito dopo alla lettura dell'età. Potresti anche usare una fgets.

smartmouse
Non ho nemmeno provato ad usare la soluzione che mi hai proposto, anche perchè non ho capito come implementare quel while.
In ogni caso, possibile che non ci sia un modo pratico in C per leggere semplicemente una "frase" da tastiera?

PS: Perchè nel tuo esempio funziona e nel mio no?

apatriarca
Il problema non è la lettura della frase da tastiera, ma nello scanf. Questa funzione termina la lettura prima della fine della riga, per cui il successivo fgets termina la lettura della riga con il numero invece che passare al successivo. Non sono al corrente di nessuna funzione in C il cui obiettivo sia semplicemente quello di ignorare il resto della riga corrente. Ma, in ogni caso è implementabile semplicemente con il ciclo che ti ho scritto. È tutto quello che si deve fare per ignorare completamente la riga corrente.

smartmouse
"smartmouse":
Non ho nemmeno provato ad usare la soluzione che mi hai proposto, anche perchè non ho capito come implementare quel while.
In ogni caso, possibile che non ci sia un modo pratico in C per leggere semplicemente una "frase" da tastiera?

PS: Perchè nel tuo esempio funziona e nel mio no?


Up!

apatriarca
Il problema del codice NON è fgets e la lettura di una riga di testo. fgets è l'unica funzione dello standard C (escludendo la poco sicura e simile gets) che permette di leggere una intera riga dalla console. E comunque funziona abbastanza bene. Il problema del tuo codice è quello di mischiare scanf e fgets. Mentre il primo legge una riga per volta, il secondo legge solo una parte della riga e lascia quindi il resto nel buffer di lettura. Quando viene quindi lanciata fgets nell'iterazione successiva, viene letto il resto della riga che è già presente nel buffer invece di attendere l'inserimento della nuova riga. Se non ti piace la soluzione che ti ho proposto sei allora costretto a cambiare il modo in cui leggi il numero. Una possibile soluzione può essere quella di usare la coppia fgets/atoi come segue:
char buffer[10]; 
/*
 * ...
 */
fgets(buffer, 10, stdin);
lista[i].eta = atoi(buffer);

smartmouse
Scusami tanto ma non avevo fatto caso che c'era una seconda pagina del topic e che mi avevi risposto!

Ho capito che c'è un problema nell'utilizzare fgets e scanf insieme, ma non sono riuscito ad applicare una soluzione. Anche perchè il codice a cui sto cercando di apportare la modifica è diverso da quello che avevo postato così giusto per far capire la "tipologia" del mio problema.

Ecco il codice (una funzione) in cui devo risolvere il problema di fgets/scanf:

void inserisci(void) {
     int i;
     int dim;
     
     printf("\nQuanti esami hai superato? ");
     scanf("%d",&dim);
     for (i=0; i<dim; i++) { //Lettura da tastiera dei campi del libretto
         printf("\nEsame: ");
         fgets(esami[i].nome, 20, stdin);
         //scanf("%s", &esami[i].nome);
         printf("CFU: ");
         scanf("%d", &esami[i].cfu);
         do { //Ripetizione della richiesta del voto fin quando viene inserito un numero minore di 18 oppure maggiore di 30
             printf("Voto: ");
             scanf("%d", &esami[i].voto);
             if (esami[i].voto<18 || esami[i].voto>30) printf("\nVoto non valido. Inserisci un voto compreso tra 18 e 30\n\n"); 
         } while (esami[i].voto<18 || esami[i].voto>30);
         printf("\n");
     }
}

ilario991
"smartmouse":
Scusami tanto ma non avevo fatto caso che c'era una seconda pagina del topic e che mi avevi risposto!

Ho capito che c'è un problema nell'utilizzare fgets e scanf insieme, ma non sono riuscito ad applicare una soluzione. Anche perchè il codice a cui sto cercando di apportare la modifica è diverso da quello che avevo postato così giusto per far capire la "tipologia" del mio problema.

Ecco il codice (una funzione) in cui devo risolvere il problema di fgets/scanf:

void inserisci(void) {
     int i;
     int dim;
     
     printf("\nQuanti esami hai superato? ");
     scanf("%d",&dim);
     for (i=0; i<dim; i++) { //Lettura da tastiera dei campi del libretto
         printf("\nEsame: ");
         fgets(esami[i].nome, 20, stdin);
         //scanf("%s", &esami[i].nome);
         printf("CFU: ");
         scanf("%d", &esami[i].cfu);
         do { //Ripetizione della richiesta del voto fin quando viene inserito un numero minore di 18 oppure maggiore di 30
             printf("Voto: ");
             scanf("%d", &esami[i].voto);
             if (esami[i].voto<18 || esami[i].voto>30) printf("\nVoto non valido. Inserisci un voto compreso tra 18 e 30\n\n"); 
         } while (esami[i].voto<18 || esami[i].voto>30);
         printf("\n");
     }
}


Ciao :)
Ti dico brevemente dove sta il problema:

Quando fai una scanf (per esempio scanf("%d",&var)) la scanf ti legge il numero (nell'esempio un intero) ma lascia nel buffer il '\n' che hai dato per inserire l'input.
Quando poi successivamente chiami una gets questa vede il '\n' nel buffer e termina subito.
Soluzione? usare fflush(stdin); Mettilo prima della fgets

smartmouse
Cavoli, io non capivo perchè vedevo che la fgets veniva PRIMA della scanf... invece era il contrario perchè avevo dimenticato la scanf per la richiesta del numero degli esami!
Ora è tutto chiaro!

Infine, la tua soluzione è stata geniale! Senza conoscere fflush, per intuito ho capito che "pulisce" il buffer di lettura (stdin), è così?

ilario991
"smartmouse":
Cavoli, io non capivo perchè vedevo che la fgets veniva PRIMA della scanf... invece era il contrario perchè avevo dimenticato la scanf per la richiesta del numero degli esami!
Ora è tutto chiaro!

Infine, la tua soluzione è stata geniale! Senza conoscere fflush, per intuito ho capito che "pulisce" il buffer di lettura (stdin), è così?

Sì.
http://www.cplusplus.com/reference/clib ... io/fflush/

apatriarca
If it was open for reading and the last operation was an input operation, the behavior depends on the specific library implementation. In some implementations this causes the input buffer to be cleared, but this is not standard behavior.

fflush(stdin) ha un comportamento che dipende dal compilatore e libreria utilizzata quando usato su buffer di lettura (come stdin). Sarebbe meglio non appoggiarsi a funzioni il cui comportamento non è ben definito. Ed è in effetti la ragione per cui non l'avevo consigliato fin da subito. Ma una funzione che ignora il resto della riga corrente è molto facile da implementare:
void ignore_line(FILE *file)
{
    int c;
    do {
        c = getc(file);
    } while (c != '\n' && c != EOF);
}

ilario991
"apatriarca":
If it was open for reading and the last operation was an input operation, the behavior depends on the specific library implementation. In some implementations this causes the input buffer to be cleared, but this is not standard behavior.

fflush(stdin) ha un comportamento che dipende dal compilatore e libreria utilizzata quando usato su buffer di lettura (come stdin). Sarebbe meglio non appoggiarsi a funzioni il cui comportamento non è ben definito. Ed è in effetti la ragione per cui non l'avevo consigliato fin da subito. Ma una funzione che ignora il resto della riga corrente è molto facile da implementare:
void ignore_line(FILE *file)
{
    int c;
    do {
        c = getc(file);
    } while (c != '\n' && c != EOF);
}


:? Mah... fflush è una funzione STANDARD del C ed è sempre consigliabile usare quelle standard piuttosto che implementarne di proprie per garantire la portabilità

apatriarca
:? Mah... fflush è una funzione STANDARD del C ed è sempre consigliabile usare quelle standard piuttosto che implementarne di proprie per garantire la portabilità

La citazione che ho inserito viene dal link che hai inserito ed è presente anche nello standard. fflush su input buffer è undefined behaviour. NON E' IN NESSUN MODO PORTABILE! Ogni compilatore è libero di IMPLEMENTARE quella funzione COME DESIDERA. Al contrario la mia funzione è PERFETTAMENTE PORTABILE su ogni piattaforma in cui è presente un compilatore C standard. Fa infatti uso semplicemente del comportamento di getc (che è completamente definito) e delle costanti EOF e '\n' che sono definite nello standard. Ci sono diversi punti oscuri o poco precisati nello standard C, questo è uno di quelli.

smartmouse
Innanzitutto vi dico che le vostre discussioni mi fanno sentire meglio, perchè capisco che ho a che fare con persone davvero competenti!
...e per questo non smetterò mai di ringraziare voi e matematicamente.it tutto!

Tornando al mio problema, ormai risolto, ho solo una domanda per apatriarca: come utilizzo quella funzione nel mio programma?

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.