In questa risposta suppongo che stai leggendo e interpretando righe di testo . Forse stai chiedendo all'utente, che sta scrivendo qualcosa e sta colpendo INVIO. O forse stai leggendo righe di testo strutturato da un file di dati di qualche tipo.
Dato che stai leggendo righe di testo, ha senso organizzare il tuo codice attorno a una funzione di libreria che legge, bene, una riga di testo. La funzione Standard è fgets(), sebbene ce ne siano altre (incluso getline). E poi il passo successivo è interpretare quella riga di testo in qualche modo.
Ecco la ricetta di base per chiamare fgetsper leggere una riga di testo:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Questo legge semplicemente in una riga di testo e lo stampa di nuovo. Come scritto ha un paio di limitazioni, che vedremo tra un minuto. Ha anche una grande funzionalità: quel numero 512 che abbiamo passato come secondo argomento fgetsè la dimensione dell'array
lineche stiamo chiedendo fgetsdi leggere. Questo fatto - che possiamo dire fgetsquanto è permesso leggere - significa che possiamo essere sicuri che fgetsnon traboccherà l'array leggendo troppo in esso.
Quindi ora sappiamo come leggere una riga di testo, ma cosa accadrebbe se volessimo davvero leggere un numero intero, un numero in virgola mobile, un singolo carattere o una sola parola? (Cioè, che cosa succede se la
scanfchiamata che stiamo cercando di migliorare era stato utilizzando un identificatore di formato come %d, %f, %c, o %s?)
È facile reinterpretare una riga di testo - una stringa - come una di queste cose. Per convertire una stringa in un numero intero, il modo più semplice (sebbene imperfetto) per farlo è chiamare atoi(). Per convertire in un numero in virgola mobile, c'è atof(). (E ci sono anche modi migliori, come vedremo tra un minuto.) Ecco un esempio molto semplice:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
Se si desidera che l'utente digiti un singolo carattere (forse y o
ncome risposta sì / no), puoi letteralmente prendere il primo carattere della riga, in questo modo:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Questo ignora, ovviamente, la possibilità che l'utente abbia digitato una risposta multi-carattere; ignora tranquillamente tutti i caratteri extra che sono stati digitati.)
Infine, se si desidera che l'utente digiti una stringa sicuramente no contenente spazi bianchi, se si desidera trattare la riga di input
hello world!
come la stringa "hello" seguita da qualcos'altro (che è ciò che il scanfformato %savrebbe fatto), beh, in quel caso, mi sono un po 'piegato, non è così facile reinterpretare la linea in quel modo, dopo tutto, quindi la risposta a questa parte della domanda dovrà aspettare un po '.
Ma prima voglio tornare a tre cose che ho saltato.
(1) Abbiamo chiamato
fgets(line, 512, stdin);
da leggere nell'array linee dove 512 ha le dimensioni dell'array, linequindi fgetssa di non sovraccaricarlo. Ma per assicurarti che 512 sia il numero giusto (in particolare, per verificare se qualcuno ha modificato il programma per modificarne le dimensioni), devi rileggere ovunque sia linestato dichiarato. Questo è un fastidio, quindi ci sono due modi molto migliori per mantenere sincronizzate le dimensioni. È possibile, (a) utilizzare il preprocessore per creare un nome per la dimensione:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Oppure, (b) usa l' sizeofoperatore di C :
fgets(line, sizeof(line), stdin);
(2) Il secondo problema è che non abbiamo verificato errori. Quando leggi l'input, dovresti sempre verificare la possibilità di errore. Se per qualsiasi motivo fgetsnon è possibile leggere la riga di testo richiesta, indica questo restituendo un puntatore null. Quindi avremmo dovuto fare cose del genere
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Infine, c'è il problema che, al fine di leggere una riga di testo,
fgetslegge i caratteri e li riempie nel tuo array fino a quando non trova il \ncarattere che termina la linea e riempie anche il \npersonaggio nel tuo array . Puoi vederlo se modifichi leggermente il nostro esempio precedente:
printf("you typed: \"%s\"\n", line);
Se eseguo questo e digito "Steve" quando mi viene richiesto, viene stampato
you typed: "Steve
"
Quello "sulla seconda riga è perché la stringa che leggeva e stampava era in realtà"Steve\n" .
A volte quella nuova linea extra non ha importanza (come quando abbiamo chiamato
atoioatof , poiché entrambi ignorano qualsiasi input non numerico aggiuntivo dopo il numero), ma a volte importa molto. Così spesso vorremmo togliere quella nuova riga. Ci sono molti modi per farlo, che vedrò tra un minuto. (So di averlo detto molto. Ma tornerò su tutte queste cose, lo prometto.)
A questo punto, potresti pensare: "Pensavo che avessi detto che scanf
non andava bene, e che l'altro modo sarebbe molto meglio. Ma fgetssta iniziando a sembrare un fastidio. Chiamare è scanfstato così facile ! Non posso continuare a usarlo? "
Certo, puoi continuare a usare scanf, se vuoi. (E per
cose davvero semplici, in un certo senso è più semplice.) Ma, per favore, non venire a piangere da me quando ti fallisce a causa di uno dei suoi 17 capricci e debolezze, o va in un ciclo infinito a causa dell'input del tuo non mi aspettavo, o quando non riesci a capire come usarlo per fare qualcosa di più complicato. E diamo un'occhiata ai fgetsfastidi reali:
Devi sempre specificare la dimensione dell'array. Beh, ovviamente, non è affatto un fastidio: è una caratteristica, perché l'overflow del buffer è una cosa davvero brutta.
Devi controllare il valore di ritorno. In realtà, è un lavaggio, perché per usarlo scanfcorrettamente, devi controllare anche il suo valore di ritorno.
Devi spogliare la \nschiena. Questo, lo ammetto, è un vero fastidio. Vorrei che ci fosse una funzione standard che potrei indicarti che non ha avuto questo piccolo problema. (Per favore nessuno solleva gets.) Ma rispetto a scanf's17 diversi fastidi, prenderò questo fastidio di fgetsogni giorno.
Quindi, come si fa si striscia che a capo? Tre modi:
a) modo ovvio:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) Modo complicato e compatto:
strtok(line, "\n");
Purtroppo questo non funziona sempre.
(c) Un altro modo compatto e leggermente oscuro:
line[strcspn(line, "\n")] = '\0';
E ora che è fuori mano, possiamo tornare a un'altra cosa che ho ignorato: le imperfezioni di atoi()e atof(). Il problema con questi è che non ti danno alcuna indicazione utile di successo o fallimento: ignorano silenziosamente l'input non numerico finale e restituiscono tranquillamente 0 se non c'è alcun input numerico. Le alternative preferite - che presentano anche altri vantaggi - sono strtole strtod.
strtolti permette anche di usare una base diversa da 10, il che significa che puoi ottenere l'effetto (tra le altre cose) %oo %xconscanf. Ma mostrare come usare correttamente queste funzioni è una storia in sé, e sarebbe troppo una distrazione da ciò che si sta già trasformando in una narrazione piuttosto frammentata, quindi non parlerò più di loro adesso.
Il resto della narrativa principale riguarda input che potresti cercare di analizzare, il che è più complicato di un singolo numero o personaggio. Cosa succede se si desidera leggere una riga contenente due numeri, o più parole separate da spazi bianchi o punteggiatura di inquadratura specifica? È qui che le cose diventano interessanti e dove le cose probabilmente si stavano complicando se stavi cercando di fare le cose usando scanf, e dove ora ci sono molte più opzioni che hai letto in modo pulito una riga di testo fgets, sebbene l'intera storia su tutte quelle opzioni potremmo probabilmente riempire un libro, quindi potremo solo grattare la superficie qui.
La mia tecnica preferita è quella di spezzare la linea in "parole" separate da spazi, quindi fare qualcosa di più con ogni "parola". Una delle principali funzioni standard per farlo è
strtok(che ha anche i suoi problemi e che valuta anche una discussione completamente separata). La mia preferenza è una funzione dedicata per costruire una serie di puntatori per ogni "parola" spezzata, una funzione che descrivo in
queste note del corso . Ad ogni modo, una volta che hai "parole", puoi ulteriormente elaborare ciascuna, forse con le stesse atoi/ atof/ strtol/ strtod
funzioni che abbiamo già visto.
Paradossalmente, anche se qui abbiamo trascorso un bel po 'di tempo e sforzi per capire come allontanarci scanf, un altro ottimo modo per gestire la linea di testo che abbiamo appena letto
fgetsè passarlo sscanf. In questo modo, si ottiene la maggior parte dei vantaggi scanf, ma senza la maggior parte degli svantaggi.
Se la sintassi di input è particolarmente complicata, potrebbe essere opportuno utilizzare una libreria "regexp" per analizzarla.
Infine, puoi utilizzare qualsiasi soluzione di analisi ad hoc adatta a te. Puoi spostarti attraverso la linea di un personaggio alla volta con un
char *puntatore che controlla i caratteri che ti aspetti. Oppure puoi cercare caratteri specifici usando funzioni come strchro strrchr, oppure strspnoppure strcspn, o strpbrk. Oppure puoi analizzare / convertire e saltare gruppi di caratteri numerici usando le funzioni strtolo
strtodche abbiamo ignorato in precedenza.
C'è ovviamente molto di più che si possa dire, ma spero che questa introduzione possa iniziare.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2non rileva come male il testo non numerico finale.