Wie kann C ++ eine Zeichenfolge (angegeben als char *) in ein int analysieren? Eine robuste und klare Fehlerbehandlung ist ein Plus (anstatt Null zurückzugeben ).
Wie kann C ++ eine Zeichenfolge (angegeben als char *) in ein int analysieren? Eine robuste und klare Fehlerbehandlung ist ein Plus (anstatt Null zurückzugeben ).
Antworten:
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
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 true
und i
den Wert erhalten 1337
. Wir können dieses Problem umgehen, indem wir sicherstellen, dass stringstream
nach 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 stringstream
gibt 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, stringstream
lä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_cast
für diese . Überlegen Sie, was die lexical_cast
Dokumentation 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 stringstream
das Kontrollniveau schlecht ist, und dennoch stringstream
sollte es verwendet werden, anstatt, lexical_cast
wenn Sie "ein höheres Kontrollniveau" benötigen. Da lexical_cast
es sich nur um einen Wrapper handelt, stringstream
treten dieselben Probleme stringstream
auf: schlechte Unterstützung für mehrere Zahlenbasen und schlechte Fehlerbehandlung.
Glücklicherweise hat jemand bereits alle oben genannten Probleme gelöst. Die C-Standardbibliothek enthält strtol
und 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 base
Null (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 gibt absolut keinen guten Grund, eine andere Methode anzuwenden.
strtol
threadsicher sein. POSIX erfordert außerdem die errno
Verwendung eines threadlokalen Speichers. Selbst auf Nicht-POSIX-Systemen verwenden fast alle Implementierungen errno
auf Multithread-Systemen threadlokalen Speicher. Der neueste C ++ - Standard muss errno
POSIX-kompatibel sein. Der neueste C-Standard erfordert ebenfallserrno
einen threadlokalen Speicher. Selbst unter Windows, das definitiv nicht POSIX-kompatibel ist, errno
ist es threadsicher und im weiteren Sinne auch strtol
.
std::stol
dafür, dass Ausnahmen angemessen ausgelöst werden, anstatt Konstanten zurückzugeben.
std::stol
sogar 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::strtol
C-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.
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)
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.
Sie können Boosts verwendenlexical_cast
, die dies in eine allgemeinere Oberfläche einschließen.
lexical_cast<Target>(Source)
wirft bad_lexical_cast
auf Versagen.
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 ++.
Sie können Stringstreams verwenden
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
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;
}
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
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'
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.
Ab C ++ 17 können Sie std::from_chars
ab 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.
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::stoi
dazu funktioniert es in Pre-C ++ 11. Auch im Gegensatz zu std::stoi
, boost::lexical_cast
undstringstream
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 ...
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 ;
}
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;
}
" "
. strtol()
wird nicht festgelegt, um festzulegen, errno
wann 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_MAX
und || l < LONG_MIN
keinen Zweck erfüllen - sie sind nie wahr.
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.
atoi
scheint angesichts anderer Antworten wie der akzeptierten nicht wie "C ++" zu sein std::stoi()
.
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:
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;
}
StringToDecimal
ist 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:
CstrtoxllWrapper
Wraps beide strtoull
und 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).
CstrtoxllWrapper
wird von StringToSigned
und StringToUnsigned
mit 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 StringToUnsigned
die endgültigen Bereichsprüfungen für den zugrunde liegenden Typ durch. Schließlich StringToDecimal
entscheidet 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!
long long
statt intmax_t
?
if (ePtr != str)
. Verwenden Sie außerdem isspace((unsigned char) *ePtr)
, um negative Werte von richtig zu behandeln *ePtr
.
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.
atoi
in der Frage verlinkt habe , bin ich mir dessen bewusst. Die Frage betrifft eindeutig nicht C, sondern C ++. -1