web-dev-qa-db-fra.com

Comment analyser une chaîne à un int en C ++?

Quelle est la manière C++ d'analyser une chaîne (donnée sous la forme char *) dans un int? Une gestion d'erreur claire et robuste est un plus (au lieu de zéro retourné ).

256
Eugene Yokota

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

int myNr = std::stoi(myString);

Il lève une exception en cas d'erreur de conversion.

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

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

155
CC.

Ce qu'il ne faut pas faire

Voici mon premier conseil: n'utilisez pas stringstream pour cela . Bien qu'au début cela puisse sembler simple à utiliser, vous constaterez que vous devez faire beaucoup de travail supplémentaire si vous voulez de la robustesse et une bonne gestion des erreurs.

Voici une approche qui semble intuitivement 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;
}

Cela pose un problème majeur: str2int(i, "1337h4x0r") retournera avec plaisir true et i obtiendra la valeur 1337. Nous pouvons contourner ce problème en vérifiant qu'il n'y a plus de caractères dans 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ésolu un problème, mais il reste encore quelques problèmes.

Que se passe-t-il si le nombre dans la chaîne n'est pas en base 10? Nous pouvons essayer de prendre en charge d'autres bases en réglant le flux sur le mode correct (par exemple ss << std::hex) avant d'essayer 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. Ils ne savent même pas que est un nombre! Comment peut-on s'attendre à savoir 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 invalides. Mais ce n'est pas très flexible ou robuste. Il n'y a 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éussit toujours pour les nombres octaux (avec un zéro non significatif) et la conversion octale peut réussir pour certains nombres décimaux. Alors maintenant, vous devez rechercher un zéro non significatif. Mais attendez! Les nombres hexadécimaux peuvent également commencer par un zéro (0x ...). Soupir.

Même si vous parvenez à résoudre les problèmes ci-dessus, il existe un autre problème plus important: que se passe-t-il si l'appelant doit faire la distinction entre une entrée incorrecte (par exemple, "123foo") et un nombre situé hors de la plage int ( Par exemple, "4000000000" pour 32 bits int)? Avec stringstream, il n’ya aucun moyen de faire cette distinction. Nous savons seulement si la conversion a réussi ou échoué. Si cela échoue, nous n'avons aucun moyen de savoir pourquoi cela a échoué. Comme vous pouvez le constater, stringstream laisse beaucoup à désirer si vous voulez de la robustesse et une gestion claire des erreurs.

Ceci m'amène à mon deuxième conseil: ne pas utiliser le lexical_cast de Boost pour cela . Considérez ce que la documentation lexical_cast a à dire:

Si un degré de contrôle supérieur est requis sur les conversions, std :: stringstream et std :: wstringstream offrent un chemin plus approprié. Lorsque des conversions non basées sur des flux sont requises, lexical_cast est un outil inapproprié pour le travail et n'est pas spécialement conçu pour de tels scénarios.

Quoi?? Nous avons déjà vu que stringstream a un faible niveau de contrôle, et pourtant il indique que stringstream devrait être utilisé à la place de lexical_cast si vous avez besoin d'un "niveau de contrôle supérieur". De plus, étant donné que lexical_cast est juste un wrapper autour de stringstream, il présente les mêmes problèmes que celui que stringstream fait: un support médiocre pour les bases de nombres multiples et une gestion des erreurs médiocre.

La meilleure solution

Heureusement, quelqu'un a déjà résolu tous les problèmes ci-dessus. La bibliothèque standard C contient strtol et une famille qui n’a 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;
}

Assez simple pour quelque chose qui gère tous les cas d'erreur et prend également en charge toute base numérique comprise entre 2 et 36. Si base est égal à zéro (valeur par défaut), il tentera 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 gère toutes les erreurs avec un minimum d'effort.

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

  • Il montre beaucoup mieux performance d'exécution
  • Cela introduit moins de temps système lors de la compilation (les autres récupèrent près de 20 fois plus de SLOC à partir des en-têtes)
  • Il en résulte la plus petite taille de code

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

198
Dan Moulding

Ceci est un moyen C plus sûr que atoi ()

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

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

C++ avec bibliothèque standard stringstream : (merci CMS )

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

Avec boost 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: Correction de la version stringstream afin qu’elle gère les erreurs. (merci aux commentaires de CMS et de JK sur le post original)

67
Luka Marinko

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

22
jk.

Le bon vieux mode 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 plusieurs bases bien.

