Wie analysiere ich einen String zu einem Int in C ++?


260

Wie kann C ++ eine Zeichenfolge (angegeben als char *) in ein int analysieren? Eine robuste und klare Fehlerbehandlung ist ein Plus (anstatt Null zurückzugeben ).


21
Wie wäre es mit einigen Beispielen aus dem Folgenden: codeproject.com/KB/recipes/Tokenizer.aspx Sie sind sehr effizient und etwas elegant

@Beh Tou Cheh, wenn du denkst, dass es eine gute Möglichkeit ist, int zu analysieren, poste es bitte als Antwort.
Eugene Yokota

Antworten:


164

Im neuen C ++ 11 gibt es dafür Funktionen: stoi, stol, stoll, stoul und so weiter.

int myNr = std::stoi(myString);

Bei einem Konvertierungsfehler wird eine Ausnahme ausgelöst.

Sogar diese neuen Funktionen haben immer noch das gleiche Problem wie Dan: Sie konvertieren die Zeichenfolge "11x" gerne in eine Ganzzahl "11".

Weitere Informationen : http://en.cppreference.com/w/cpp/string/basic_string/stol


4
Aber sie akzeptieren Argumente als das, eines davon ist ein Punkt auf size_t, der, wenn nicht null, auf das erste nicht konvertierte Zeichen gesetzt wird
Zharf

Ja, mit dem zweiten Parameter von std :: stoi können Sie ungültige Eingaben erkennen. Sie müssen jedoch immer noch Ihre eigene Konvertierungsfunktion ausführen ...
CC.

Genau wie die akzeptierte Antwort, aber mit diesen Standardfunktionen wäre das viel sauberer, imo
Zharf

204

Was nicht zu tun

Hier ist mein erster Ratschlag: Verwenden Sie hierfür keinen Stringstream . Während es auf den ersten Blick einfach zu sein scheint, werden Sie feststellen, dass Sie viel zusätzliche Arbeit leisten müssen, wenn Sie Robustheit und eine gute Fehlerbehandlung wünschen.

Hier ist ein Ansatz, der intuitiv zu funktionieren scheint:

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

Dies hat ein großes Problem: str2int(i, "1337h4x0r")Ich werde gerne zurückkehren trueund iden Wert erhalten 1337. Wir können dieses Problem umgehen, indem wir sicherstellen, dass stringstreamnach der Konvertierung keine Zeichen mehr vorhanden sind :

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

Wir haben ein Problem behoben, aber es gibt noch einige andere Probleme.

Was ist, wenn die Zahl in der Zeichenfolge nicht Basis 10 ist? Wir können versuchen, andere Basen aufzunehmen, indem wir den Stream auf den richtigen Modus einstellen (z. B. ss << std::hex), bevor wir die Konvertierung versuchen. Dies bedeutet jedoch, dass der Anrufer a priori wissen muss, auf welcher Basis die Nummer basiert - und wie kann der Anrufer dies möglicherweise wissen? Der Anrufer weiß noch nicht, wie die Nummer lautet. Sie wissen nicht einmal, dass es so isteine Zahl! Wie kann von ihnen erwartet werden, dass sie wissen, um welche Basis es sich handelt? Wir könnten einfach vorschreiben, dass alle in unsere Programme eingegebenen Zahlen Basis 10 sein müssen und hexadezimale oder oktale Eingaben als ungültig ablehnen müssen. Das ist aber nicht sehr flexibel oder robust. Es gibt keine einfache Lösung für dieses Problem. Sie können die Konvertierung nicht einfach einmal für jede Basis versuchen, da die Dezimalkonvertierung für Oktalzahlen (mit einer führenden Null) immer erfolgreich ist und die Oktalkonvertierung für einige Dezimalzahlen erfolgreich sein kann. Jetzt müssen Sie nach einer führenden Null suchen. Aber warte! Hexadezimalzahlen können auch mit einer führenden Null beginnen (0x ...). Seufzer.

