Jaki jest sposób C ++ parsowania łańcucha (podanego jako char *) w int? Solidna i przejrzysta obsługa błędów to plus (zamiast zwracania zera ).
Jaki jest sposób C ++ parsowania łańcucha (podanego jako char *) w int? Solidna i przejrzysta obsługa błędów to plus (zamiast zwracania zera ).
Odpowiedzi:
W nowym C ++ 11 są do tego funkcje: stoj, stol, stoll, stoul i tak dalej.
int myNr = std::stoi(myString);
Zgłasza wyjątek dotyczący błędu konwersji.
Nawet te nowe funkcje nadal mają ten sam problem, co zauważył Dan: z przyjemnością przekonwertują ciąg „11x” na liczbę całkowitą „11”.
Zobacz więcej: http://en.cppreference.com/w/cpp/string/basic_string/stol
size_t
nie jest równy długości łańcucha, to zatrzymał się wcześnie. W takim przypadku nadal zwróci 11, ale pos
będzie 2 zamiast długości łańcucha 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Oto moja pierwsza rada: nie używaj do tego łańcucha znaków . Choć na początku może wydawać się prosty w użyciu, okaże się, że musisz wykonać wiele dodatkowej pracy, jeśli chcesz solidności i dobrej obsługi błędów.
Oto podejście, które intuicyjnie wydaje się działać:
bool str2int (int &i, char const *s)
{
std::stringstream ss(s);
ss >> i;
if (ss.fail()) {
// not an integer
return false;
}
return true;
}
Ma to poważny problem: str2int(i, "1337h4x0r")
chętnie wróci true
i i
otrzyma wartość 1337
. Możemy obejść ten problem, upewniając się, że stringstream
po konwersji nie ma więcej znaków :
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;
}
Rozwiązaliśmy jeden problem, ale wciąż istnieje kilka innych problemów.
Co się stanie, jeśli liczba w ciągu nie będzie podstawową wartością 10? Możemy spróbować dostosować inne zasady, ustawiając strumień w prawidłowy tryb (np. ss << std::hex
) Przed próbą konwersji. Ale to oznacza, że dzwoniący musi z góry wiedzieć, na jakiej podstawie jest ten numer - i skąd dzwoniący może to wiedzieć? Dzwoniący nie wie jeszcze, jaki jest numer. Nawet nie wiedzą, że tak jestnumer! Jak można oczekiwać, że będą wiedzieć, na jakiej podstawie? Możemy po prostu nakazać, aby wszystkie liczby wprowadzane do naszych programów musiały być podstawową wartością 10 i odrzucać dane szesnastkowe lub ósemkowe jako nieprawidłowe. Ale to nie jest zbyt elastyczne ani solidne. Nie ma prostego rozwiązania tego problemu. Nie można po prostu spróbować konwersji raz dla każdej podstawy, ponieważ konwersja dziesiętna zawsze powiedzie się dla liczb ósemkowych (z wiodącym zerem), a konwersja ósemkowa może się powieść dla niektórych liczb dziesiętnych. Więc teraz musisz sprawdzić wiodące zero. Ale poczekaj! Liczby szesnastkowe mogą również zaczynać się od zera na początku (0x ...). Westchnienie.
Nawet jeśli uda ci się poradzić sobie z powyższymi problemami, istnieje jeszcze jeden większy problem: co, jeśli dzwoniący będzie musiał odróżnić złe wejście (np. „123foo”) od liczby spoza zakresu int
(np. „4000000000” dla 32-bit int
)? Dzięki stringstream
nie ma sposobu na dokonanie tego rozróżnienia. Wiemy tylko, czy konwersja się powiodła, czy nie. Jeśli to się nie powiedzie, nie możemy wiedzieć, dlaczego się nie udało. Jak widać, stringstream
pozostawia wiele do życzenia, jeśli chcesz solidności i wyraźnej obsługi błędów.
To prowadzi mnie do mojej drugiej rady: nie używaj lexical_cast
do tego wzmocnienia . Zastanów się, co lexical_cast
ma do powiedzenia dokumentacja:
Tam, gdzie wymagany jest wyższy stopień kontroli nad konwersjami, std :: stringstream i std :: wstringstream oferują bardziej odpowiednią ścieżkę. Tam, gdzie wymagane są konwersje nie oparte na strumieniu, lexical_cast jest niewłaściwym narzędziem dla zadania i nie jest specjalnie zaprojektowane do takich scenariuszy.
Co?? Widzieliśmy już, że stringstream
ma słaby poziom kontroli, a jednak mówi, że stringstream
należy go używać zamiast, lexical_cast
jeśli potrzebujesz „wyższego poziomu kontroli”. Ponadto, ponieważ lexical_cast
jest to tylko opakowanie stringstream
, cierpi z powodu tych samych problemów, stringstream
co: słaba obsługa wielu baz danych i słaba obsługa błędów.
Na szczęście ktoś już rozwiązał wszystkie powyższe problemy. Biblioteka standardowa C zawiera strtol
i rodzinę, która nie ma żadnego z tych problemów.
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;
}
Całkiem proste jak na coś, co obsługuje wszystkie przypadki błędów, a także obsługuje dowolną bazę liczb od 2 do 36. Jeśli base
wynosi zero (domyślnie), spróbuje przekonwertować z dowolnej bazy. Lub osoba dzwoniąca może podać trzeci argument i określić, że konwersję należy próbować tylko dla określonej bazy. Jest solidny i obsługuje wszystkie błędy przy minimalnym wysiłku.
Inne powody, aby preferować strtol
(i rodzinę):
Nie ma absolutnie żadnego powodu, aby używać innej metody.
strtol
bezpieczeństwa wątków. POSIX wymaga także errno
użycia lokalnego magazynu wątków. Nawet w systemach innych niż POSIX, prawie wszystkie implementacje errno
systemów wielowątkowych używają pamięci lokalnej. Najnowszy standard C ++ wymaga errno
zgodności z POSIX. Najnowszy standard C wymaga także errno
przechowywania w lokalnym wątku. Nawet w systemie Windows, który zdecydowanie nie jest zgodny z POSIX, errno
jest bezpieczny dla wątków, a co za tym idzie - również strtol
.
std::stol
opcje, które będą odpowiednio zgłaszać wyjątki, a nie zwracać stałe.
std::stol
nawet dodałem ją do języka C ++. To powiedziawszy, nie sądzę, że można uczciwie powiedzieć, że jest to „kodowanie C w C ++”. Głupie jest twierdzenie, że std::strtol
jest to kodowanie C, gdy jest to wyraźnie część języka C ++. Moja odpowiedź idealnie pasowała do C ++, kiedy została napisana i nadal obowiązuje nawet w przypadku nowej std::stol
. Wywoływanie funkcji, które mogą generować wyjątki, nie zawsze jest najlepsze w każdej sytuacji programowania.
Jest to bezpieczniejszy sposób w C niż atoi ()
const char* str = "123";
int i;
if(sscanf(str, "%d", &i) == EOF )
{
/* error */
}
C ++ ze standardowym ciągiem znaków biblioteki : (dzięki CMS )
int str2int (const string &str) {
stringstream ss(str);
int num;
if((ss >> num).fail())
{
//ERROR
}
return num;
}
Z biblioteką doładowań : (dzięki 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
}
Edycja: Naprawiono wersję ciągu, która obsługuje błędy. (dzięki komentarzowi CMS i jk do oryginalnego postu)
Dobry „stary” sposób nadal działa. Polecam strtol lub strtoul. Pomiędzy statusem zwracanym a „endPtr” można uzyskać dobre wyniki diagnostyczne. Ładnie obsługuje również wiele baz.
Możesz użyć wzmocnienialexical_cast
, które otacza to bardziej ogólny interfejs.
lexical_cast<Target>(Source)
rzuca się bad_lexical_cast
na porażkę.
Możesz użyć ciągu znaków ze standardowego biblioteki bibliotek C ++:
stringstream ss(str);
int x;
ss >> x;
if(ss) { // <-- error handling
// use x
} else {
// not a number
}
Stan strumienia zostanie ustawiony na niepowodzenie, jeśli podczas próby odczytania liczby całkowitej wystąpi cyfra.
Zobacz pułapki pułapek dla pułapek obsługi błędów i strumieni w C ++.
Możesz użyć ciągu znaków
int str2int (const string &str) {
stringstream ss(str);
int num;
ss >> num;
return num;
}
Myślę, że te trzy linki podsumowują to:
Rozwiązania stringstream i lexical_cast są mniej więcej takie same, jak rzutowanie leksykalne przy użyciu stringstream.
Niektóre specjalizacje obsady leksykalnej wykorzystują inne podejście, patrz http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp w celu uzyskania szczegółowych informacji. Liczby całkowite i zmiennoprzecinkowe specjalizują się teraz w konwersji liczb całkowitych na ciągi znaków.
Można specjalizować lexical_cast dla własnych potrzeb i sprawić, by był szybki. To byłoby najlepsze rozwiązanie zadowalające wszystkie strony, czyste i proste.
Wspomniane już artykuły pokazują porównanie różnych metod konwersji liczb całkowitych <-> ciągów. Sensowne są następujące podejścia: stara c-way, spirit.karma, fastformat, prosta naiwna pętla.
Leksyk_cast jest w niektórych przypadkach ok, np. Do konwersji int na ciąg znaków.
Konwersja napisów na int przy użyciu rzutowania leksykalnego nie jest dobrym pomysłem, ponieważ jest 10-40 razy wolniejsza niż atoi w zależności od użytej platformy / kompilatora.
Boost.Spirit.Karma wydaje się być najszybszą biblioteką do konwersji liczb całkowitych na ciąg.
ex.: generate(ptr_char, int_, integer_number);
a prosta prosta pętla z wyżej wymienionego artykułu jest najszybszym sposobem konwersji łańcucha znaków na int, oczywiście nie najbezpieczniejszym, strtol () wydaje się bezpieczniejszym rozwiązaniem
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;
}
Biblioteka C ++ String Toolkit Library (StrTk) ma następujące rozwiązanie:
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 może mieć iteratory bez znaku char *, char * lub std :: string, a oczekuje się, że T będzie znakiem int, takim jak int, int lub long
v = (10 * v) + digit;
przepełnia niepotrzebnie po wprowadzeniu ciągu o wartości tekstowej INT_MIN
. Tabela ma wątpliwą wartość vs po prostudigit >= '0' && digit <= '9'
Jeśli masz c ++ 11, odpowiednie rozwiązania w dzisiejszych czasach są C ++ całkowitą funkcji konwersji w <string>
: stoi
, stol
, stoul
, stoll
, stoull
. Zgłaszają odpowiednie wyjątki, gdy otrzymają nieprawidłowe dane wejściowe, i używają szybkiego i małegostrto*
funkcji pod maską.
Jeśli utkniesz z wcześniejszą wersją C ++, byłoby naśladowanie tych funkcji w twojej implementacji.
Od wersji C ++ 17 możesz korzystać std::from_chars
z <charconv>
nagłówka, jak tu udokumentowano .
Na przykład:
#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;
}
}
Jako bonus, może również obsługiwać inne bazy, takie jak szesnastkowy.
Podoba mi się odpowiedź Dana Mouldinga , dodam do niej trochę stylu 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;
}
Działa zarówno dla std :: string, jak i const char * poprzez niejawną konwersję. Jest także przydatny do konwersji bazowej, np. Wszystkie to_int("0x7b")
i to_int("0173")
oraz to_int("01111011", 2)
i to_int("0000007B", 16)
i to_int("11120", 3)
ito_int("3L", 34);
wróci 123.
W przeciwieństwie do std::stoi
tego działa w wersjach wcześniejszych niż C ++ 11. W przeciwieństwie do również std::stoi
, boost::lexical_cast
istringstream
rzuca wyjątki dziwne ciągi jak „123hohoho”.
NB: Ta funkcja toleruje spacje wiodące, ale nie spacje końcowe, tzn. to_int(" 123")
Zwraca 123, gdy to_int("123 ")
zgłasza wyjątek. Upewnij się, że jest to dopuszczalne w twoim przypadku użycia lub dostosuj kod.
Taka funkcja może być częścią STL ...
Znam trzy sposoby konwersji String na int:
Albo użyj funkcji stoj (String to int), albo po prostu skorzystaj z Stringstream, trzeciego sposobu przejścia na indywidualną konwersję, kod poniżej:
1. metoda
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. metoda
#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. metoda - ale nie w przypadku indywidualnej konwersji
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 ;
}
Podoba mi się odpowiedź Dana , szczególnie ze względu na unikanie wyjątków. W przypadku rozwoju systemów wbudowanych i innych systemów niskiego poziomu może nie być dostępna odpowiednia struktura wyjątków.
Dodano sprawdzenie spacji po prawidłowym ciągu ... te trzy linie
while (isspace(*end)) {
end++;
}
Dodano również sprawdzanie błędów analizy.
if ((errno != 0) || (s == end)) {
return INCONVERTIBLE;
}
Oto pełna funkcja ..
#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()
nie jest określony, aby ustawić, errno
gdy konwersja nie nastąpi. Lepiej używać, if (s == end) return INCONVERTIBLE;
aby wykryć brak konwersji. A następnie if (*s == '\0' || *end != '\0')
mogą uprościć do if (*end)
2) || l > LONG_MAX
i || l < LONG_MIN
nie służą żadnemu celowi - nigdy nie są prawdziwe.
Możesz użyć tej zdefiniowanej metody.
#define toInt(x) {atoi(x.c_str())};
A jeśli chcesz przekonwertować z ciągu na liczbę całkowitą, po prostu wykonaj następujące czynności.
int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}
Wynik wyniósłby 102.
atoi
nie wydaje się być „sposobem C ++” w świetle innych odpowiedzi, takich jak zaakceptowane std::stoi()
.
Wiem, że to starsze pytanie, ale natknąłem się na to wiele razy i do tej pory nie znalazłem ładnie szablonowego rozwiązania o następujących cechach:
Oto moja, z paskiem testowym. Ponieważ używa funkcji C strtoull / strtoll pod maską, zawsze konwertuje najpierw na największy dostępny typ. Następnie, jeśli nie używasz największego typu, przeprowadzi dodatkowe kontrole zakresu, aby sprawdzić, czy Twój typ nie został przepełniony (niedostatecznie). W tym celu jest nieco mniej wydajny niż wtedy, gdy ktoś właściwie wybrał strtol / strtoul. Działa to jednak również w przypadku skrótów / znaków i, zgodnie z moją najlepszą wiedzą, nie istnieje standardowa funkcja biblioteki, która by to robiła.
Cieszyć się; mam nadzieję, że ktoś uzna to za przydatne.
#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
jest metodą lądu użytkownika; jest przeciążony, więc można go nazwać tak:
int a; a = StringToDecimal<int>("100");
albo to:
int a; StringToDecimal(a, "100");
Nienawidzę powtarzania typu int, więc wolę ten drugi. Zapewnia to, że jeśli zmieni się rodzaj „a”, nie uzyska się złych wyników. Chciałbym, żeby kompilator mógł to rozgryźć w następujący sposób:
int a; a = StringToDecimal("100");
... ale C ++ nie przewiduje typów zwracanych szablonów, więc to najlepsze, co mogę uzyskać.
Implementacja jest dość prosta:
CstrtoxllWrapper
opakowuje oba elementy strtoull
i strtoll
wywołuje dowolne, które jest konieczne, w oparciu o podpisany typ szablonu i zapewniając dodatkowe gwarancje (np. negatywne dane wejściowe są niedozwolone, jeśli nie są podpisane, i zapewnia konwersję całego łańcucha).
CstrtoxllWrapper
jest stosowany w StringToSigned
i StringToUnsigned
z największym typu (Dawno / unsigned long long) dostępny kompilator; pozwala to na wykonanie maksymalnej konwersji. Następnie, jeśli jest to konieczne, StringToSigned
/ StringToUnsigned
przeprowadza ostateczne sprawdzenie zakresu dla typu bazowego. Wreszcie metoda punktu końcowego,StringToDecimal
decyduje, którą metodę szablonu StringTo * wywołać na podstawie podpisanego typu bazowego.
Myślę, że większość śmieci może zostać zoptymalizowana przez kompilator; prawie wszystko powinno determinować czas kompilacji. Wszelkie komentarze na ten temat byłyby dla mnie interesujące!
long long
zamiast intmax_t
?
if (ePtr != str)
. Ponadto użyj isspace((unsigned char) *ePtr)
do prawidłowej obsługi wartości ujemnych *ePtr
.
W C, można użyć int atoi (const char * str)
,
Analizuje ciąg znaków C interpretując jego zawartość jako liczbę całkowitą, która jest zwracana jako wartość typu int.
atoi
wspomniałem w pytaniu, jestem tego świadomy. Pytanie wyraźnie nie dotyczy C, ale C ++. -1