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


261

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


21
Que diriez-vous de certains des exemples suivants: codeproject.com/KB/recipes/Tokenizer.aspx Ils sont très efficaces et quelque peu élégants

@ Beh Tou Cheh, si vous pensez que c'est un bon moyen d'analyser l'int, veuillez le poster comme réponse.
Eugene Yokota

Réponses:


165

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èvera une exception en cas d'erreur de conversion.

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

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


4
Mais ils acceptent des arguments plus que cela, l'un d'eux étant un point à size_t qui, sinon nul, est défini sur le premier caractère non converti
Zharf

Oui, en utilisant le deuxième paramètre de std :: stoi, vous pouvez détecter une entrée non valide. Vous devez quand même lancer votre propre fonction de conversion ...
CC.

Tout comme la réponse acceptée, mais avec ces fonctions standard qui seraient beaucoup plus propres, imo
Zharf

Gardez à l'esprit que le deuxième argument de ces fonctions peut être utilisé pour dire si la chaîne entière a été convertie ou non. Si le résultat size_tn'est pas égal à la longueur de la chaîne, il s'est arrêté tôt. Il retournera toujours 11 dans ce cas, mais possera 2 au lieu de la longueur de chaîne 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Zoe

204

Ce qu'il ne faut pas faire

Voici mon premier conseil: n'utilisez pas stringstream pour cela . Bien qu'au début il 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 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 a un problème majeur: str2int(i, "1337h4x0r")reviendra avec plaisir trueet iobtiendra la valeur 1337. Nous pouvons contourner ce problème en nous assurant qu'il n'y a plus de caractères stringstreamaprè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 y a encore quelques autres problèmes.

Que faire si le nombre dans la chaîne n'est pas en base 10? Nous pouvons essayer d'accueillir 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 savoir cela? L'appelant ne sait pas encore quel est le numéro. Ils ne savent même pas que c'estun numéro! 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éussira 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 vérifier pour un zéro de tête. Mais attendez! Les nombres hexadécimaux peuvent également commencer par un zéro de tête (0x ...). Soupir.

Même si vous parvenez à résoudre les problèmes ci-dessus, il y a encore 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 numéro qui est hors de la plage de int(par exemple "4000000000" pour 32 bits int)? Avec stringstream, il n'y a aucun moyen de faire cette distinction. Nous savons seulement si la conversion a réussi ou échoué. S'il échoue, nous n'avons aucun moyen de savoir pourquoi il a échoué. Comme vous pouvez le voir, stringstreamlaisse beaucoup à désirer si vous voulez une robustesse et une gestion claire des erreurs.

Cela m'amène à mon deuxième conseil: n'utilisez pas Boost lexical_castpour cela . Considérez ce que la lexical_castdocumentation a à dire:

Lorsqu'un degré de contrôle plus élevé 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 n'est pas le bon outil pour le travail et n'est pas spécialement conçu pour de tels scénarios.

Quoi?? Nous avons déjà vu que le stringstreamniveau de contrôle était médiocre, et pourtant, il stringstreamdevrait être utilisé à la place de lexical_castsi vous avez besoin d'un "niveau de contrôle supérieur". De plus, comme il ne lexical_casts'agit que d'un wrapper stringstream, il souffre des mêmes problèmes que celui- stringstreamci: mauvaise prise en charge de plusieurs bases de nombres et mauvaise gestion des erreurs.

La meilleure solution