Selbst wenn es Ihnen gelingt, die oben genannten Probleme zu lösen, gibt es noch ein weiteres größeres Problem: Was ist, wenn der Anrufer zwischen einer schlechten Eingabe (z. B. "123foo") und einer Zahl unterscheiden muss, die außerhalb des Bereichs von int(z. B. "4000000000" für liegt? 32-Bit int)? Mit stringstreamgibt es keine Möglichkeit, diese Unterscheidung zu treffen. Wir wissen nur, ob die Konvertierung erfolgreich war oder fehlgeschlagen ist. Wenn es fehlschlägt, können wir nicht wissen, warum es fehlgeschlagen ist. Wie Sie sehen, stringstreamlässt es zu wünschen übrig, wenn Sie Robustheit und klare Fehlerbehandlung wünschen.

Dies führt mich zu meinem zweiten Ratschlag: machen keinen Gebrauch Erhöhung ist lexical_castfür diese . Überlegen Sie, was die lexical_castDokumentation zu sagen hat:

Wenn ein höheres Maß an Kontrolle über Konvertierungen erforderlich ist, bieten std :: stringstream und std :: wstringstream einen geeigneteren Pfad. Wenn nicht-streambasierte Konvertierungen erforderlich sind, ist lexical_cast das falsche Tool für den Job und für solche Szenarien nicht speziell geeignet.

Was?? Wir haben bereits gesehen, dass stringstreamdas Kontrollniveau schlecht ist, und dennoch stringstreamsollte es verwendet werden, anstatt, lexical_castwenn Sie "ein höheres Kontrollniveau" benötigen. Da lexical_castes sich nur um einen Wrapper handelt, stringstreamtreten dieselben Probleme stringstreamauf: schlechte Unterstützung für mehrere Zahlenbasen und schlechte Fehlerbehandlung.

Die beste Lösung

Glücklicherweise hat jemand bereits alle oben genannten Probleme gelöst. Die C-Standardbibliothek enthält strtolund Familie, die keines dieser Probleme haben.

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

Ziemlich einfach für etwas, das alle Fehlerfälle behandelt und auch eine beliebige Zahlenbasis von 2 bis 36 unterstützt. Wenn baseNull (die Standardeinstellung) ist, wird versucht, von einer beliebigen Basis zu konvertieren. Oder der Aufrufer kann das dritte Argument angeben und angeben, dass die Konvertierung nur für eine bestimmte Basis versucht werden soll. Es ist robust und behandelt alle Fehler mit minimalem Aufwand.

Andere Gründe zu bevorzugen strtol(und Familie):

  • Es zeigt eine viel bessere Laufzeitleistung
  • Dies führt zu einem geringeren Aufwand für die Kompilierung (die anderen ziehen fast 20-mal mehr SLOC aus den Headern ein).
  • Dies führt zu der kleinsten Codegröße

Es gibt absolut keinen guten Grund, eine andere Methode anzuwenden.


22
@ JamesDunne: POSIX muss strtolthreadsicher sein. POSIX erfordert außerdem die errnoVerwendung eines threadlokalen Speichers. Selbst auf Nicht-POSIX-Systemen verwenden fast alle Implementierungen errnoauf Multithread-Systemen threadlokalen Speicher. Der neueste C ++ - Standard muss errnoPOSIX-kompatibel sein. Der neueste C-Standard erfordert ebenfallserrno einen threadlokalen Speicher. Selbst unter Windows, das definitiv nicht POSIX-kompatibel ist, errnoist es threadsicher und im weiteren Sinne auch strtol.
Dan Moulding

