Come analizzare una stringa in un int in C ++?


261

Qual è il modo C ++ di analizzare una stringa (data come char *) in un int? Una gestione degli errori chiara e affidabile è un vantaggio (anziché restituire zero ).


21
Che ne dici di alcuni degli esempi seguenti: codeproject.com/KB/recipes/Tokenizer.aspx Sono molto efficienti e in qualche modo eleganti

@Beh Tou Cheh, se pensi che sia un buon modo per analizzare int, ti preghiamo di pubblicarlo come risposta.
Eugene Yokota,

Risposte:


165

Nel nuovo C ++ 11 ci sono funzioni per questo: stoi, stol, stoll, stoul e così via.

int myNr = std::stoi(myString);

Genererà un'eccezione sull'errore di conversione.

Anche queste nuove funzioni hanno ancora lo stesso problema rilevato da Dan: convertiranno felicemente la stringa "11x" in numero intero "11".

Scopri di più: http://en.cppreference.com/w/cpp/string/basic_string/stol


4
Ma accettano argomenti di questo, uno dei quali è un punto a size_t che, se non è nullo, è impostato sul primo carattere non convertito
Zharf

Sì, utilizzando il secondo parametro di std :: stoi è possibile rilevare input non validi. Devi comunque eseguire la tua funzione di conversione ...
CC.

Proprio come ha fatto la risposta accettata, ma con queste funzioni standard sarebbe molto più pulito, imo
Zharf

Tieni presente che il secondo argomento di queste funzioni può essere utilizzato per dire se l'intera stringa è stata convertita o meno. Se il risultato size_tnon è uguale alla lunghezza della stringa, si è interrotto in anticipo. Restituirà comunque 11 in quel caso, ma possarà 2 invece della lunghezza della stringa 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Zoe

204

Cosa non fare

Ecco il mio primo consiglio: non usare stringstream per questo . Mentre all'inizio può sembrare semplice da usare, scoprirai che devi fare molto lavoro extra se vuoi robustezza e buona gestione degli errori.

Ecco un approccio che intuitivamente sembra che dovrebbe funzionare:

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

Questo ha un grosso problema: str2int(i, "1337h4x0r")tornerà felicemente truee iotterrà il valore 1337. Possiamo aggirare questo problema assicurandoci che non ci siano più caratteri nel stringstreamdopo la conversione:

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

Abbiamo risolto un problema, ma ci sono ancora un paio di altri problemi.

Cosa succede se il numero nella stringa non è base 10? Possiamo provare ad accogliere altre basi impostando lo stream sulla modalità corretta (ad es. ss << std::hex) Prima di provare la conversione. Ma questo significa che il chiamante deve sapere a priori quale base è il numero - e come può forse chiamarlo? Il chiamante non sa ancora quale sia il numero. Non sanno nemmeno che lo siaun numero! Come ci si può aspettare che sappiano che base è? Potremmo semplicemente imporre che tutti i numeri immessi nei nostri programmi debbano essere base 10 e rifiutare l'input esadecimale o ottale come non valido. Ma questo non è molto flessibile o robusto. Non esiste una soluzione semplice a questo problema. Non puoi semplicemente provare la conversione una volta per ogni base, perché la conversione decimale avrà sempre successo per i numeri ottali (con uno zero iniziale) e la conversione ottale potrebbe avere successo per alcuni numeri decimali. Quindi ora devi controllare lo zero iniziale. Ma aspetta! I numeri esadecimali possono iniziare anche con uno zero iniziale (0x ...). Sospiro.

Anche se riesci a gestire i problemi di cui sopra, c'è ancora un altro problema più grande: cosa succede se il chiamante deve distinguere tra input errato (ad es. "123foo") e un numero che non rientra nell'intervallo di int(ad es. "4000000000" per 32 bit int)? Con stringstream, non c'è modo di fare questa distinzione. Sappiamo solo se la conversione ha avuto esito positivo o negativo. Se fallisce, non abbiamo modo di sapere perché abbia fallito. Come puoi vedere, stringstreamlascia molto a desiderare se vuoi robustezza e chiara gestione degli errori.

Questo mi porta al mio secondo consiglio: non usare Boost lexical_castper questo . Considera cosa lexical_castdice la documentazione:

