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 fgets
per 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
line
che stiamo chiedendo fgets
di leggere. Questo fatto - che possiamo dire fgets
quanto è permesso leggere - significa che possiamo essere sicuri che fgets
non 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
scanf
chiamata 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
n
come 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 scanf
formato %s
avrebbe 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 line
e dove 512 ha le dimensioni dell'array, line
quindi fgets
sa 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 line
stato 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' sizeof
operatore 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 fgets
non è 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,
fgets
legge i caratteri e li riempie nel tuo array fino a quando non trova il \n
carattere che termina la linea e riempie anche il \n
personaggio 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
atoi
oatof
, 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 fgets
sta iniziando a sembrare un fastidio. Chiamare è scanf
stato 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 fgets
fastidi 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 scanf
correttamente, devi controllare anche il suo valore di ritorno.
Devi spogliare la \n
schiena. 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's
17 diversi fastidi, prenderò questo fastidio di fgets
ogni 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 strtol
e strtod
.
strtol
ti permette anche di usare una base diversa da 10, il che significa che puoi ottenere l'effetto (tra le altre cose) %o
o %x
conscanf
. 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 strchr
o strrchr
, oppure strspn
oppure strcspn
, o strpbrk
. Oppure puoi analizzare / convertire e saltare gruppi di caratteri numerici usando le funzioni strtol
o
strtod
che 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)) != 2
non rileva come male il testo non numerico finale.