7
Ich kann Ihrer Argumentation gegen die Verwendung von boost :: lexical_cast nicht wirklich folgen. Wie sie sagen, bietet std :: stringstream in der Tat viel Kontrolle - Sie tun alles von der Fehlerprüfung bis zur Ermittlung der Basis selbst. In der aktuellen Dokumentation heißt es: "Für komplexere Konvertierungen, z. B. wenn Präzision oder Formatierung eine strengere Kontrolle erfordern als das Standardverhalten von lexical_cast, wird der herkömmliche std :: stringstream-Ansatz empfohlen."
fhd

8
Dies ist eine unangemessene C-Codierung in C ++. Die Standardbibliothek enthältstd::stol dafür, dass Ausnahmen angemessen ausgelöst werden, anstatt Konstanten zurückzugeben.
FuzzyTew

22
@fuzzyTew Ich habe diese Antwort geschrieben, bevor sie std::stolsogar zur C ++ - Sprache hinzugefügt wurde. Trotzdem halte ich es nicht für fair zu sagen, dass dies "C-Codierung in C ++" ist. Es ist albern zu sagen, dass dies std::strtolC-Codierung ist, wenn sie explizit Teil der C ++ - Sprache ist. Meine Antwort traf perfekt auf C ++ zu, als es geschrieben wurde, und es gilt auch mit dem neuen std::stol. Das Aufrufen von Funktionen, die Ausnahmen auslösen können, ist nicht immer für jede Programmiersituation die beste.
Dan Moulding

9
@fuzzyTew: Der Speicherplatzmangel ist eine außergewöhnliche Bedingung. Fehlformatierte Datendateien, die vom Computer erstellt wurden, sind eine Ausnahmebedingung. Tippfehler bei Benutzereingaben sind jedoch keine Ausnahme. Es ist gut, einen Parsing-Ansatz zu haben, der normale, nicht außergewöhnliche Parsing-Fehler behandeln kann.
Ben Voigt

67

Dies ist ein sicherer C-Weg als atoi ()

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

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

C ++ mit Standardbibliothek stringstream : (danke CMS )

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

Mit Boost- Bibliothek: (danke 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
}

Bearbeiten: Die Stringstream-Version wurde so korrigiert, dass Fehler behandelt werden. (Dank des Kommentars von CMS und jk zum Originalbeitrag)


1
Bitte aktualisieren Sie Ihre Stringstream-Version, um eine Überprüfung auf stringstream :: fail () aufzunehmen (wie vom Fragesteller "Robuste und klare Fehlerbehandlung" angefordert)
jk.

2
Ihre Stringstream-Version akzeptiert Sachen wie "10haha" ohne sich zu beschweren
Johannes Schaub - litb

3
Ändern Sie es in (! (ss >> num) .fail () && (ss >> ws) .eof ()) von ((ss >> num) .fail ()), wenn Sie die gleiche Behandlung wie lexical_cast
Johannes

3
Die Stringstream-Methode für C ++ mit Standardbibliothek funktioniert auch mit der Prüfung .fail () nicht für Strings wie "12-SomeString".
captonssj

C ++ 11 enthält jetzt Standard-Schnellfunktionen
fuzzyTew

21

Der gute alte C-Weg funktioniert immer noch. Ich empfehle strtol oder strtoul. Zwischen dem Rückgabestatus und dem 'endPtr' können Sie eine gute Diagnoseausgabe geben. Es handhabt auch mehrere Basen gut.


4
Oh, bitte verwenden Sie dieses alte C-Zeug nicht, wenn Sie C ++ programmieren. Es gibt bessere / einfachere / sauberere / modernere / sicherere Möglichkeiten, dies in C ++ zu tun!
jk.

27
Es ist lustig, wenn Leute über "modernere" Wege zur Lösung eines Problems besorgt sind.
J Miller

@ Jason, IMO stärkere Typensicherheit und Fehlerbehandlung ist eine modernere Idee als die von C.
Eugene Yokota

6
Ich habe mir die anderen Antworten angesehen und bisher ist nichts offensichtlich besser / einfacher / sauberer oder sicherer. Auf dem Plakat stand, dass er einen Char * hatte. Das schränkt die Sicherheit ein, die Sie bekommen werden :)
Chris Arguin