21
Chris Arguin

Vous pouvez utiliser le stringstream de la bibliothèque standard C++:

stringstream ss(str);
int x;
ss >> x;

if(ss) { // <-- error handling
  // use x
} else {
  // not a number
}

L'état du flux sera configuré pour échouer si un non-chiffre est rencontré lors de la tentative de lecture d'un entier.

Voir Pièges de flux pour connaître les pièges du traitement des erreurs et des flux en C++.

16
jk.

Vous pouvez utiliser stringstream's

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}
10
CMS

Je pense que ces trois liens résument:

les solutions stringstream et lexical_cast sont à peu près les mêmes que la conversion lexicale utilisant stringstream.

Certaines spécialisations de la distribution lexicale utilisent une approche différente, voir http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp pour plus de détails. Les entiers et les flottants sont maintenant spécialisés pour la conversion de nombre entier en chaîne.

On peut spécialiser lexical_cast pour ses propres besoins et le rendre rapide. Ce serait la solution ultime pour satisfaire toutes les parties, propre et simple.

Les articles déjà mentionnés comparent différentes méthodes de conversion de chaînes <-> entières. Les approches suivantes ont du sens: ancien chemin, spirit.karma, fastformat, simple boucle naïve.

Lexical_cast est ok dans certains cas, par exemple. pour int à la conversion de chaîne.

La conversion de chaîne en int à l'aide de la conversion lexicale n'est pas une bonne idée car elle est 10 à 40 fois plus lente que atoi, selon la plate-forme/le compilateur utilisé.

Boost.Spirit.Karma semble être la bibliothèque la plus rapide pour convertir un entier en chaîne.

ex.: generate(ptr_char, int_, integer_number);

et la boucle simple de base de l'article mentionné ci-dessus est un moyen rapide de convertir une chaîne en int, ce n'est évidemment pas le plus sûr, strtol () semble être une solution plus sûre

int naive_char_2_int(const char *p) {
    int x = 0;
    bool neg = false;
    if (*p == '-') {
        neg = true;
        ++p;
    }
    while (*p >= '0' && *p <= '9') {
        x = (x*10) + (*p - '0');
        ++p;
    }
    if (neg) {
        x = -x;
    }
    return x;
}
7
caa

La C++ String Toolkit Library (StrTk) propose la solution suivante:

static const std::size_t digit_table_symbol_count = 256;
static const unsigned char digit_table[digit_table_symbol_count] = {
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF - 0x07
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x08 - 0x0F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x10 - 0x17
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x18 - 0x1F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x20 - 0x27
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x28 - 0x2F
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
   0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x38 - 0x3F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 - 0x47
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x48 - 0x4F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x50 - 0x57
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x58 - 0x5F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x60 - 0x67
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x68 - 0x6F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x70 - 0x77
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x78 - 0x7F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x80 - 0x87
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x88 - 0x8F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x90 - 0x97
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x98 - 0x9F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA0 - 0xA7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA8 - 0xAF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB0 - 0xB7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB8 - 0xBF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC0 - 0xC7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC8 - 0xCF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD0 - 0xD7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD8 - 0xDF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 - 0xE7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE8 - 0xEF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xF0 - 0xF7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  // 0xF8 - 0xFF
 };

template<typename InputIterator, typename T>
inline bool string_to_signed_type_converter_impl_itr(InputIterator begin, InputIterator end, T& v)
{
   if (0 == std::distance(begin,end))
      return false;
   v = 0;
   InputIterator it = begin;
   bool negative = false;
   if ('+' == *it)
      ++it;
   else if ('-' == *it)
   {
      ++it;
      negative = true;
   }
   if (end == it)
      return false;
   while(end != it)
   {
      const T digit = static_cast<T>(digit_table[static_cast<unsigned int>(*it++)]);
      if (0xFF == digit)
         return false;
      v = (10 * v) + digit;
   }
   if (negative)
      v *= -1;
   return true;
}

InputIterator peut être des itérateurs non signés char *, char * ou std :: string, et T doit être un int signé, tel que signé int, int ou long

7
Matthieu N.

Si vous avez C++ 11, les solutions appropriées sont actuellement les fonctions de conversion d'entiers C++ dans <string>: stoi, stol, stoul, stoll, stoull. Ils lancent les exceptions appropriées quand on leur donne une entrée incorrecte et utilisent les fonctions rapides et petites strto* sous le capot.

