Jak parsować ciąg do int w C ++?

261

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

Eugene Yokota
źródło
21
Co powiesz na niektóre z poniższych przykładów: codeproject.com/KB/recipes/Tokenizer.aspx Są bardzo wydajne i nieco eleganckie
@Beh Tou Cheh, jeśli uważasz, że to dobry sposób na przeanalizowanie int, prześlij go jako odpowiedź.
Eugene Yokota,
1
Same dla C: stackoverflow.com/questions/7021725/...
Ciro Santilli郝海东冠状病六四事件法轮功

Odpowiedzi:

165

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

CC.
źródło
4
Ale akceptują argumenty, z których jeden jest punktem na size_t, który, jeśli nie zero, jest ustawiony na pierwszą nieprzekształconą postać
Zharf,
Tak, używając drugiego parametru std :: stoj, możesz wykryć nieprawidłowe dane wejściowe. Nadal musisz uruchomić własną funkcję konwersji ...
CC.
Tak jak zrobiła to odpowiedź zaakceptowana, ale przy tych standardowych funkcjach, które byłyby o wiele czystsze, imo
Zharf
Należy pamiętać, że drugiego argumentu w tych funkcjach można użyć do określenia, czy cały ciąg został przekonwertowany, czy nie. Jeśli wynik size_tnie jest równy długości łańcucha, to zatrzymał się wcześnie. W takim przypadku nadal zwróci 11, ale posbędzie 2 zamiast długości łańcucha 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Zoe
204

Czego nie robić

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 truei iotrzyma wartość 1337. Możemy obejść ten problem, upewniając się, że stringstreampo 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 stringstreamnie 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ć, stringstreampozostawia 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_castdo tego wzmocnienia . Zastanów się, co lexical_castma 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 stringstreamma słaby poziom kontroli, a jednak mówi, że stringstreamnależy go używać zamiast, lexical_castjeśli potrzebujesz „wyższego poziomu kontroli”. Ponadto, ponieważ lexical_castjest to tylko opakowanie stringstream, cierpi z powodu tych samych problemów, stringstreamco: słaba obsługa wielu baz danych i słaba obsługa błędów.

Najlepszym rozwiązaniem

Na szczęście ktoś już rozwiązał wszystkie powyższe problemy. Biblioteka standardowa C zawiera strtoli 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 basewynosi 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ę):

  • Wykazuje znacznie lepszą wydajność w czasie wykonywania
  • Wprowadza mniej czasu kompilacji (inne pobierają prawie 20 razy więcej SLOC z nagłówków)
  • Powoduje to najmniejszy rozmiar kodu

Nie ma absolutnie żadnego powodu, aby używać innej metody.

Dan Molding
źródło
22
@JamesDunne: POSIX wymaga strtolbezpieczeństwa wątków. POSIX wymaga także errnoużycia lokalnego magazynu wątków. Nawet w systemach innych niż POSIX, prawie wszystkie implementacje errnosystemów wielowątkowych używają pamięci lokalnej. Najnowszy standard C ++ wymaga errnozgodności z POSIX. Najnowszy standard C wymaga także errnoprzechowywania w lokalnym wątku. Nawet w systemie Windows, który zdecydowanie nie jest zgodny z POSIX, errnojest bezpieczny dla wątków, a co za tym idzie - również strtol.
Dan Molding,
7
Naprawdę nie mogę podążać za twoim rozumowaniem przeciwko użyciu boost :: lexical_cast. Jak mówią, std :: stringstream rzeczywiście oferuje wiele kontroli - robisz wszystko, od sprawdzania błędów do samodzielnego określania bazy. Obecna dokumentacja przedstawia to następująco: „W przypadku bardziej zaangażowanych konwersji, takich jak precyzja lub formatowanie, które wymagają ściślejszej kontroli niż jest to oferowane przez domyślne zachowanie lexical_cast, zalecane jest konwencjonalne podejście std :: stringstream”.
fhd
8
Jest to nieodpowiednie kodowanie C w C ++. Standardowa biblioteka zawiera takie std::stolopcje, które będą odpowiednio zgłaszać wyjątki, a nie zwracać stałe.
fuzzyTew
22
@fuzzyTew Napisałem tę odpowiedź wcześniej, std::stolnawet 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::strtoljest 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.
Dan Molding
9
@fuzzyTew: Brak miejsca na dysku to wyjątkowy warunek. Zniekształcone pliki danych produkowane komputerowo stanowią wyjątkowy warunek. Ale literówki w danych wprowadzanych przez użytkownika nie są wyjątkowe. Dobrze jest mieć podejście analizujące, które jest w stanie poradzić sobie z normalnymi, nietypowymi błędami analizowania.
Ben Voigt,
67

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)