16

Sie können den a-Stringstream aus dem C ++ - Standardbibliothek verwenden:

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

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

Der Stream-Status wird auf "Fehlgeschlagen" gesetzt, wenn beim Versuch, eine Ganzzahl zu lesen, eine Nicht-Ziffer festgestellt wird.

Unter Stream-Fallstricke finden Sie Fallstricke bei der Fehlerbehandlung und bei Streams in C ++.


2
Die C ++ - Stringstream-Methode funktioniert auch bei der Prüfung des 'Stream-Status' nicht für Strings wie "12-SomeString".
captonssj

10

Sie können Stringstreams verwenden

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

4
Dies behandelt jedoch keine Fehler. Sie müssen den Stream auf Fehler überprüfen.
jk.

1
Richtig, Sie müssen den Stream überprüfen, wenn ((ss >> num) .fail ()) {// ERROR}
CMS

2
Die C ++ - Stringstream-Methode funktioniert nicht für Strings wie "12-SomeString", selbst bei der Überprüfung des 'Stream-Status'
captonssj

7

Ich denke, diese drei Links fassen es zusammen:

stringstream- und lexical_cast-Lösungen entsprechen in etwa der Verwendung von stringstream durch lexical cast.

Einige Spezialisierungen der lexikalischen Besetzung verwenden einen anderen Ansatz. Weitere Informationen finden Sie unter http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp . Ganzzahlen und Gleitkommazahlen sind jetzt auf die Konvertierung von Ganzzahlen in Zeichenfolgen spezialisiert.

Man kann lexical_cast auf seine eigenen Bedürfnisse spezialisieren und es schnell machen. Dies wäre die ultimative Lösung, die alle Beteiligten zufriedenstellt, sauber und einfach.

Die bereits erwähnten Artikel zeigen einen Vergleich zwischen verschiedenen Methoden zum Konvertieren von Ganzzahlen <-> Zeichenfolgen. Folgende Ansätze sind sinnvoll: alter C-Way, Spirit.Karma, Fastformat, einfache naive Schleife.

Lexical_cast ist in einigen Fällen in Ordnung, z. B. für die Konvertierung von int in Zeichenfolgen.

Das Konvertieren von Zeichenfolgen in int mithilfe von lexikalischem Cast ist keine gute Idee, da es je nach verwendeter Plattform / verwendetem Compiler 10-40-mal langsamer als atoi ist.

Boost.Spirit.Karma scheint die schnellste Bibliothek zum Konvertieren von Ganzzahlen in Zeichenfolgen zu sein.

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

und einfache einfache Schleife aus dem oben erwähnten Artikel ist der schnellste Weg, um String in int zu konvertieren, offensichtlich nicht der sicherste, strtol () scheint eine sicherere Lösung zu sein

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

Die C ++ String Toolkit Library (StrTk) bietet die folgende Lösung:

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

Der InputIterator kann entweder aus vorzeichenlosen Iteratoren char *, char * oder std :: string bestehen, und es wird erwartet, dass T ein vorzeichenbehaftetes int ist, z. B. signiertes int, int oder long


1
WARNUNG Diese Implementierung sieht gut aus, behandelt aber keine Überläufe, soweit ich das beurteilen kann.
Vinnie Falco

2
Code behandelt keinen Überlauf. v = (10 * v) + digit;Überlauf unnötig mit String-Eingabe mit dem Textwert von INT_MIN. Tabelle ist von fragwürdigem Wert vs einfachdigit >= '0' && digit <= '9'
chux - Reinstate Monica

6

Wenn Sie C ++ 11, heute die entsprechenden Lösungen sind die C ++ integer Umwandlungsfunktionen in <string>: stoi, stol, stoul, stoll, stoull. Sie werfen bei falscher Eingabe entsprechende Ausnahmen und nutzen die schnellen und kleinen strto*Funktionen unter der Haube.

Wenn Sie mit einer früheren Version von C ++ nicht weiterkommen, können Sie diese Funktionen in Ihrer Implementierung nachahmen.


6

Ab C ++ 17 können Sie std::from_charsab dem hier<charconv> dokumentierten Header verwenden .

Beispielsweise:

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

Als Bonus kann es auch andere Basen wie Hexadezimal verarbeiten.


3

Ich mag die Antwort von Dan Moulding , ich werde nur ein bisschen C ++ - Stil hinzufügen:

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

Es funktioniert sowohl für std :: string als auch für const char * durch die implizite Konvertierung. Es ist auch nützlich für die Basiskonvertierung, z. B. all to_int("0x7b")und to_int("0173")und to_int("01111011", 2)und to_int("0000007B", 16)und to_int("11120", 3)und to_int("3L", 34);würde 123 zurückgeben.

Im Gegensatz std::stoidazu funktioniert es in Pre-C ++ 11. Auch im Gegensatz zu std::stoi, boost::lexical_castundstringstream es wirft Ausnahmen für seltsame Saiten wie "123hohoho".

NB: Diese Funktion toleriert führende Leerzeichen, jedoch keine nachfolgenden Leerzeichen, dh to_int(" 123")gibt 123 zurück, während eine to_int("123 ")Ausnahme ausgelöst wird. Stellen Sie sicher, dass dies für Ihren Anwendungsfall akzeptabel ist, oder passen Sie den Code an.

Eine solche Funktion könnte Teil von STL sein ...


2

Ich kenne drei Möglichkeiten, String in int umzuwandeln:

Verwenden Sie entweder die Funktion stoi (String to int) oder wählen Sie einfach Stringstream, den dritten Weg zur individuellen Konvertierung. Der Code ist unten aufgeführt:

1. Methode

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. Methode

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

3. Methode - aber nicht für eine individuelle Konvertierung

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

Ich mag Dans Antwort , besonders wegen der Vermeidung von Ausnahmen. Für die Entwicklung eingebetteter Systeme und andere Systementwicklungen auf niedriger Ebene ist möglicherweise kein geeignetes Ausnahme-Framework verfügbar.

Nach einer gültigen Zeichenfolge wurde eine Prüfung auf Leerzeichen hinzugefügt ... diese drei Zeilen

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


Es wurde auch eine Überprüfung auf Analysefehler hinzugefügt.

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


Hier ist die komplette Funktion ..

#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 hat Code hinzugefügt, um die von Ihnen erwähnten Bedenken auszuräumen.
Pellucide

1) Fehler mit Eingabe wie kann immer noch nicht erkannt werden " ". strtol()wird nicht festgelegt, um festzulegen, errnowann keine Konvertierung erfolgt. Besser zu verwenden if (s == end) return INCONVERTIBLE; , um keine Konvertierung zu erkennen. Und dann if (*s == '\0' || *end != '\0')kann zu if (*end)2) vereinfachen || l > LONG_MAXund || l < LONG_MINkeinen Zweck erfüllen - sie sind nie wahr.
chux

