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 ).
Réponses
Trop de publicités?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.
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
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)
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.