2011-02-08 4 views
3

Ceci est pour une tâche de devoir pour trier certaines chaînes données. Je demande à l'utilisateur le nombre de chaînes qu'il souhaite trier avec scanf, en allouant un tableau basé sur ce nombre, puis en obtenant les chaînes elles-mêmes avec fgets.Problème avec scanf et fgets

Tout fonctionne correctement si le nombre de chaînes est codé en dur, mais l'ajout de scanf permet à l'utilisateur de décider de tout vider. Voici le code:

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 

#define LENGTH 20 // Maximum string length. 

int main(void) 
{ 
    int index, numStrings = 0; 
    char **stringArray; 
    printf("Input the number of strings that you'd like to sort: "); 
    assert(scanf("%d", &numStrings) == 1); 
    stringArray = (char **)malloc(numStrings * sizeof(char *)); 

    for (index = 0; index < numStrings; index++) 
    { 
     stringArray[index] = (char *)malloc(LENGTH * sizeof(char)); 
     assert(stringArray[index] != NULL); 
     printf("Input string: "); 
     assert(fgets(stringArray[index], LENGTH, stdin) != NULL); 
    } 

    // Sort strings, free allocated memory. 

    return 0; 
} 

Et voici ce que la console ressemble:

 
Input the number of strings that you'd like to sort: 3 
Input string: Input string: foo 
Input string: bar 

Il saute sur la première itération de la boucle, ce qui entraîne une chaîne vide au début du tableau. Ma question est la suivante: pourquoi fait-elle cela et comment puis-je y remédier?


Voici ce que la console regarde avec la chaîne de format "%d\n" passé à scanf:

 
Input the number of strings that you'd like to sort: 3 
foo 
Input string: Input string: bar 
Input string: baz 

Donc, je peux entrer toutes les chaînes, mais la première invite pour une chaîne est au mauvais endroit .

+4

N'affirmez pas() 'sur des fonctions dont on peut raisonnablement penser qu'elles échoueront, telles que des fonctions d'entrée qui lisent l'entrée de l'utilisateur. C'est mieux que d'ignorer l'erreur; ce n'est pas aussi bon que de gérer correctement l'erreur. –

+0

@Jonathan Merci pour le conseil. – gdejohn

Répondre

3

La vraie réponse (dans mon opinion humble mais toujours correcte: P) est de ne pas utiliser scanf. Utilisez fgets pour lire la première ligne (c'est-à-dire le numéro) et ensuite analyser cette chaîne avec, disons, sscanf ou strtoul. De cette façon, vous avez la possibilité de gérer les erreurs lorsque quelqu'un ne saisit pas les données dans un format sympa, et vous n'avez pas à contourner le manque de gestion robuste des espaces blancs de scanf.

De plus, n'utilisez jamais un int pour stocker des tailles, sauf si vous prévoyez d'avoir beaucoup de matrices de -4 longueur. La norme spécifie le type non signé size_t en tant que type non signé suffisamment grand pour stocker les tailles d'objet et les indices de tableau. L'utilisation de tout autre type n'est pas garantie de fonctionner.

+0

De quelle partie de mon code faites-vous référence lorsque vous dites ne pas utiliser un int pour stocker des tailles? – gdejohn

+0

@Charlatan - 'numberOfStrings' spécifiquement, et vraiment tout ce que vous passez à' malloc', recevez de 'strlen' ou utilisez pour indexer un tableau devrait être' size_t'. –

+0

Alors, comment recommandez-vous que j'obtienne quelque chose de type 'size_t' pour passer à' malloc' de 'stdin'? Parse un int et le jeter? Je suis allé avec votre réponse, d'ailleurs (utilisé 'atoi'), et ça fonctionne bien maintenant. – gdejohn

6

Vous devez dire scanf à écraserait \ n en mettant \ n dans le scanf:

scanf("%d\n", &numStrings) 

sans elle, scanf lit le caractère de nouvelle ligne résiduelle [du moment où le bouton d'entrée a été frappé] comme première ligne dans la boucle

+1

Quelques autres commentaires à ajouter à la réponse de Foo Bah: Un problème dans votre code est que vous ne devriez rien faire avec des effets secondaires dans 'assert'; les choses à l'intérieur de 'assert' ne sont pas lancées en mode release. Vous pouvez également mettre 'fflush (stdout);' après votre 'printf' pour vous assurer que l'invite est imprimée avant que l'utilisateur ne soit invité à entrer. –

+0

Notez également que les fgets incluront le caractère de fin de ligne si votre chaîne n'est pas trop longue. –

+0

Et, si la chaîne est trop longue, 'fgets()' laissera les caractères restants de la ligne à lire lors du prochain appel à l'une des fonctions d'E/S standard. –