Heureusement, quelqu'un a déjà résolu tous les problèmes ci-dessus. La bibliothèque standard C contient une strtolfamille 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 n'importe quelle base numérique de 2 à 36. Si baseest 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 gère toutes les erreurs avec un minimum d'effort.

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

  • Il présente des performances d'exécution bien meilleures
  • Il introduit moins de temps de compilation (les autres tirent près de 20 fois plus de SLOC 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.


22
@JamesDunne: POSIX doit strtolêtre thread-safe. POSIX nécessite également errnod'utiliser le stockage local des threads. Même sur les systèmes non POSIX, presque toutes les implémentations de errnosur les systèmes multithread utilisent le stockage thread-local. La dernière norme C ++ doit errnoêtre compatible POSIX. La dernière norme C requiert également un errnostockage local par thread. Même sous Windows, qui n'est certainement pas conforme à POSIX, errnoest thread-safe et, par extension, l'est également strtol.
Dan Moulding

7
Je ne peux pas vraiment suivre votre raisonnement contre l'utilisation de boost :: lexical_cast. Comme on dit, std :: stringstream offre en effet beaucoup de contrôle - vous faites tout, de la vérification des erreurs à la détermination de la base vous-même. La documentation actuelle la présente comme suit: "Pour des conversions plus complexes, telles que lorsque la précision ou le formatage nécessitent un contrôle plus strict que celui offert par le comportement par défaut de lexical_cast, l'approche std :: stringstream classique est recommandée."
fhd

8
Il s'agit d'un codage C inapproprié dans C ++. La bibliothèque standard contient std::stolpour cela, qui générera de manière appropriée des exceptions plutôt que de renvoyer des constantes.
fuzzyTew

22
@fuzzyTew J'ai écrit cette réponse avant std::stolmême d'être ajoutée au langage C ++. Cela dit, je ne pense pas qu'il soit juste de dire qu'il s'agit de "codage C en C ++". C'est idiot de dire que std::strtolc'est du codage C quand il fait explicitement partie du langage C ++. Ma réponse s'appliquait parfaitement au C ++ quand il a été écrit et il s'applique toujours même avec le nouveau std::stol. L'appel de fonctions qui peuvent lever des exceptions n'est pas toujours le meilleur pour chaque situation de programmation.
Dan Moulding

9
@fuzzyTew: Le manque d'espace disque est une condition exceptionnelle. Les fichiers de données mal formatés produits par ordinateur sont une condition exceptionnelle. Mais les fautes de frappe dans l'entrée utilisateur ne sont pas exceptionnelles. Il est bon d'avoir une approche d'analyse qui est capable de gérer les échecs d'analyse normaux et non exceptionnels.
Ben Voigt

67

C'est un moyen C plus sûr qu'atoi ()

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

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

C ++ avec chaîne de chaînes de bibliothèque standard : (merci CMS )

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

Avec la bibliothèque boost : (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
}

Modifier: correction de la version de chaîne de caractères afin qu'elle gère les erreurs. (merci aux commentaires de CMS et de jk sur le post original)


1
veuillez mettre à jour votre version de stringstream pour inclure une vérification de stringstream :: fail () (comme demandé par le questionneur "Gestion d'erreur robuste et claire")
jk.

2
Votre version stringstream acceptera des trucs comme "10haha" sans se plaindre
Johannes Schaub - litb

3
changez-le en (! (ss >> num) .fail () && (ss >> ws) .eof ()) de ((ss >> num) .fail ()) si vous voulez le même traitement comme lexical_cast
Johannes Schaub - litb

3
Le C ++ avec la méthode standard de chaîne de bibliothèque ne fonctionne pas pour les chaînes telles que "12-SomeString" même avec la vérification .fail ().
captonssj

C ++ 11 inclut des fonctions rapides standard pour cela maintenant
fuzzyTew

21

La bonne vieille façon C fonctionne toujours. Je recommande strtol ou strtoul. Entre l'état de retour et le «endPtr», vous pouvez donner une bonne sortie de diagnostic. Il gère également très bien plusieurs bases.


4
Oh s'il vous plaît, n'utilisez pas ce vieux truc en C lors de la programmation de C ++. Il existe des façons meilleures / plus faciles / plus propres / plus modernes / plus sûres de le faire en C ++!
jk.

27
C'est drôle quand les gens sont préoccupés par des moyens "plus modernes" de résoudre un problème.
J Miller,

@Jason, la sécurité de type IMO et la gestion des erreurs sont une idée plus moderne que celle de C.
Eugene Yokota

6
J'ai regardé les autres réponses, et jusqu'à présent, rien n'est évidemment meilleur / plus facile / plus propre ou plus sûr. L'affiche indiquait qu'il avait un char *. Cela limite le niveau de sécurité que vous allez obtenir :)
Chris Arguin


16

Vous pouvez utiliser le flux de chaînes a du libraray standard C ++:

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

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

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

Voir les pièges du flux pour les pièges de la gestion des erreurs et des flux en C ++.


2
La méthode C ++ stringstream ne fonctionne pas pour les chaînes telles que "12-SomeString" même avec la vérification de "l'état du flux".
captonssj

10

Vous pouvez utiliser le stringstream

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

4
Mais cela ne gère aucune erreur. Vous devez vérifier le flux pour les échecs.
jk.

1
À droite, vous devez vérifier le flux si ((ss >> num) .fail ()) {// ERROR}
CMS

2
La méthode C ++ stringstream ne fonctionne pas pour les chaînes telles que "12-SomeString" même avec la vérification de "l'état du flux"
captonssj

8

Je pense que ces trois liens résument:

Les solutions stringstream et lexical_cast sont à peu près les mêmes que le cast lexical utilise stringstream.

Certaines spécialisations de la fonte 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 désormais spécialisés pour la conversion d'entiers en chaînes.

On peut spécialiser lexical_cast pour ses propres besoins et faire vite. Ce serait la solution ultime satisfaisant toutes les parties, propre et simple.

Les articles déjà mentionnés montrent une comparaison entre différentes méthodes de conversion d'entiers <-> chaînes. Les approches suivantes ont du sens: ancienne voie C, spirit.karma, format rapide, boucle naïve simple.

Lexical_cast est correct dans certains cas, par exemple pour la conversion int en chaîne.

La conversion de chaîne en entier à l'aide d'une conversion lexicale n'est pas une bonne idée car elle est 10 à 40 fois plus lente qu'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 le plus rapide de convertir une chaîne en int, évidemment pas la plus sûre, 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

La bibliothèque C ++ String Toolkit (StrTk) a 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 soit des itérateurs char *, char * ou std :: string non signés, et T devrait être un int signé, tel que int signé, int ou long


1
AVERTISSEMENT Cette implémentation a l'air sympa, mais ne gère pas les débordements pour autant que je sache.
Vinnie Falco

2
Le code ne gère pas le débordement. v = (10 * v) + digit;déborde inutilement avec une entrée de chaîne avec la valeur texte de INT_MIN. Le tableau est d'une valeur discutable vs simplementdigit >= '0' && digit <= '9'
chux - Réinstallez Monica

6

Si vous avez C ++ 11, les solutions appropriées sont aujourd'hui le C ++ entier des fonctions de conversion en <string>: stoi, stol, stoul, stoll, stoull. Ils lancent des exceptions appropriées lorsqu'ils reçoivent une entrée incorrecte et utilisent les fonctions rapides et petites strto*sous le capot.

Si vous êtes bloqué avec une révision antérieure de C ++, il serait transférable de vous d'imiter ces fonctions dans votre implémentation.


6

À partir de C ++ 17, vous pouvez utiliser à std::from_charspartir de l'en- <charconv>tête comme indiqué 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, comme hexadécimales.


3

J'aime la réponse de Dan Moulding , je vais juste y 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;
}

Il fonctionne à la fois pour std :: string et const char * via la conversion implicite. Il est également utile pour la conversion de base, par exemple tous 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);retournerait 123.