@chux Auf einem Mac ist die Errno für Analysefehler festgelegt, unter Linux ist die Errno jedoch nicht festgelegt. Der Code wurde so geändert, dass er vom "End" -Zeiger abhängt, um dies zu erkennen.
Pellucide

0

Sie können diese definierte Methode verwenden.

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

Und wenn Sie von String in eine Ganzzahl konvertieren würden, würden Sie einfach Folgendes tun.

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

Die Ausgabe wäre 102.


4
idk. Das Schreiben eines Definitionsmakros atoischeint angesichts anderer Antworten wie der akzeptierten nicht wie "C ++" zu sein std::stoi().
Eugene Yokota

Ich finde es lustiger mit vordefinierten Methoden: P
Boris

0

Ich weiß, dass dies eine ältere Frage ist, aber ich bin so oft darauf gestoßen und habe bis heute noch keine gut vorgestellte Lösung mit den folgenden Merkmalen gefunden:

  • Kann jede Basis konvertieren (und Basistyp erkennen)
  • Erkennt fehlerhafte Daten (dh stellt sicher, dass die gesamte Zeichenfolge, weniger führende / nachfolgende Leerzeichen, von der Konvertierung verbraucht wird)
  • Stellt sicher, dass der Bereich des Zeichenfolgenwerts unabhängig vom konvertierten Typ akzeptabel ist.