Laddove è richiesto un maggiore livello di controllo sulle conversioni, std :: stringstream e std :: wstringstream offrono un percorso più appropriato. Laddove sono richieste conversioni non basate su stream, lexical_cast è lo strumento sbagliato per il lavoro e non è sottoposto a casi speciali per tali scenari.

Che cosa?? Abbiamo già visto che stringstreamha un basso livello di controllo, eppure dice che stringstreamdovrebbe essere usato invece che lexical_castse hai bisogno di "un livello di controllo più alto". Inoltre, poiché lexical_castè solo un wrapper in giro stringstream, soffre degli stessi problemi che stringstreampresenta: scarso supporto per più basi di numeri e scarsa gestione degli errori.

La migliore soluzione

Fortunatamente, qualcuno ha già risolto tutti i problemi di cui sopra. La libreria standard C contiene una strtolfamiglia che non presenta nessuno di questi problemi.

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

Abbastanza semplice per qualcosa che gestisce tutti i casi di errore e supporta anche qualsiasi base numerica da 2 a 36. Se baseè zero (impostazione predefinita) proverà a convertire da qualsiasi base. Oppure il chiamante può fornire il terzo argomento e specificare che la conversione deve essere tentata solo per una base particolare. È robusto e gestisce tutti gli errori con un minimo sforzo.

Altri motivi per preferire strtol(e la famiglia):

  • Presenta prestazioni di runtime molto migliori
  • Introduce meno tempo di compilazione in termini di tempo di compilazione (gli altri generano quasi 20 volte più SLOC dalle intestazioni)
  • Ne risulta la dimensione del codice più piccola

Non c'è assolutamente alcun buon motivo per usare qualsiasi altro metodo.


22
@JamesDunne: POSIX strtoldeve essere thread-safe. POSIX richiede inoltre errnodi utilizzare l'archiviazione locale thread. Anche su sistemi non POSIX, quasi tutte le implementazioni errnosu sistemi multithread utilizzano l'archiviazione thread-local. L'ultimo standard C ++ richiede la errnoconformità POSIX. L'ultimo standard C richiede inoltre errnodi disporre di un archivio locale thread. Anche su Windows, che non è assolutamente conforme a POSIX, errnoè thread-safe e, per estensione, lo è anche strtol.
Dan Molding,

7
Non riesco davvero a seguire il tuo ragionamento contro l'uso di boost :: lexical_cast. Come si suol dire, std :: stringstream offre davvero molto controllo: fai tutto dal controllo degli errori alla determinazione di te stesso. La documentazione attuale lo pone così: "Per conversioni più coinvolte, come ad esempio dove la precisione o la formattazione richiedono un controllo più rigoroso di quello offerto dal comportamento predefinito di lexical_cast, si consiglia l'approccio convenzionale std :: stringstream."
FHD

8
Questa è una codifica C inappropriata all'interno di C ++. La libreria standard contiene std::stolper questo, che genererà opportunamente eccezioni anziché restituire costanti.
fuzzy

22
@fuzzyTew Ho scritto questa risposta prima std::stolancora di essere aggiunto al linguaggio C ++. Detto questo, non credo sia giusto dire che si tratta di "codifica C in C ++". È sciocco dire che std::strtolè la codifica C quando fa esplicitamente parte del linguaggio C ++. La mia risposta si applicava perfettamente al C ++ quando è stato scritto e si applica anche al nuovo std::stol. La chiamata di funzioni che possono generare eccezioni non è sempre la migliore per ogni situazione di programmazione.
Dan Molding,

9
@fuzzyTew: esaurire lo spazio su disco è una condizione eccezionale. I file di dati non formattati prodotti dal computer sono una condizione eccezionale. Ma i refusi nell'input dell'utente non sono eccezionali. È utile disporre di un approccio di analisi in grado di gestire errori di analisi normali e non eccezionali.
Ben Voigt,

67

Questo è un modo C più sicuro di atoi ()

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

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

C ++ con stringhe di libreria standard : (grazie CMS )

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

Con libreria boost : (grazie 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
}

Modifica: corretta la versione di Stringstream in modo che gestisse gli errori. (grazie al commento di CMS e jk sul post originale)