Contrairement à std::stoicela fonctionne dans pré-C ++ 11. Contrairement à std::stoi, boost::lexical_castetstringstream il lève des exceptions pour les chaînes étranges comme "123hohoho".

NB: Cette fonction tolère les espaces de début mais pas les espaces de fin, c'est-à-dire to_int(" 123")renvoie 123 tout en to_int("123 ")lançant 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

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

Soit utiliser la fonction stoi (String to int) ou tout simplement aller avec Stringstream, la troisième façon de procéder à une 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;
} 

3e 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 ;

}

1

J'aime la réponse de Dan , en particulier parce qu'il évite les exceptions. Pour le développement de systèmes embarqués et autres développements de systèmes de bas niveau, il n’existe peut-être pas de structure d’exception appropriée.

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

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


Ajout d'une vérification des erreurs d'analyse également.

    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;
}

@chux a ajouté du code pour répondre aux préoccupations que vous avez mentionnées.
pellucide

1) Échoue toujours à détecter une erreur avec une entrée comme " ". strtol()n'est pas spécifié à définir errnolorsqu'aucune conversion ne se produit. Mieux vaut utiliser if (s == end) return INCONVERTIBLE; pour détecter aucune conversion. Et puis if (*s == '\0' || *end != '\0')peut se simplifier en if (*end)2) || l > LONG_MAXet || l < LONG_MINne servir à rien - ils ne sont jamais vrais.
chux

