Dans cette réponse, je vais supposer que vous lisez et interprétez des lignes de texte . Vous invitez peut-être l'utilisateur, qui tape quelque chose et appuie sur RETOUR. Ou peut-être que vous lisez des lignes de texte structuré à partir d'un fichier de données quelconque.
Puisque vous lisez des lignes de texte, il est logique d'organiser votre code autour d'une fonction de bibliothèque qui lit, eh bien, une ligne de texte. La fonction Standard est fgets()
, bien qu'il y en ait d'autres (y compris getline
). Et puis l'étape suivante consiste à interpréter cette ligne de texte d'une manière ou d'une autre.
Voici la recette de base pour appeler fgets
pour lire une ligne de texte:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Cela lit simplement une ligne de texte et l'imprime. Tel qu'il est écrit, il a quelques limitations, que nous verrons dans une minute. Il a également une très grande fonctionnalité: le nombre 512 que nous avons passé comme deuxième argument fgets
est la taille du tableau dans
line
lequel nous demandons fgets
de lire. Ce fait - que nous pouvons dire fgets
combien il est autorisé à lire - signifie que nous pouvons être sûrs que fgets
le tableau ne débordera pas en y lisant trop.
Alors maintenant, nous savons lire une ligne de texte, mais que faire si nous voulions vraiment lire un entier, ou un nombre à virgule flottante, ou un seul caractère, ou un seul mot? (Autrement dit, si l'
scanf
appel que nous essayons d'améliorer avait été l' aide d' un spécificateur de format comme %d
, %f
, %c
ou %s
?)
Il est facile de réinterpréter une ligne de texte - une chaîne - comme n'importe laquelle de ces choses. Pour convertir une chaîne en entier, la façon la plus simple (mais imparfaite) de le faire est d'appeler atoi()
. Pour convertir en nombre à virgule flottante, il y a atof()
. (Et il existe également de meilleures façons, comme nous le verrons dans une minute.) Voici un exemple très simple:
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);
Si vous vouliez que l'utilisateur tape un seul caractère (peut y
- être ou
n
comme réponse oui / non), vous pouvez littéralement simplement saisir le premier caractère de la ligne, comme ceci:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Cela ignore, bien sûr, la possibilité que l'utilisateur tape une réponse à plusieurs caractères; il ignore silencieusement tous les caractères supplémentaires qui ont été saisis.)
Enfin, si vous vouliez que l'utilisateur tape une chaîne ne contenant certainement pas d' espace, si vous vouliez traiter la ligne d'entrée
hello world!
comme la chaîne "hello"
suivie par autre chose (ce que le scanf
format %s
aurait fait), eh bien, dans ce cas, j'ai un peu tordu, ce n'est pas si facile de réinterpréter la ligne de cette façon, après tout, donc la réponse à cela une partie de la question devra attendre un peu.
Mais je veux d'abord revenir sur trois choses que j'ai ignorées.
(1) Nous avons appelé
fgets(line, 512, stdin);
à lire dans le tableau line
, et où 512 est la taille du tableau sait line
donc fgets
ne pas le déborder. Mais pour vous assurer que 512 est le bon nombre (en particulier, pour vérifier si quelqu'un a peut-être modifié le programme pour changer la taille), vous devez relire où il a line
été déclaré. C'est une nuisance, il existe donc deux bien meilleures façons de synchroniser les tailles. Vous pouvez, (a) utiliser le préprocesseur pour donner un nom à la taille:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Ou, (b) utilisez l' sizeof
opérateur de C :
fgets(line, sizeof(line), stdin);
(2) Le deuxième problème est que nous n'avons pas recherché d'erreur. Lorsque vous lisez une entrée, vous devez toujours vérifier la possibilité d'erreur. Si, pour une raison quelconque, fgets
vous ne pouvez pas lire la ligne de texte à laquelle vous lui avez demandé, cela indique cela en renvoyant un pointeur nul. Nous aurions donc dû faire des choses comme
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Enfin, il y a le problème que, pour lire une ligne de texte,
fgets
lit les caractères et les remplit dans votre tableau jusqu'à ce qu'il trouve le \n
caractère qui termine la ligne, et il remplit également le \n
caractère dans votre tableau . Vous pouvez le voir si vous modifiez légèrement notre exemple précédent:
printf("you typed: \"%s\"\n", line);
Si je lance ceci et tape "Steve" quand il me le demande, il s'imprime
you typed: "Steve
"
Cela "
sur la deuxième ligne est dû au fait que la chaîne lue et imprimée était en fait "Steve\n"
.
Parfois, cette nouvelle ligne supplémentaire n'a pas d'importance (comme lorsque nous avons appelé
atoi
ou atof
, car ils ignorent tous les deux les entrées non numériques supplémentaires après le numéro), mais parfois cela compte beaucoup. Si souvent, nous voulons supprimer cette nouvelle ligne. Il y a plusieurs façons de le faire, que j'aborderai dans une minute. (Je sais que j'ai souvent dit cela. Mais je reviendrai à toutes ces choses, je le promets.)
À ce stade, vous pensez peut-être: "Je pensais que vous aviez dit que ce scanf
n'était pas bon, et cette autre façon serait tellement mieux. Mais cela fgets
commence à ressembler à une nuisance. Appeler scanf
était si facile ! Je ne peux pas continuer à l'utiliser? "
Bien sûr, vous pouvez continuer à utiliser scanf
, si vous le souhaitez. (Et pour
des choses vraiment simples, à certains égards, c'est plus simple.) Mais, s'il vous plaît, ne venez pas me pleurer quand il vous échoue en raison de l'un de ses 17 caprices et faiblesses, ou entre dans une boucle infinie à cause de l'entrée de votre ne vous attendiez pas, ou quand vous ne pouvez pas comprendre comment l'utiliser pour faire quelque chose de plus compliqué. Et regardons fgets
les nuisances réelles de:
Vous devez toujours spécifier la taille du tableau. Eh bien, bien sûr, ce n'est pas du tout une nuisance - c'est une fonctionnalité, car le débordement de tampon est vraiment une mauvaise chose.
Vous devez vérifier la valeur de retour. En fait, c'est un lavage, car pour l'utiliser scanf
correctement, vous devez également vérifier sa valeur de retour.
Vous devez retirer le \n
dos. C'est, je l'avoue, une véritable nuisance. J'aurais aimé qu'il y ait une fonction standard sur laquelle je pourrais vous indiquer qui n'a pas eu ce petit problème. (S'il vous plaît, personne n'évoque gets
.) Mais par rapport à scanf's
17 nuisances différentes, je prendrai celle-là de fgets
n'importe quel jour.
Alors, comment voulez - vous supprimer cette nouvelle ligne? Trois façons:
(a) Manière évidente:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) Façon délicate et compacte:
strtok(line, "\n");
Malheureusement, celui-ci ne fonctionne pas toujours.
(c) Une autre manière compacte et légèrement obscure:
line[strcspn(line, "\n")] = '\0';
Et maintenant que c'est terminé, nous pouvons revenir à une autre chose que j'ai ignorée: les imperfections de atoi()
et atof()
. Le problème avec ceux-ci est qu'ils ne vous donnent aucune indication utile de succès ou d'échec: ils ignorent tranquillement l'entrée non numérique de fin et ils retournent tranquillement 0 s'il n'y a pas d'entrée numérique du tout. Les alternatives préférées - qui présentent également certains autres avantages - sont strtol
et strtod
.
strtol
vous permet également d'utiliser une base autre que 10, ce qui signifie que vous pouvez obtenir l'effet (entre autres) %o
ou %x
avecscanf
. Mais montrer comment utiliser correctement ces fonctions est une histoire en soi, et serait trop distraire de ce qui se transforme déjà en un récit assez fragmenté, donc je ne vais pas en dire plus à ce sujet maintenant.
Le reste de la narration principale concerne une entrée que vous essayez d'analyser, c'est plus compliqué qu'un simple chiffre ou caractère. Que faire si vous souhaitez lire une ligne contenant deux nombres, ou plusieurs mots séparés par des espaces, ou une ponctuation de cadrage spécifique? C'est là que les choses deviennent intéressantes, et où les choses devenaient probablement compliquées si vous essayiez de faire des choses en utilisant scanf
, et où il y a beaucoup plus d'options maintenant que vous avez lu proprement une ligne de texte en utilisant fgets
, bien que l'histoire complète de toutes ces options pourrait probablement remplir un livre, donc nous allons seulement pouvoir gratter la surface ici.
Ma technique préférée est de diviser la ligne en «mots» séparés par des espaces, puis de faire quelque chose de plus avec chaque «mot». L'une des principales fonctions standard pour ce faire est
strtok
(qui a également ses problèmes, et qui évalue également toute une discussion séparée). Ma préférence est une fonction dédiée pour construire un tableau de pointeurs vers chaque "mot" séparé, une fonction que je décris dans
ces notes de cours . Quoi qu'il en soit, une fois que vous avez des "mots", vous pouvez poursuivre le traitement de chacun, peut-être avec les mêmes fonctions atoi
/ atof
/ strtol
/ que strtod
nous avons déjà examinées.
Paradoxalement, même si nous avons passé scanf
pas mal de temps et d'efforts ici à trouver un moyen de nous éloigner , une autre bonne façon de gérer la ligne de texte que nous venons de lire
fgets
est de la transmettre sscanf
. De cette façon, vous vous retrouvez avec la plupart des avantages de scanf
, mais sans la plupart des inconvénients.
Si votre syntaxe d'entrée est particulièrement compliquée, il peut être approprié d'utiliser une bibliothèque "regexp" pour l'analyser.
Enfin, vous pouvez utiliser toutes les solutions d'analyse ad hoc qui vous conviennent. Vous pouvez parcourir la ligne un caractère à la fois avec un
char *
pointeur vérifiant les caractères que vous attendez. Ou vous pouvez rechercher des caractères spécifiques en utilisant des fonctions comme strchr
ou strrchr
, ou strspn
ou strcspn
, ou strpbrk
. Ou vous pouvez analyser / convertir et ignorer des groupes de caractères numériques à l'aide des fonctions strtol
ou
strtod
que nous avons ignorées plus tôt.
Il y a évidemment beaucoup plus à dire, mais j'espère que cette introduction vous aidera à démarrer.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2
ne détecte pas aussi mauvais le texte non numérique de fin.