Si vous êtes bloqué par une révision antérieure de C++, il vous serait utile de reproduire ces fonctions dans votre implémentation.

6
fuzzyTew

À partir de C++ 17, vous pouvez utiliser std::from_chars à partir de l'en-tête <charconv> comme documenté ici .

Par exemple:

#include <iostream>
#include <charconv>
#include <array>

int main()
{
    char const * str = "42";
    int value = 0;

    std::from_chars_result result = std::from_chars(std::begin(str), std::end(str), value);

    if(result.error == std::errc::invalid_argument)
    {
      std::cout << "Error, invalid format";
    }
    else if(result.error == std::errc::result_out_of_range)
    {
      std::cout << "Error, value too big for int range";
    }
    else
    {
      std::cout << "Success: " << result;
    }
}

En prime, il peut également gérer d'autres bases, telles que l'hexadécimal.

5
Pharap

J'aime la réponse de Dan Moulding , je vais juste ajouter un peu de style C++:

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

int to_int(const std::string &s, int base = 0)
{
    char *end;
    errno = 0;
    long result = std::strtol(s.c_str(), &end, base);
    if (errno == ERANGE || result > INT_MAX || result < INT_MIN)
        throw std::out_of_range("toint: string is out of range");
    if (s.length() == 0 || *end != '\0')
        throw std::invalid_argument("toint: invalid string");
    return result;
}

Cela fonctionne à la fois pour std :: string et const char * via la conversion implicite. Il est également utile pour la conversion de base, par exemple. all to_int("0x7b") et to_int("0173") et to_int("01111011", 2) et to_int("0000007B", 16) et to_int("11120", 3) et to_int("3L", 34); renverraient 123.

Contrairement à std::stoi, il fonctionne dans les versions antérieures à C++ 11. De plus, contrairement à std::stoi, boost::lexical_cast et stringstream, il génère des exceptions pour les chaînes étranges telles que "123hohoho".

NB: Cette fonction tolère les espaces de début, mais pas les espaces de fin, c.-à-d. to_int(" 123") renvoie 123 alors que to_int("123 ") lève une exception. Assurez-vous que cela est acceptable pour votre cas d'utilisation ou ajustez le code.

Une telle fonction pourrait faire partie de STL ...

2
user3925906

Je connais trois façons de convertir String en int:

Utilisez la fonction stoi (String to int) ou utilisez simplement Stringstream, troisième méthode de conversion individuelle. Le code est ci-dessous:

1ère méthode

std::string s1 = "4533";
std::string s2 = "3.010101";
std::string s3 = "31337 with some string";

int myint1 = std::stoi(s1);
int myint2 = std::stoi(s2);
int myint3 = std::stoi(s3);

std::cout <<  s1 <<"=" << myint1 << '\n';
std::cout <<  s2 <<"=" << myint2 << '\n';
std::cout <<  s3 <<"=" << myint3 << '\n';

2ème méthode

#include <string.h>
#include <sstream>
#include <iostream>
#include <cstring>
using namespace std;


int StringToInteger(string NumberAsString)
{
    int NumberAsInteger;
    stringstream ss;
    ss << NumberAsString;
    ss >> NumberAsInteger;
    return NumberAsInteger;
}
int main()
{
    string NumberAsString;
    cin >> NumberAsString;
    cout << StringToInteger(NumberAsString) << endl;
    return 0;
} 

ème méthode - mais pas pour une conversion individuelle

std::string str4 = "453";
int i = 0, in=0; // 453 as on
for ( i = 0; i < str4.length(); i++)
{

    in = str4[i];
    cout <<in-48 ;

}
2
Iqra.

J'aime réponse de Dan , en particulier pour éviter les exceptions. Pour le développement de systèmes intégrés et d'autres développements de systèmes de bas niveau, il peut ne pas y avoir de cadre d'exception approprié disponible.

Ajout d'une vérification de l'espace après une chaîne valide ... ces trois lignes

    while (isspace(*end)) {
        end++;
    }


Une vérification des erreurs d’analyse a également été ajoutée.

    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }


Voici la fonction complète ..

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

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

STR2INT_ERROR str2long (long &l, char const *s, int base = 0)
{
    char *end = (char *)s;
    errno = 0;

    l = strtol(s, &end, base);

    if ((errno == ERANGE) && (l == LONG_MAX)) {
        return OVERFLOW;
    }
    if ((errno == ERANGE) && (l == LONG_MIN)) {
        return UNDERFLOW;
    }
    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }    
    while (isspace((unsigned char)*end)) {
        end++;
    }

    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }

    return SUCCESS;
}
1
pellucide

