277 votes

Comment analyser une chaîne de caractères vers un nombre entier en C++ ?

Quelle est la façon C++ d'analyser une chaîne de caractères (donnée comme char *) en un int ? Une gestion robuste et claire des erreurs est un plus (au lieu de renvoyant le zéro ).

221voto

Dan Moulding Points 46866

Ce qu'il ne faut pas faire

Voici mon premier conseil : n'utilisez pas stringstream pour cela . Si, à première vue, il semble simple à utiliser, vous vous rendrez compte que vous devez effectuer beaucoup de travail supplémentaire si vous souhaitez obtenir une certaine robustesse et une bonne gestion des erreurs.

Voici une approche qui, intuitivement, semble devoir fonctionner :

bool str2int (int &i, char const *s)
{
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail()) {
        // not an integer
        return false;
    }
    return true;
}

Il y a un problème majeur : str2int(i, "1337h4x0r") reviendra volontiers true y i obtiendra la valeur 1337 . Nous pouvons contourner ce problème en nous assurant qu'il n'y a pas d'autres caractères dans le champ stringstream après la conversion :

bool str2int (int &i, char const *s)
{
    char              c;
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail() || ss.get(c)) {
        // not an integer
        return false;
    }
    return true;
}

Nous avons réglé un problème, mais il y en a encore deux autres.

Que faire si le nombre dans la chaîne n'est pas en base 10 ? Nous pouvons essayer de prendre en compte d'autres bases en réglant le flux sur le mode correct (par ex. ss << std::hex ) avant de tenter la conversion. Mais cela signifie que l'appelant doit savoir a priori quelle est la base du numéro et comment l'appelant peut-il le savoir ? L'appelant ne sait pas encore quel est le numéro. Il ne sait même pas qu'il est un nombre ! Comment peut-on s'attendre à ce qu'ils sachent de quelle base il s'agit ? Nous pourrions simplement exiger que tous les nombres entrés dans nos programmes soient en base 10 et rejeter les entrées hexadécimales ou octales comme non valides. Mais cela n'est ni très souple ni très robuste. Il n'existe pas de solution simple à ce problème. Vous ne pouvez pas simplement essayer la conversion une fois pour chaque base, car la conversion décimale réussira toujours pour les nombres octaux (avec un zéro en tête) et la conversion octale peut réussir pour certains nombres décimaux. Vous devez donc maintenant vérifier la présence d'un zéro initial. Mais attendez ! Les nombres hexadécimaux peuvent aussi commencer par un zéro initial (0x...). Soupir.

Même si vous parvenez à résoudre les problèmes ci-dessus, il reste un autre problème plus important : que se passe-t-il si l'appelant doit faire la distinction entre une mauvaise entrée (par exemple "123foo") et un nombre qui n'est pas dans la plage de l'option int (par exemple, "4000000000" pour le 32-bit int ) ? Avec stringstream il n'y a aucun moyen de faire cette distinction. Nous savons seulement si la conversion a réussi ou échoué. Si elle échoue, nous n'avons aucun moyen de savoir por qué il a échoué. Comme vous pouvez le voir, stringstream laisse beaucoup à désirer si vous voulez de la robustesse et une gestion claire des erreurs.

Cela m'amène à mon deuxième conseil : n'utilisez pas de Boost's lexical_cast pour cette . Considérez ce que le lexical_cast la documentation doit dire :

Lorsqu'un degré de contrôle plus élevé est sur les conversions, std::stringstream et std::wstringstream offrent un chemin plus chemin plus approprié. Lorsque conversions non basées sur le flux sont nécessaires, lexical_cast n'est pas le bon outil outil pour ce travail et n'est pas pour de tels scénarios.

Quoi ? Nous avons déjà vu que stringstream a un faible niveau de contrôle, et pourtant il dit stringstream doit être utilisé à la place de lexical_cast si vous avez besoin d'un "niveau de contrôle plus élevé". En outre, parce que lexical_cast est juste une enveloppe autour de stringstream il souffre des mêmes problèmes que stringstream fait : mauvaise prise en charge de plusieurs bases de données numériques et mauvaise gestion des erreurs.

La meilleure solution

Heureusement, quelqu'un a déjà résolu tous ces problèmes. La bibliothèque standard du C contient strtol et la famille qui n'ont aucun de ces problèmes.

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2int (int &i, char const *s, int base = 0)
{
    char *end;
    long  l;
    errno = 0;
    l = strtol(s, &end, base);
    if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
        return OVERFLOW;
    }
    if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
        return UNDERFLOW;
    }
    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }
    i = l;
    return SUCCESS;
}

Plutôt simple pour quelque chose qui gère tous les cas d'erreur et qui prend en charge toutes les bases numériques de 2 à 36. Si base est zéro (la valeur par défaut), il essaiera de convertir à partir de n'importe quelle base. Ou l'appelant peut fournir le troisième argument et spécifier que la conversion ne doit être tentée que pour une base particulière. Il est robuste et traite toutes les erreurs avec un minimum d'effort.

Autres raisons de préférer strtol (et la famille) :

  • Il expose beaucoup mieux performances en cours d'exécution
  • Il introduit moins de frais généraux au moment de la compilation (les autres utilisent près de 20 fois plus de SLOC pour les en-têtes).
  • Il en résulte la plus petite taille de code

Il n'y a absolument aucune raison valable d'utiliser une autre méthode.

177voto

CC. Points 502

Dans le nouveau C++11, il existe des fonctions pour cela : stoi, stol, stoll, stoul et ainsi de suite.

int myNr = std::stoi(myString);

Une exception sera levée en cas d'erreur de conversion.

Même ces nouvelles fonctions ont toujours le même problème comme l'a noté Dan : ils convertiront volontiers la chaîne "11x" en entier "11".

Voir plus : http://en.cppreference.com/w/cpp/string/basic_string/stol

70voto

Luka Marinko Points 1184

C'est une méthode plus sûre que atoi().

const char* str = "123";
int i;

if(sscanf(str, "%d", &i)  == EOF )
{
   /* error */
}

C++ avec bibliothèque standard chaîne de caractères (merci) CMS )

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  if((ss >> num).fail())
  { 
      //ERROR 
  }
  return num;
}

Avec booster bibliothèque : (merci jk )

#include <boost/lexical_cast.hpp>
#include <string>

try
{
    std::string str = "123";
    int number = boost::lexical_cast< int >( str );
}
catch( const boost::bad_lexical_cast & )
{
    // Error
}

Edit : Corrigé la version stringstream pour qu'elle gère les erreurs. (merci aux commentaires de CMS et de jk sur le post original)

22voto

Chris Arguin Points 6469

La bonne vieille méthode C fonctionne toujours. Je recommande strtol ou strtoul. Entre le statut de retour et le 'endPtr', vous pouvez donner une bonne sortie de diagnostic. Il gère également les bases multiples de manière agréable.

21voto

jk. Points 4421

Vous pouvez utiliser Boost's lexical_cast qui enveloppe ce dans une interface plus générique. lexical_cast<Target>(Source) jette bad_lexical_cast en cas d'échec.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X