1
si prega di aggiornare la versione di stringstream per includere un controllo per stringstream :: fail () (come richiesto dall'interrogatore "Gestione degli errori chiara e affidabile")
jk.

2
La tua versione stringstream accetterà cose come "10haha" senza lamentarti
Johannes Schaub - litb

3
cambiarlo in (! (ss >> num) .fail () && (ss >> ws) .eof ()) da ((ss >> num) .fail ()) se si desidera la stessa gestione di lexical_cast
Johannes Schaub - litb

3
Il C ++ con il metodo stringstream della libreria standard non funziona con stringhe come "12-SomeString" anche con il controllo .fail ().
captonssj,

C ++ 11 include funzioni veloci standard per questo ora
fuzzyTew

21

Il buon vecchio modo di C funziona ancora. Raccomando strtol o strtoul. Tra lo stato di ritorno e 'endPtr', è possibile fornire un buon output diagnostico. Gestisce bene anche più basi.


4
Oh, per favore, non usare questa vecchia roba C durante la programmazione di C ++. Ci sono modi migliori / più semplici / più puliti / più moderni / più sicuri per farlo in C ++!
jk.

27
È divertente quando le persone sono preoccupate per i modi "più moderni" per risolvere un problema.
J Miller,

@Jason, la sicurezza del tipo IMO più forte e la gestione degli errori è un'idea più moderna rispetto a quella di C.
Eugene Yokota,

6
Ho esaminato le altre risposte e finora nulla è ovviamente migliore / più facile / più pulito o più sicuro. Il poster diceva che aveva un personaggio *. Ciò limita la quantità di sicurezza che otterrai :)
Chris Arguin,


16

È possibile utilizzare a stringstream dal libraray standard C ++:

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

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

Lo stato del flusso verrà impostato in modo da non riuscire se viene rilevata una non cifra durante il tentativo di leggere un numero intero.

Vedi le insidie ​​dello streaming per le insidie ​​della gestione degli errori e dei flussi in C ++.


2
Il metodo stringstream C ++ non funziona per stringhe come "12-SomeString" anche con il controllo "stream state".
captonssj,

10

Puoi usare stringstream

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

4
Ma questo non gestisce alcun errore. Devi controllare il flusso per errori.
jk.

1
Bene, devi controllare il flusso se ((ss >> num) .fail ()) {// ERRORE}
CMS

2
Il metodo stringstream C ++ non funziona con stringhe come "12-SomeString" anche con il controllo "stream state"
captonssj

8

Penso che questi tre link lo riassumano:

Le soluzioni stringstream e lexical_cast sono quasi uguali a quelle del cast lessicale che utilizza stringstream.

Alcune specializzazioni del cast lessicale usano un approccio diverso, vedi http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp per i dettagli. I numeri interi e float sono ora specializzati per la conversione da intero a stringa.

Si può specializzare lexical_cast per le proprie esigenze e renderlo veloce. Questa sarebbe la soluzione definitiva in grado di soddisfare tutte le parti, pulita e semplice.

Gli articoli già menzionati mostrano il confronto tra diversi metodi di conversione di stringhe di interi <->. I seguenti approcci hanno un senso: vecchio c-way, spirit.karma, fastformat, semplice ciclo ingenuo.

Lexical_cast è ok in alcuni casi, ad esempio per la conversione da int a stringa.

Convertire una stringa in int usando il cast lessicale non è una buona idea in quanto è 10-40 volte più lento di atoi a seconda della piattaforma / compilatore utilizzato.

Boost.Spirit.Karma sembra essere la libreria più veloce per convertire numeri interi in stringhe.

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

e il semplice loop di base dell'articolo menzionato sopra è un modo più veloce per convertire string in int, ovviamente non il più sicuro, strtol () sembra una soluzione più sicura

int naive_char_2_int(const char *p) {
    int x = 0;
    bool neg = false;
    if (*p == '-') {
        neg = true;
        ++p;
    }
    while (*p >= '0' && *p <= '9') {
        x = (x*10) + (*p - '0');
        ++p;
    }
    if (neg) {
        x = -x;
    }
    return x;
}

7

La libreria C ++ String Toolkit (StrTk) ha la seguente soluzione:

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 può essere di tipo unsigned char *, char * o std :: string iterators e si prevede che T sia un int con segno, come se int, int o long


1
ATTENZIONE Questa implementazione ha un bell'aspetto, ma per quanto ne so non gestisce gli overflow.
Vinnie Falco,

2
Il codice non gestisce l'overflow. v = (10 * v) + digit;trabocca inutilmente con l'input di stringa con il valore di testo di INT_MIN. La tabella ha un valore discutibile vs semplicementedigit >= '0' && digit <= '9'
chux - Ripristina Monica il

6

Se si dispone di C ++ 11, le soluzioni appropriate al giorno d'oggi sono il C ++ intero funzioni di conversione in <string>: stoi, stol, stoul, stoll, stoull. Fanno le opportune eccezioni quando ricevono input errati e usano le strto*funzioni veloci e piccole sotto il cofano.

Se sei bloccato con una revisione precedente di C ++, sarebbe imbarazzante per te imitare queste funzioni nella tua implementazione.


6

Da C ++ 17 in poi puoi usare std::from_charsdall'intestazione <charconv>come documentato qui .

Per esempio:

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

Come bonus, può anche gestire altre basi, come esadecimale.


3

Mi piace la risposta di Dan Moulding , aggiungerò solo un po 'di stile 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;
}