Luka Marinko
źródło
1
zaktualizuj swoją wersję stringstream, aby zawierała sprawdzanie stringstream :: fail () (zgodnie z pytaniem pytającego „Solidna i przejrzysta obsługa błędów”)
jk.
2
Twoja wersja strunowa przyjmie takie rzeczy jak „10haha” bez narzekania
Johannes Schaub - litb
3
zmień na (! (ss >> num) .fail () && (ss >> ws) .eof ()) z ((ss >> num) .fail ()), jeśli chcesz mieć taką samą obsługę jak lexical_cast
Johannes Schaub - litb
3
C ++ ze standardową biblioteką ciąg znaków metody nie działa dla ciągów takich jak „12-SomeString”, nawet przy sprawdzeniu .fail ().
captonssj
C ++ 11 zawiera teraz standardowe szybkie funkcje
fuzzyTew
21

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.

Chris Arguin
źródło
4
Och, proszę, nie używaj tego starego C do programowania C ++. Istnieją lepsze / łatwiejsze / czystsze / nowocześniejsze / bezpieczniejsze sposoby na zrobienie tego w C ++!
jk.
27
To zabawne, gdy ludzie martwią się o „bardziej nowoczesne” sposoby rozwiązania problemu.
J Miller,
@Jason, IMO silniejsze bezpieczeństwo typu i obsługa błędów jest bardziej nowoczesnym pomysłem w porównaniu do C.
Eugene Yokota,
6
Przejrzałem inne odpowiedzi i jak dotąd nic nie jest oczywiście lepsze / łatwiejsze / czystsze lub bezpieczniejsze. Plakat powiedział, że ma char *. To ogranicza poziom bezpieczeństwa, jaki zamierzasz uzyskać :)
Chris Arguin,
16

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

jk.
źródło
2
Metoda łańcucha znaków C ++ nie działa dla ciągów takich jak „12-SomeString”, nawet przy sprawdzeniu „stanu strumienia”.
captonssj
10

Możesz użyć ciągu znaków

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}
CMS
źródło
4
Ale to nie obsługuje żadnych błędów. Musisz sprawdzić strumień pod kątem awarii.
jk.
1
Dobrze, musisz sprawdzić strumień, jeśli ((ss >> num) .fail ()) {// ERROR}
CMS
2
Metoda łańcucha znaków C ++ nie działa dla ciągów takich jak „12-SomeString”, nawet przy sprawdzeniu „stanu strumienia”
captonssj
8

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;
}
caa
źródło
7

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

Rup
źródło
1
OSTRZEŻENIE Ta implementacja wygląda ładnie, ale o ile wiem, nie obsługuje przelewów.
Vinnie Falco
2
Kod nie obsługuje przepełnienia. 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'
chux - Przywróć Monikę
6

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.

fuzzyTew
źródło
6

Od wersji C ++ 17 możesz korzystać std::from_charsz <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.

Pharap
źródło
3

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::stoitego działa w wersjach wcześniejszych niż C ++ 11. W przeciwieństwie do również std::stoi, boost::lexical_castistringstream 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 ...

użytkownik3925906
źródło
2

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 ;

}
Iqra.
źródło
1

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;
}
pellucide
źródło
@chux dodał kod, aby rozwiązać problemy, o których wspomniałeś.
pellucide
1) Nadal nie wykrywa błędu z wejściem podobnym " ". strtol()nie jest określony, aby ustawić, errnogdy 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_MAXi || l < LONG_MINnie służą żadnemu celowi - nigdy nie są prawdziwe.
chux - Przywróć Monikę
@chux Na Macu errno jest ustawione na błędy parsowania, ale w Linuksie errno nie jest ustawione. Zmieniono kod, aby zależał od wskaźnika „końca”, aby to wykryć.
pellucide
0

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.

Boris
źródło
4
Nie wiem. Pisanie makra definiującego atoinie wydaje się być „sposobem C ++” w świetle innych odpowiedzi, takich jak zaakceptowane std::stoi().
Eugene Yokota,
Boris
0

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:

  • Może konwertować dowolną bazę (i wykryć typ bazy)
  • Wykryje błędne dane (tj. Zapewni, że konwersja zajmie cały ciąg, mniej początkowych / końcowych białych znaków)
  • Zapewni, że niezależnie od typu przekonwertowanego zakresu, dopuszczalny jest zakres wartości ciągu.

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

StringToDecimaljest 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:

CstrtoxllWrapperopakowuje oba elementy strtoulli strtollwywoł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).

CstrtoxllWrapperjest stosowany w StringToSignedi StringToUnsignedz największym typu (Dawno / unsigned long long) dostępny kompilator; pozwala to na wykonanie maksymalnej konwersji. Następnie, jeśli jest to konieczne, StringToSigned/ StringToUnsignedprzeprowadza 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!

DreamWarrior
źródło
„użyj największego typu” -> dlaczego long longzamiast intmax_t?
chux - Przywróć Monikę
Pewnie chcesz if (ePtr != str). Ponadto użyj isspace((unsigned char) *ePtr)do prawidłowej obsługi wartości ujemnych *ePtr.
chux - Przywróć Monikę
-3

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.

Czarna Mamba
źródło
2
Jak atoiwspomniałem w pytaniu, jestem tego świadomy. Pytanie wyraźnie nie dotyczy C, ale C ++. -1
Eugene Yokota,