@chux Sur un mac, l'errno est défini pour les erreurs d'analyse, mais sur linux, l'errno n'est pas défini. Code modifié pour dépendre du pointeur "fin" pour détecter cela.
pellucide

0

Vous pouvez utiliser cette méthode définie.

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

Et si vous deviez convertir une chaîne en entier, vous feriez simplement ce qui suit.

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

La sortie serait 102.


4
idk. L'écriture d'une macro de définition atoine semble pas être "la manière C ++", à la lumière d'autres réponses comme acceptée std::stoi().
Eugene Yokota

Je trouve plus amusant d'utiliser des méthodes prédéfinies: P
Boris

0

Je sais que c'est une question plus ancienne, mais je l'ai rencontrée tant de fois 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étectera les données erronées (c.-à-d. Garantira que la chaîne entière, moins les espaces blancs de début / fin, est consommée par la conversion)
  • S'assurera que, quel que soit le type converti en, la plage de la valeur de la chaîne est acceptable.

Alors, voici le mien, avec une sangle de test. Parce qu'il utilise les fonctions C strtoull / strtoll sous le capot, il se convertit toujours d'abord en le plus grand type disponible. Ensuite, si vous n'utilisez pas le plus grand type, il effectuera des vérifications de plage supplémentaires pour vérifier que votre type n'était pas sur (sous) débordé. Pour cela, il est un peu moins performant que si l'on choisit correctement strtol / strtoul. Cependant, cela fonctionne également pour les courts métrages / 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;
}

StringToDecimalest la méthode de l'utilisateur-terre; il est surchargé, il 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, alors préférez 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 retour de modèle, c'est donc le meilleur que je puisse obtenir.

L'implémentation est assez simple:

CstrtoxllWrapperencapsule les deux strtoullet strtoll, en appelant ce qui est nécessaire en fonction de la signature du type de modèle et en fournissant des garanties supplémentaires (par exemple, une entrée négative est interdite si non signée et elle garantit que la chaîne entière a été convertie).

CstrtoxllWrapperest utilisé par StringToSignedet StringToUnsignedavec le plus grand type (long long / unsigned long long) disponible pour le compilateur; cela permet d'effectuer la conversion maximale. Ensuite, si cela est nécessaire, StringToSigned/ StringToUnsignedeffectue les dernières vérifications de plage sur le type sous-jacent. Enfin, la méthode du point de terminaison StringToDecimal,, décide des méthodes de modèle StringTo * à appeler en fonction de la signature du type sous-jacent.

Je pense que la plupart des fichiers indésirables 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 me serait intéressant!


"utiliser le plus grand type" -> pourquoi long longau lieu de intmax_t?
chux

Confiant que vous voulez if (ePtr != str). En outre, utilisez isspace((unsigned char) *ePtr)pour gérer correctement les valeurs négatives de *ePtr.
chux

-3

En C, vous pouvez utiliser int atoi (const char * str),

Analyse la chaîne C en interprétant son contenu comme un nombre entier, qui est renvoyé comme une valeur de type int.


2
Comme je l'ai indiqué atoidans la question, j'en suis conscient. La question n'est clairement pas sur C, mais sur C ++. -1
Eugene Yokota
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.