Je sais que c’est une question plus ancienne, mais je l’ai souvent rencontrée et, à ce jour, je n’ai toujours pas trouvé de solution bien conçue ayant les caractéristiques suivantes:

  • Peut convertir n'importe quelle base (et détecter le type de base)
  • Détecte les données erronées (c’est-à-dire que la conversion absorbe toute la chaîne, moins les espaces de début et de fin)
  • Garantira que, quel que soit le type converti en, la plage de la valeur de la chaîne est acceptable.

Donc, voici le mien, avec une sangle de test. Comme il utilise les fonctions C strtoull/strtoll sous le capot, il se convertit toujours en premier au type le plus grand disponible. Ensuite, si vous n'utilisez pas le type le plus grand, il effectuera des vérifications de plage supplémentaires pour vérifier que votre type n'était pas trop (trop) écoulé. Pour cela, il est un peu moins performant que si on choisissait correctement strtol/strtoul. Cependant, cela fonctionne également pour les shorts/caractères et, à ma connaissance, il n'existe pas de fonction de bibliothèque standard qui le fasse également.

Prendre plaisir; j'espère que quelqu'un le trouvera utile.

#include <cstdlib>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include <sstream>

static const int DefaultBase = 10;

template<typename T>
static inline T CstrtoxllWrapper(const char *str, int base = DefaultBase)
{
    while (isspace(*str)) str++; // remove leading spaces; verify there's data
    if (*str == '\0') { throw std::invalid_argument("str; no data"); } // nothing to convert

    // NOTE:  for some reason strtoull allows a negative sign, we don't; if
    //          converting to an unsigned then it must always be positive!
    if (!std::numeric_limits<T>::is_signed && *str == '-')
    { throw std::invalid_argument("str; negative"); }

    // reset errno and call fn (either strtoll or strtoull)
    errno = 0;
    char *ePtr;
    T tmp = std::numeric_limits<T>::is_signed ? strtoll(str, &ePtr, base)
                                              : strtoull(str, &ePtr, base);

    // check for any C errors -- note these are range errors on T, which may
    //   still be out of the range of the actual type we're using; the caller
    //   may need to perform additional range checks.
    if (errno != 0) 
    {
            if (errno == ERANGE) { throw std::range_error("str; out of range"); }
            else if (errno == EINVAL) { throw std::invalid_argument("str; EINVAL"); }
            else { throw std::invalid_argument("str; unknown errno"); }
    }

    // verify everything converted -- extraneous spaces are allowed
    if (ePtr != NULL)
    {
            while (isspace(*ePtr)) ePtr++;
            if (*ePtr != '\0') { throw std::invalid_argument("str; bad data"); }
    }

    return tmp;
}