Funziona sia con std :: string che con const char * attraverso la conversione implicita. È anche utile per la conversione di base, ad esempio all to_int("0x7b")e to_int("0173")e to_int("01111011", 2)e to_int("0000007B", 16)e to_int("11120", 3)e to_int("3L", 34);restituire 123.

Diversamente std::stoifunziona in pre-C ++ 11. Anche a differenza di std::stoi, boost::lexical_castestringstream genera eccezioni per strane stringhe come "123hohoho".

NB: questa funzione tollera gli spazi iniziali ma non gli spazi finali, ovvero to_int(" 123")restituisce 123 mentre to_int("123 ")genera un'eccezione. Assicurati che questo sia accettabile per il tuo caso d'uso o modifica il codice.

Tale funzione potrebbe far parte di STL ...


2

Conosco tre modi per convertire String in int:

Utilizza la funzione stoi (String to int) o semplicemente usa Stringstream, il terzo modo per passare alla conversione individuale, il codice è sotto:

1o metodo

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

2o metodo

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

3o metodo - ma non per una conversione individuale

std::string str4 = "453";
int i = 0, in=0; // 453 as on
for ( i = 0; i < str4.length(); i++)
{

    in = str4[i];
    cout <<in-48 ;

}

1

Mi piace la risposta di Dan , specialmente per evitare le eccezioni. Per lo sviluppo di sistemi embedded e altri sistemi di basso livello, potrebbe non essere disponibile un framework di eccezioni adeguato.

Aggiunto un controllo per gli spazi bianchi dopo una stringa valida ... queste tre righe

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


Aggiunto un controllo anche per gli errori di analisi.

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


Ecco la funzione completa ..

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2long (long &l, char const *s, int base = 0)
{
    char *end = (char *)s;
    errno = 0;

    l = strtol(s, &end, base);

    if ((errno == ERANGE) && (l == LONG_MAX)) {
        return OVERFLOW;
    }
    if ((errno == ERANGE) && (l == LONG_MIN)) {
        return UNDERFLOW;
    }
    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }    
    while (isspace((unsigned char)*end)) {
        end++;
    }

    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }

    return SUCCESS;
}

@chux ha aggiunto il codice per occuparsi delle preoccupazioni che hai citato.
pellucide,

1) Non riesce ancora a rilevare l'errore con input simili " ". strtol()non è specificato per l'impostazione errnoquando non si verifica alcuna conversione. Meglio usare if (s == end) return INCONVERTIBLE; per rilevare nessuna conversione. E poi if (*s == '\0' || *end != '\0')può semplificare a if (*end)2) || l > LONG_MAXe || l < LONG_MINnon servire a nulla - non sono mai veri.
chux - Ripristina Monica il

@chux Su un Mac, Errno è impostato per l'analisi degli errori, ma su Linux Errno non è impostato. Codice modificato in base al puntatore "fine" per rilevarlo.
pellucide

0

È possibile utilizzare questo metodo definito.

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

E se dovessi convertire da String a un numero intero, dovresti semplicemente fare quanto segue.

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

L'output sarebbe 102.


4
idk. Scrivere una macro definita in giro atoinon sembra "il modo C ++", alla luce di altre risposte come quella accettata std::stoi().
Eugene Yokota,

Lo trovo più divertente usando metodi predefiniti: P
Boris

0