Also, hier ist meine mit einem Testband. Da die C-Funktionen strtoull / strtoll unter der Haube verwendet werden, wird immer zuerst der größte verfügbare Typ konvertiert. Wenn Sie dann nicht den größten Typ verwenden, werden zusätzliche Bereichsprüfungen durchgeführt, um sicherzustellen, dass Ihr Typ nicht über- (unter) geflossen ist. Dafür ist es etwas weniger performant als wenn man strtol / strtoul richtig gewählt hat. Es funktioniert jedoch auch für Kurzfilme / Zeichen, und meines Wissens gibt es auch keine Standardbibliotheksfunktion, die dies tut.

Genießen; hoffentlich findet es jemand nützlich.

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

StringToDecimalist die User-Land-Methode; es ist überladen, so dass es entweder wie folgt aufgerufen werden kann:

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

oder dieses:

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

Ich hasse es, den int-Typ zu wiederholen, also bevorzuge ich letzteren. Dies stellt sicher, dass bei einer Änderung des Typs "a" keine schlechten Ergebnisse erzielt werden. Ich wünschte, der Compiler könnte es so herausfinden:

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

... aber C ++ leitet keine Vorlagenrückgabetypen ab, das ist also das Beste, was ich bekommen kann.

Die Implementierung ist ziemlich einfach:

CstrtoxllWrapperWraps beide strtoullund strtoll, basierend auf der Signatur des Vorlagentyps, je nachdem, was erforderlich ist, und Bereitstellung einiger zusätzlicher Garantien (z. B. ist eine negative Eingabe nicht zulässig, wenn sie nicht signiert ist, und stellt sicher, dass die gesamte Zeichenfolge konvertiert wurde).

CstrtoxllWrapperwird von StringToSignedund StringToUnsignedmit dem größten Typ (long long / unsigned long long) verwendet, der dem Compiler zur Verfügung steht; Dadurch kann die maximale Konvertierung durchgeführt werden. Falls erforderlich, führt StringToSigned/ dann StringToUnsigneddie endgültigen Bereichsprüfungen für den zugrunde liegenden Typ durch. Schließlich StringToDecimalentscheidet die Endpunktmethode basierend auf der Signatur des zugrunde liegenden Typs, welche der StringTo * -Vorlagenmethoden aufgerufen werden soll.

Ich denke, der größte Teil des Mülls kann vom Compiler optimiert werden. Fast alles sollte zur Kompilierungszeit deterministisch sein. Jeder Kommentar zu diesem Aspekt wäre für mich interessant!


"größten Typ verwenden" -> warum long longstatt intmax_t?
chux

Zuversichtlich, dass Sie wollen if (ePtr != str). Verwenden Sie außerdem isspace((unsigned char) *ePtr), um negative Werte von richtig zu behandeln *ePtr.
chux

-3

In C können Sie verwenden int atoi (const char * str),

Analysiert den C-String str und interpretiert seinen Inhalt als ganzzahlige Zahl, die als Wert vom Typ int zurückgegeben wird.


2
Wie ich atoiin der Frage verlinkt habe , bin ich mir dessen bewusst. Die Frage betrifft eindeutig nicht C, sondern C ++. -1
Eugene Yokota
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.