template<typename T>
T StringToSigned(const char *str, int base = DefaultBase)
{
    static const long long max = std::numeric_limits<T>::max();
    static const long long min = std::numeric_limits<T>::min();

    long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp < min || tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit signed range (";
            if (sizeof(T) != 1) err << min << ".." << max;
            else err << (int) min << ".." << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
T StringToUnsigned(const char *str, int base = DefaultBase)
{
    static const unsigned long long max = std::numeric_limits<T>::max();

    unsigned long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit unsigned range (0..";
            if (sizeof(T) != 1) err << max;
            else err << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
inline T
StringToDecimal(const char *str, int base = DefaultBase)
{
    return std::numeric_limits<T>::is_signed ? StringToSigned<T>(str, base)
                                             : StringToUnsigned<T>(str, base);
}

template<typename T>
inline T
StringToDecimal(T &out_convertedVal, const char *str, int base = DefaultBase)
{
    return out_convertedVal = StringToDecimal<T>(str, base);
}

/*============================== [ Test Strap ] ==============================*/ 

#include <inttypes.h>
#include <iostream>

static bool _g_anyFailed = false;

template<typename T>
void TestIt(const char *tName,
            const char *s, int base,
            bool successExpected = false, T expectedValue = 0)
{
    #define FAIL(s) { _g_anyFailed = true; std::cout << s; }

    T x;
    std::cout << "converting<" << tName << ">b:" << base << " [" << s << "]";
    try
    {
            StringToDecimal<T>(x, s, base);
            // get here on success only
            if (!successExpected)
            {
                    FAIL(" -- TEST FAILED; SUCCESS NOT EXPECTED!" << std::endl);
            }
            else
            {
                    std::cout << " -> ";
                    if (sizeof(T) != 1) std::cout << x;
                    else std::cout << (int) x;  // don't print garbage chars
                    if (x != expectedValue)
                    {
                            FAIL("; FAILED (expected value:" << expectedValue << ")!");
                    }
                    std::cout << std::endl;
            }
    }
    catch (std::exception &e)
    {
            if (successExpected)
            {
                    FAIL(   " -- TEST FAILED; EXPECTED SUCCESS!"
                         << " (got:" << e.what() << ")" << std::endl);
            }
            else
            {
                    std::cout << "; expected exception encounterd: [" << e.what() << "]" << std::endl;
            }
    }
}

#define TEST(t, s, ...) \
    TestIt<t>(#t, s, __VA_ARGS__);

int main()
{
    std::cout << "============ variable base tests ============" << std::endl;
    TEST(int, "-0xF", 0, true, -0xF);
    TEST(int, "+0xF", 0, true, 0xF);
    TEST(int, "0xF", 0, true, 0xF);
    TEST(int, "-010", 0, true, -010);
    TEST(int, "+010", 0, true, 010);
    TEST(int, "010", 0, true, 010);
    TEST(int, "-10", 0, true, -10);
    TEST(int, "+10", 0, true, 10);
    TEST(int, "10", 0, true, 10);

    std::cout << "============ base-10 tests ============" << std::endl;
    TEST(int, "-010", 10, true, -10);
    TEST(int, "+010", 10, true, 10);
    TEST(int, "010", 10, true, 10);
    TEST(int, "-10", 10, true, -10);
    TEST(int, "+10", 10, true, 10);
    TEST(int, "10", 10, true, 10);
    TEST(int, "00010", 10, true, 10);

    std::cout << "============ base-8 tests ============" << std::endl;
    TEST(int, "777", 8, true, 0777);
    TEST(int, "-0111 ", 8, true, -0111);
    TEST(int, "+0010 ", 8, true, 010);

    std::cout << "============ base-16 tests ============" << std::endl;
    TEST(int, "DEAD", 16, true, 0xDEAD);
    TEST(int, "-BEEF", 16, true, -0xBEEF);
    TEST(int, "+C30", 16, true, 0xC30);

    std::cout << "============ base-2 tests ============" << std::endl;
    TEST(int, "-10011001", 2, true, -153);
    TEST(int, "10011001", 2, true, 153);

    std::cout << "============ irregular base tests ============" << std::endl;
    TEST(int, "Z", 36, true, 35);
    TEST(int, "ZZTOP", 36, true, 60457993);
    TEST(int, "G", 17, true, 16);
    TEST(int, "H", 17);

    std::cout << "============ space deliminated tests ============" << std::endl;
    TEST(int, "1337    ", 10, true, 1337);
    TEST(int, "   FEAD", 16, true, 0xFEAD);
    TEST(int, "   0711   ", 0, true, 0711);

    std::cout << "============ bad data tests ============" << std::endl;
    TEST(int, "FEAD", 10);
    TEST(int, "1234 asdfklj", 10);
    TEST(int, "-0xF", 10);
    TEST(int, "+0xF", 10);
    TEST(int, "0xF", 10);
    TEST(int, "-F", 10);
    TEST(int, "+F", 10);
    TEST(int, "12.4", 10);
    TEST(int, "ABG", 16);
    TEST(int, "10011002", 2);

    std::cout << "============ int8_t range tests ============" << std::endl;
    TEST(int8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(int8_t, "80", 16);
    TEST(int8_t, "-80", 16, true, std::numeric_limits<int8_t>::min());
    TEST(int8_t, "-81", 16);
    TEST(int8_t, "FF", 16);
    TEST(int8_t, "100", 16);

    std::cout << "============ uint8_t range tests ============" << std::endl;
    TEST(uint8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(uint8_t, "80", 16, true, std::numeric_limits<int8_t>::max()+1);
    TEST(uint8_t, "-80", 16);
    TEST(uint8_t, "-81", 16);
    TEST(uint8_t, "FF", 16, true, std::numeric_limits<uint8_t>::max());
    TEST(uint8_t, "100", 16);

    std::cout << "============ int16_t range tests ============" << std::endl;
    TEST(int16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(int16_t, "8000", 16);
    TEST(int16_t, "-8000", 16, true, std::numeric_limits<int16_t>::min());
    TEST(int16_t, "-8001", 16);
    TEST(int16_t, "FFFF", 16);
    TEST(int16_t, "10000", 16);

    std::cout << "============ uint16_t range tests ============" << std::endl;
    TEST(uint16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(uint16_t, "8000", 16, true, std::numeric_limits<int16_t>::max()+1);
    TEST(uint16_t, "-8000", 16);
    TEST(uint16_t, "-8001", 16);
    TEST(uint16_t, "FFFF", 16, true, std::numeric_limits<uint16_t>::max());
    TEST(uint16_t, "10000", 16);

    std::cout << "============ int32_t range tests ============" << std::endl;
    TEST(int32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(int32_t, "80000000", 16);
    TEST(int32_t, "-80000000", 16, true, std::numeric_limits<int32_t>::min());
    TEST(int32_t, "-80000001", 16);
    TEST(int32_t, "FFFFFFFF", 16);
    TEST(int32_t, "100000000", 16);

    std::cout << "============ uint32_t range tests ============" << std::endl;
    TEST(uint32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(uint32_t, "80000000", 16, true, std::numeric_limits<int32_t>::max()+1);
    TEST(uint32_t, "-80000000", 16);
    TEST(uint32_t, "-80000001", 16);
    TEST(uint32_t, "FFFFFFFF", 16, true, std::numeric_limits<uint32_t>::max());
    TEST(uint32_t, "100000000", 16);

    std::cout << "============ int64_t range tests ============" << std::endl;
    TEST(int64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(int64_t, "8000000000000000", 16);
    TEST(int64_t, "-8000000000000000", 16, true, std::numeric_limits<int64_t>::min());
    TEST(int64_t, "-8000000000000001", 16);
    TEST(int64_t, "FFFFFFFFFFFFFFFF", 16);
    TEST(int64_t, "10000000000000000", 16);

    std::cout << "============ uint64_t range tests ============" << std::endl;
    TEST(uint64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(uint64_t, "8000000000000000", 16, true, std::numeric_limits<int64_t>::max()+1);
    TEST(uint64_t, "-8000000000000000", 16);
    TEST(uint64_t, "-8000000000000001", 16);
    TEST(uint64_t, "FFFFFFFFFFFFFFFF", 16, true, std::numeric_limits<uint64_t>::max());
    TEST(uint64_t, "10000000000000000", 16);

    std::cout << std::endl << std::endl
              << (_g_anyFailed ? "!! SOME TESTS FAILED !!" : "ALL TESTS PASSED")
              << std::endl;

    return _g_anyFailed;
}

StringToDecimal est la méthode utilisateur-land; il est surchargé et peut donc être appelé comme ceci:

int a; a = StringToDecimal<int>("100");

ou ca:

int a; StringToDecimal(a, "100");

Je déteste répéter le type int, préférez donc ce dernier. Cela garantit que si le type de 'a' change, on n'obtient pas de mauvais résultats. Je souhaite que le compilateur puisse le comprendre comme:

int a; a = StringToDecimal("100");

... mais, C++ ne déduit pas les types de renvoi de modèle, c'est donc le meilleur que je puisse obtenir.

L'implémentation est assez simple:

CstrtoxllWrapper enveloppe strtoull et strtoll, en appelant selon le besoin en fonction de la signature du type de modèle et en fournissant certaines garanties supplémentaires (par exemple, une entrée négative est interdite si elle est signée et garantit la chaîne a été converti).

CstrtoxllWrapper est utilisé par StringToSigned et StringToUnsigned avec le type le plus large (long long/unsigned long) disponible pour le compilateur; cela permet d'effectuer la conversion maximale. Ensuite, s'il est nécessaire, StringToSigned/StringToUnsigned effectue les derniers contrôles de plage sur le type sous-jacent. Enfin, la méthode de point de terminaison, StringToDecimal, détermine les méthodes de modèle StringTo * à appeler en fonction de la signature du type sous-jacent.

Je pense que la plupart des déchets peuvent être optimisés par le compilateur; à peu près tout devrait être déterministe au moment de la compilation. Tout commentaire sur cet aspect m'intéresserait!

0
DreamWarrior

Vous pouvez utiliser cette méthode définie.

#define toInt(x) {atoi(x.c_str())};

Et si vous deviez convertir String en Integer, procédez comme suit.

int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}

La sortie serait 102.

0
Boris