So che questa è una domanda più vecchia, ma l'ho trovata tante volte e, ad oggi, non ho ancora trovato una soluzione ben modellata con le seguenti caratteristiche:

  • Può convertire qualsiasi base (e rilevare il tipo di base)
  • Rileverà dati errati (cioè assicurerà che l'intera stringa, meno spazi bianchi iniziali / finali, venga consumata dalla conversione)
  • Garantirà che, indipendentemente dal tipo convertito, l'intervallo del valore della stringa sia accettabile.

Quindi, ecco il mio, con una cinghia di prova. Poiché utilizza le funzioni C strtoull / strtoll sotto il cofano, converte sempre prima il tipo più grande disponibile. Quindi, se non si utilizza il tipo più grande, eseguirà ulteriori controlli di intervallo per verificare che il tipo non sia stato superato (sotto). Per questo, è un po 'meno performante di se si scegliesse correttamente strtol / strtoul. Tuttavia, funziona anche per cortometraggi / caratteri e, per quanto ne so, non esiste alcuna funzione di libreria standard che lo faccia.

Godere; si spera che qualcuno lo trovi utile.

#include <cstdlib>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include <sstream>

static const int DefaultBase = 10;

template<typename T>
static inline T CstrtoxllWrapper(const char *str, int base = DefaultBase)
{
    while (isspace(*str)) str++; // remove leading spaces; verify there's data
    if (*str == '\0') { throw std::invalid_argument("str; no data"); } // nothing to convert

    // NOTE:  for some reason strtoull allows a negative sign, we don't; if
    //          converting to an unsigned then it must always be positive!
    if (!std::numeric_limits<T>::is_signed && *str == '-')
    { throw std::invalid_argument("str; negative"); }

    // reset errno and call fn (either strtoll or strtoull)
    errno = 0;
    char *ePtr;
    T tmp = std::numeric_limits<T>::is_signed ? strtoll(str, &ePtr, base)
                                              : strtoull(str, &ePtr, base);

    // check for any C errors -- note these are range errors on T, which may
    //   still be out of the range of the actual type we're using; the caller
    //   may need to perform additional range checks.
    if (errno != 0) 
    {
            if (errno == ERANGE) { throw std::range_error("str; out of range"); }
            else if (errno == EINVAL) { throw std::invalid_argument("str; EINVAL"); }
            else { throw std::invalid_argument("str; unknown errno"); }
    }

    // verify everything converted -- extraneous spaces are allowed
    if (ePtr != NULL)
    {
            while (isspace(*ePtr)) ePtr++;
            if (*ePtr != '\0') { throw std::invalid_argument("str; bad data"); }
    }

    return tmp;
}

template<typename T>
T StringToSigned(const char *str, int base = DefaultBase)
{
    static const long long max = std::numeric_limits<T>::max();
    static const long long min = std::numeric_limits<T>::min();

    long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp < min || tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit signed range (";
            if (sizeof(T) != 1) err << min << ".." << max;
            else err << (int) min << ".." << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
T StringToUnsigned(const char *str, int base = DefaultBase)
{
    static const unsigned long long max = std::numeric_limits<T>::max();

    unsigned long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit unsigned range (0..";
            if (sizeof(T) != 1) err << max;
            else err << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
inline T
StringToDecimal(const char *str, int base = DefaultBase)
{
    return std::numeric_limits<T>::is_signed ? StringToSigned<T>(str, base)
                                             : StringToUnsigned<T>(str, base);
}

template<typename T>
inline T
StringToDecimal(T &out_convertedVal, const char *str, int base = DefaultBase)
{
    return out_convertedVal = StringToDecimal<T>(str, base);
}

/*============================== [ Test Strap ] ==============================*/ 

#include <inttypes.h>
#include <iostream>

static bool _g_anyFailed = false;

template<typename T>
void TestIt(const char *tName,
            const char *s, int base,
            bool successExpected = false, T expectedValue = 0)
{
    #define FAIL(s) { _g_anyFailed = true; std::cout << s; }

    T x;
    std::cout << "converting<" << tName << ">b:" << base << " [" << s << "]";
    try
    {
            StringToDecimal<T>(x, s, base);
            // get here on success only
            if (!successExpected)
            {
                    FAIL(" -- TEST FAILED; SUCCESS NOT EXPECTED!" << std::endl);
            }
            else
            {
                    std::cout << " -> ";
                    if (sizeof(T) != 1) std::cout << x;
                    else std::cout << (int) x;  // don't print garbage chars
                    if (x != expectedValue)
                    {
                            FAIL("; FAILED (expected value:" << expectedValue << ")!");
                    }
                    std::cout << std::endl;
            }
    }
    catch (std::exception &e)
    {
            if (successExpected)
            {
                    FAIL(   " -- TEST FAILED; EXPECTED SUCCESS!"
                         << " (got:" << e.what() << ")" << std::endl);
            }
            else
            {
                    std::cout << "; expected exception encounterd: [" << e.what() << "]" << std::endl;
            }
    }
}

#define TEST(t, s, ...) \
    TestIt<t>(#t, s, __VA_ARGS__);

int main()
{
    std::cout << "============ variable base tests ============" << std::endl;
    TEST(int, "-0xF", 0, true, -0xF);
    TEST(int, "+0xF", 0, true, 0xF);
    TEST(int, "0xF", 0, true, 0xF);
    TEST(int, "-010", 0, true, -010);
    TEST(int, "+010", 0, true, 010);
    TEST(int, "010", 0, true, 010);
    TEST(int, "-10", 0, true, -10);
    TEST(int, "+10", 0, true, 10);
    TEST(int, "10", 0, true, 10);

    std::cout << "============ base-10 tests ============" << std::endl;
    TEST(int, "-010", 10, true, -10);
    TEST(int, "+010", 10, true, 10);
    TEST(int, "010", 10, true, 10);
    TEST(int, "-10", 10, true, -10);
    TEST(int, "+10", 10, true, 10);
    TEST(int, "10", 10, true, 10);
    TEST(int, "00010", 10, true, 10);

    std::cout << "============ base-8 tests ============" << std::endl;
    TEST(int, "777", 8, true, 0777);
    TEST(int, "-0111 ", 8, true, -0111);
    TEST(int, "+0010 ", 8, true, 010);

    std::cout << "============ base-16 tests ============" << std::endl;
    TEST(int, "DEAD", 16, true, 0xDEAD);
    TEST(int, "-BEEF", 16, true, -0xBEEF);
    TEST(int, "+C30", 16, true, 0xC30);

    std::cout << "============ base-2 tests ============" << std::endl;
    TEST(int, "-10011001", 2, true, -153);
    TEST(int, "10011001", 2, true, 153);

    std::cout << "============ irregular base tests ============" << std::endl;
    TEST(int, "Z", 36, true, 35);
    TEST(int, "ZZTOP", 36, true, 60457993);
    TEST(int, "G", 17, true, 16);
    TEST(int, "H", 17);

    std::cout << "============ space deliminated tests ============" << std::endl;
    TEST(int, "1337    ", 10, true, 1337);
    TEST(int, "   FEAD", 16, true, 0xFEAD);
    TEST(int, "   0711   ", 0, true, 0711);

    std::cout << "============ bad data tests ============" << std::endl;
    TEST(int, "FEAD", 10);
    TEST(int, "1234 asdfklj", 10);
    TEST(int, "-0xF", 10);
    TEST(int, "+0xF", 10);
    TEST(int, "0xF", 10);
    TEST(int, "-F", 10);
    TEST(int, "+F", 10);
    TEST(int, "12.4", 10);
    TEST(int, "ABG", 16);
    TEST(int, "10011002", 2);

    std::cout << "============ int8_t range tests ============" << std::endl;
    TEST(int8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(int8_t, "80", 16);
    TEST(int8_t, "-80", 16, true, std::numeric_limits<int8_t>::min());
    TEST(int8_t, "-81", 16);
    TEST(int8_t, "FF", 16);
    TEST(int8_t, "100", 16);

    std::cout << "============ uint8_t range tests ============" << std::endl;
    TEST(uint8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(uint8_t, "80", 16, true, std::numeric_limits<int8_t>::max()+1);
    TEST(uint8_t, "-80", 16);
    TEST(uint8_t, "-81", 16);
    TEST(uint8_t, "FF", 16, true, std::numeric_limits<uint8_t>::max());
    TEST(uint8_t, "100", 16);

    std::cout << "============ int16_t range tests ============" << std::endl;
    TEST(int16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(int16_t, "8000", 16);
    TEST(int16_t, "-8000", 16, true, std::numeric_limits<int16_t>::min());
    TEST(int16_t, "-8001", 16);
    TEST(int16_t, "FFFF", 16);
    TEST(int16_t, "10000", 16);

    std::cout << "============ uint16_t range tests ============" << std::endl;
    TEST(uint16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(uint16_t, "8000", 16, true, std::numeric_limits<int16_t>::max()+1);
    TEST(uint16_t, "-8000", 16);
    TEST(uint16_t, "-8001", 16);
    TEST(uint16_t, "FFFF", 16, true, std::numeric_limits<uint16_t>::max());
    TEST(uint16_t, "10000", 16);

    std::cout << "============ int32_t range tests ============" << std::endl;
    TEST(int32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(int32_t, "80000000", 16);
    TEST(int32_t, "-80000000", 16, true, std::numeric_limits<int32_t>::min());
    TEST(int32_t, "-80000001", 16);
    TEST(int32_t, "FFFFFFFF", 16);
    TEST(int32_t, "100000000", 16);

    std::cout << "============ uint32_t range tests ============" << std::endl;
    TEST(uint32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(uint32_t, "80000000", 16, true, std::numeric_limits<int32_t>::max()+1);
    TEST(uint32_t, "-80000000", 16);
    TEST(uint32_t, "-80000001", 16);
    TEST(uint32_t, "FFFFFFFF", 16, true, std::numeric_limits<uint32_t>::max());
    TEST(uint32_t, "100000000", 16);

    std::cout << "============ int64_t range tests ============" << std::endl;
    TEST(int64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(int64_t, "8000000000000000", 16);
    TEST(int64_t, "-8000000000000000", 16, true, std::numeric_limits<int64_t>::min());
    TEST(int64_t, "-8000000000000001", 16);
    TEST(int64_t, "FFFFFFFFFFFFFFFF", 16);
    TEST(int64_t, "10000000000000000", 16);

    std::cout << "============ uint64_t range tests ============" << std::endl;
    TEST(uint64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(uint64_t, "8000000000000000", 16, true, std::numeric_limits<int64_t>::max()+1);
    TEST(uint64_t, "-8000000000000000", 16);
    TEST(uint64_t, "-8000000000000001", 16);
    TEST(uint64_t, "FFFFFFFFFFFFFFFF", 16, true, std::numeric_limits<uint64_t>::max());
    TEST(uint64_t, "10000000000000000", 16);

    std::cout << std::endl << std::endl
              << (_g_anyFailed ? "!! SOME TESTS FAILED !!" : "ALL TESTS PASSED")
              << std::endl;

    return _g_anyFailed;
}

StringToDecimalè il metodo terra dell'utente; è sovraccarico, quindi può essere chiamato in questo modo:

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

o questo:

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

Odio ripetere il tipo int, quindi preferisco quest'ultimo. Ciò garantisce che se il tipo di "a" cambia, non si ottengono risultati negativi. Vorrei che il compilatore potesse capirlo come:

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

... ma C ++ non deduce i tipi di restituzione del modello, quindi è il migliore che posso ottenere.

L'implementazione è piuttosto semplice:

CstrtoxllWrapperesegue il wrapping di entrambi strtoulle strtoll, chiamando qualunque sia necessario in base alla firma del tipo di modello e fornendo alcune garanzie aggiuntive (ad esempio, l'input negativo non è consentito se non firmato e garantisce che l'intera stringa sia stata convertita).

CstrtoxllWrapperè utilizzato da StringToSignede StringToUnsignedcon il tipo più grande (long long / unsigned long long) disponibile per il compilatore; ciò consente di eseguire la conversione massima. Quindi, se necessario, StringToSigned/ StringToUnsignedesegue i controlli dell'intervallo finale sul tipo sottostante. Infine, il metodo end-point,StringToDecimal , decide quale dei metodi del modello StringTo * chiamare in base alla firma del tipo sottostante.

Penso che la maggior parte della spazzatura possa essere ottimizzata dal compilatore; quasi tutto dovrebbe essere deterministico in fase di compilazione. Qualsiasi commento su questo aspetto sarebbe interessante per me!


"usa il tipo più grande" -> perché long longinvece di intmax_t?
chux - Ripristina Monica il

Fiducioso che vuoi if (ePtr != str). Inoltre, utilizzare isspace((unsigned char) *ePtr)per gestire correttamente i valori negativi di *ePtr.
chux - Ripristina Monica il

-3

In C, puoi usare int atoi (const char * str),

Analizza la stringa C-string interpretando il suo contenuto come un numero integrale, che viene restituito come valore di tipo int.


2
Come ho collegato alla atoidomanda, ne sono consapevole. La domanda chiaramente non riguarda C, ma C ++. -1
Eugene Yokota,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.