Come convertire una stringa in intero in C?


260

Sto cercando di scoprire se esiste un modo alternativo di convertire la stringa in intero in C.

Nel mio codice modifico regolarmente quanto segue.

char s[] = "45";

int num = atoi(s);

Quindi, c'è un modo migliore o un altro?


21
I tag e il titolo dicono che vuoi una soluzione in C, ma la tua domanda dice C o C ++. Quale vuoi?
In silico,

1
@Yann, mi dispiace per quella confusione. Preferirò C.
user618677

1
Funziona, ma non è il modo consigliato, perché non c'è modo di gestire gli errori. Non utilizzarlo mai nel codice di produzione a meno che non ci si possa fidare dell'input al 100%.
Uwe Geuder,

1
Definisci "meglio" e indica chiaramente perché hai bisogno di un altro modo.
Marchese di Lorne,

3
@EJP Solo per migliorarmi.
user618677

Risposte:


185

C'è strtolqual è l'IMO migliore. Inoltre mi è piaciuto strtonum, quindi usalo se ce l'hai (ma ricorda che non è portatile):

long long
     strtonum(const char *nptr, long long minval, long long maxval,
     const char **errstr);

MODIFICARE

Potresti anche essere interessato a strtoumaxestrtoimax quali sono le funzioni standard in C99. Ad esempio potresti dire:

uintmax_t num = strtoumax(s, NULL, 10);
if (num == UINTMAX_MAX && errno == ERANGE)
    /* Could not convert. */

Ad ogni modo, stai lontano da atoi:

Il call atoi (str) deve essere equivalente a:

(int) strtol(str, (char **)NULL, 10)

salvo che la gestione degli errori può differire. Se il valore non può essere rappresentato, il comportamento non è definito .


cosa devo includere per strtonum? Continuo a ricevere un avviso di dichiarazione implicita
jsj

@ trideceth12 Sui sistemi in cui è disponibile, deve essere dichiarato #<stdlib.h>. Tuttavia, è possibile utilizzare l' strtoumaxalternativa standard .
cnicutar,

4
Questa risposta non sembra più breve del primo codice dell'interrogatore.
Azurespot,

11
@NoniA. La concisione è sempre buona, ma non a scapito della correttezza.
cnicutar,

6
Non tanto sbagliato quanto non sicuro. atoi () funziona se l'input è valido. Ma cosa succede se si fa atoi ("gatto")? strtol () ha un comportamento definito se il valore non può essere rappresentato come long, atoi () no.
Daniel B.

27

Robusta strtolsoluzione basata su C89

Con:

  • nessun comportamento indefinito (come si potrebbe avere con la atoifamiglia)
  • una definizione più rigorosa di numero intero di strtol(ad es. nessuno spazio bianco iniziale o caratteri di cestino finali)
  • classificazione del caso di errore (ad es. per fornire utili messaggi di errore agli utenti)
  • una "test suite"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

typedef enum {
    STR2INT_SUCCESS,
    STR2INT_OVERFLOW,
    STR2INT_UNDERFLOW,
    STR2INT_INCONVERTIBLE
} str2int_errno;

/* Convert string s to int out.
 *
 * @param[out] out The converted int. Cannot be NULL.
 *
 * @param[in] s Input string to be converted.
 *
 *     The format is the same as strtol,
 *     except that the following are inconvertible:
 *
 *     - empty string
 *     - leading whitespace
 *     - any trailing characters that are not part of the number
 *
 *     Cannot be NULL.
 *
 * @param[in] base Base to interpret string in. Same range as strtol (2 to 36).
 *
 * @return Indicates if the operation succeeded, or why it failed.
 */
str2int_errno str2int(int *out, char *s, int base) {
    char *end;
    if (s[0] == '\0' || isspace(s[0]))
        return STR2INT_INCONVERTIBLE;
    errno = 0;
    long l = strtol(s, &end, base);
    /* Both checks are needed because INT_MAX == LONG_MAX is possible. */
    if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX))
        return STR2INT_OVERFLOW;
    if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN))
        return STR2INT_UNDERFLOW;
    if (*end != '\0')
        return STR2INT_INCONVERTIBLE;
    *out = l;
    return STR2INT_SUCCESS;
}

int main(void) {
    int i;
    /* Lazy to calculate this size properly. */
    char s[256];

    /* Simple case. */
    assert(str2int(&i, "11", 10) == STR2INT_SUCCESS);
    assert(i == 11);

    /* Negative number . */
    assert(str2int(&i, "-11", 10) == STR2INT_SUCCESS);
    assert(i == -11);

    /* Different base. */
    assert(str2int(&i, "11", 16) == STR2INT_SUCCESS);
    assert(i == 17);

    /* 0 */
    assert(str2int(&i, "0", 10) == STR2INT_SUCCESS);
    assert(i == 0);

    /* INT_MAX. */
    sprintf(s, "%d", INT_MAX);
    assert(str2int(&i, s, 10) == STR2INT_SUCCESS);
    assert(i == INT_MAX);

    /* INT_MIN. */
    sprintf(s, "%d", INT_MIN);
    assert(str2int(&i, s, 10) == STR2INT_SUCCESS);
    assert(i == INT_MIN);

    /* Leading and trailing space. */
    assert(str2int(&i, " 1", 10) == STR2INT_INCONVERTIBLE);
    assert(str2int(&i, "1 ", 10) == STR2INT_INCONVERTIBLE);

    /* Trash characters. */
    assert(str2int(&i, "a10", 10) == STR2INT_INCONVERTIBLE);
    assert(str2int(&i, "10a", 10) == STR2INT_INCONVERTIBLE);

    /* int overflow.
     *
     * `if` needed to avoid undefined behaviour
     * on `INT_MAX + 1` if INT_MAX == LONG_MAX.
     */
    if (INT_MAX < LONG_MAX) {
        sprintf(s, "%ld", (long int)INT_MAX + 1L);
        assert(str2int(&i, s, 10) == STR2INT_OVERFLOW);
    }

    /* int underflow */
    if (LONG_MIN < INT_MIN) {
        sprintf(s, "%ld", (long int)INT_MIN - 1L);
        assert(str2int(&i, s, 10) == STR2INT_UNDERFLOW);
    }

    /* long overflow */
    sprintf(s, "%ld0", LONG_MAX);
    assert(str2int(&i, s, 10) == STR2INT_OVERFLOW);

    /* long underflow */
    sprintf(s, "%ld0", LONG_MIN);
    assert(str2int(&i, s, 10) == STR2INT_UNDERFLOW);

    return EXIT_SUCCESS;
}

GitHub a monte .

Basato su: https://stackoverflow.com/a/6154614/895245


3
Bello robusto str2int(). Pedante: usare isspace((unsigned char) s[0]).
chux - Ripristina Monica

@chux grazie! Puoi spiegarci un po 'di più perché il (unsigned char)cast potrebbe fare la differenza?
Ciro Santilli 30 冠状 病 六四 事件 法轮功

IAR compilatore C avverte che l > INT_MAXe l < INT_MINsono il confronto intero inutile dal momento che sia risultato è sempre false. Cosa succede se li cambio in l >= INT_MAXe l <= INT_MINper cancellare gli avvisi? Su ARM C, long e int sono tipi di dati Basic con
segno a

@ecle la modifica del codice l >= INT_MAXcomporta una funzionalità errata: esempio di ritorno STR2INT_OVERFLOWcon input "32767"e 16 bit int. Utilizzare una compilazione condizionale. Esempio .
chux - Ripristina Monica il

if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;sarebbe meglio if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}consentire il codice chiamante da utilizzare errnoal di intfuori dell'intervallo. Lo stesso per if (l < INT_MIN....
chux - Ripristina Monica il

24

Non utilizzare le funzioni dal ato...gruppo. Questi sono rotti e praticamente inutili. Una soluzione moderatamente migliore sarebbe usare sscanf, anche se non è perfetta neanche.

Per convertire la stringa in numero intero, è strto...necessario utilizzare le funzioni del gruppo. Nel tuo caso specifico sarebbe strtolfunzionale.


7
sscanfha effettivamente un comportamento indefinito se tenta di convertire un numero al di fuori dell'intervallo del suo tipo (ad esempio sscanf("999999999999999999999", "%d", &n)).
Keith Thompson,

1
@Keith Thompson: Questo è esattamente ciò che intendo. atoinon fornisce alcun feedback di successo / fallimento significativo e ha un comportamento indefinito in caso di overflow. sscanffornisce una sorta di feedback di successo / fallimento (il valore di ritorno, che è ciò che lo rende "moderatamente migliore"), ma ha ancora un comportamento indefinito in caso di overflow. Solo strtolè una soluzione praticabile.
AnT

1
Concordato; Volevo solo sottolineare il problema potenzialmente fatale con sscanf. (Anche se confesso che a volte uso atoi, di solito per programmi che non mi aspetto di sopravvivere più di 10 minuti prima di eliminare la fonte.)
Keith Thompson,

5

Puoi scrivere un po 'di atoi () per divertimento:

int my_getnbr(char *str)
{
  int result;
  int puiss;

  result = 0;
  puiss = 1;
  while (('-' == (*str)) || ((*str) == '+'))
  {
      if (*str == '-')
        puiss = puiss * -1;
      str++;
  }
  while ((*str >= '0') && (*str <= '9'))
  {
      result = (result * 10) + ((*str) - '0');
      str++;
  }
  return (result * puiss);
}

Puoi anche renderlo ricorsivo che può essere vecchio in 3 righe =)


Grazie mille .. Ma potresti dirmi come funziona il codice qui sotto? code((* str) - '0')code
user618677

un personaggio ha un valore ASCII. Se non sei un tipo Linux, man ascii nella shell o, in caso contrario, vai a: table-ascii.com . Vedrai che il carattere '0' = 68 (penso) per un int. Quindi per ottenere il numero di '9' (è '0' + 9) in modo da ottenere 9 = '9' - '0'. L'hai capito?
jDourlens,

1
1) Il codice consente "----1" 2) Ha un comportamento indefinito con intoverflow quando il risultato dovrebbe essere INT_MIN. Consideramy_getnbr("-2147483648")
chux - Ripristina Monica

Grazie per la precisione, è stato solo per mostrare un piccolo esempio. Come si dice per divertimento e apprendimento. Dovresti assolutamente usare la lib standart per questo tipo di attività. Più veloce e più sicuro!
jDourlens,

2

Volevo solo condividere una soluzione per lungo tempo senza firma.

unsigned long ToUInt(char* str)
{
    unsigned long mult = 1;
    unsigned long re = 0;
    int len = strlen(str);
    for(int i = len -1 ; i >= 0 ; i--)
    {
        re = re + ((int)str[i] -48)*mult;
        mult = mult*10;
    }
    return re;
}

1
Non gestisce l'overflow. Inoltre, il parametro dovrebbe essere const char *.
Roland Illig,

2
Inoltre, cosa 48significa? Stai assumendo che sia il valore di '0'dove verrà eseguito il codice? Per favore, non infliggere ipotesi così vaste al mondo!
Toby Speight,

@TobySpeight Sì, suppongo che 48 rappresenti '0' nella tabella ASCII.
Jacob,

3
Non tutto il mondo è ASCII, basta usare '0'come dovresti.
Toby Speight,

si consiglia invece di utilizzare la funzione strtoul .
orologio rapido

1
int atoi(const char* str){
    int num = 0;
    int i = 0;
    bool isNegetive = false;
    if(str[i] == '-'){
        isNegetive = true;
        i++;
    }
    while (str[i] && (str[i] >= '0' && str[i] <= '9')){
        num = num * 10 + (str[i] - '0');
        i++;
    }
    if(isNegetive) num = -1 * num;
    return num;
}

-1

Puoi sempre creare il tuo!

#include <stdio.h>
#include <string.h>
#include <math.h>

int my_atoi(const char* snum)
{
    int idx, strIdx = 0, accum = 0, numIsNeg = 0;
    const unsigned int NUMLEN = (int)strlen(snum);

    /* Check if negative number and flag it. */
    if(snum[0] == 0x2d)
        numIsNeg = 1;

    for(idx = NUMLEN - 1; idx >= 0; idx--)
    {
        /* Only process numbers from 0 through 9. */
        if(snum[strIdx] >= 0x30 && snum[strIdx] <= 0x39)
            accum += (snum[strIdx] - 0x30) * pow(10, idx);

        strIdx++;
    }

    /* Check flag to see if originally passed -ve number and convert result if so. */
    if(!numIsNeg)
        return accum;
    else
        return accum * -1;
}

int main()
{
    /* Tests... */
    printf("Returned number is: %d\n", my_atoi("34574"));
    printf("Returned number is: %d\n", my_atoi("-23"));

    return 0;
}

Questo farà ciò che vuoi senza ingombro.


2
Ma perché? Questo non verifica la presenza di overflow e ignora semplicemente i valori di immondizia. Non c'è motivo di non usare la strto...famiglia di funzioni. Sono portatili e significativamente migliori.
Chad,

1
Strano da usare 0x2d, 0x30invece di '-', '0'. Non consente il '+'segno. Perché (int)partecipare (int)strlen(snum)? UB se l'ingresso è "". UB quando il risultato è INT_MINdovuto allo intstraripamento diaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Ripristina Monica

@chux - Questo codice è un codice dimostrativo. Ci sono facili soluzioni a quelli che hai descritto come potenziali problemi.
ButchDean,

2
@ButchDean Quello che descrivi come "codice dimostrativo" verrà utilizzato da altri che non hanno idea di tutti i dettagli. Solo il punteggio negativo e i commenti su questa risposta li proteggono ora. Secondo me, il "codice dimostrativo" deve avere una qualità molto più elevata.
Roland Illig,

@RolandIllig Piuttosto che essere tutto critico, non sarebbe più utile per gli altri mettere davvero la tua soluzione?
ButchDean,

-1

Questa funzione ti aiuterà

int strtoint_n(char* str, int n)
{
    int sign = 1;
    int place = 1;
    int ret = 0;

    int i;
    for (i = n-1; i >= 0; i--, place *= 10)
    {
        int c = str[i];
        switch (c)
        {
            case '-':
                if (i == 0) sign = -1;
                else return -1;
                break;
            default:
                if (c >= '0' && c <= '9')   ret += (c - '0') * place;
                else return -1;
        }
    }

    return sign * ret;
}

int strtoint(char* str)
{
    char* temp = str;
    int n = 0;
    while (*temp != '\0')
    {
        n++;
        temp++;
    }
    return strtoint_n(str, n);
}

Rif: http://amscata.blogspot.com/2013/09/strnumstr-version-2.html


1
Perché farlo però? Uno dei maggiori problemi con atoie gli amici è che se c'è troppo pieno, è un comportamento indefinito. La tua funzione non controlla questo. strtole gli amici lo fanno.
Chad,

1
Sì. Poiché C non è Python, spero che le persone che usano il linguaggio C siano consapevoli di questo tipo di errori di overflow. Ogni cosa ha i suoi limiti.
Amith Chinthaka,

-1

Ok, ho avuto lo stesso problema, ho trovato questa soluzione, ha funzionato per me al meglio, ho provato atoi () ma non ha funzionato bene per me, quindi ecco la mia soluzione:

void splitInput(int arr[], int sizeArr, char num[])
{
    for(int i = 0; i < sizeArr; i++)
        // We are subtracting 48 because the numbers in ASCII starts at 48.
        arr[i] = (int)num[i] - 48;
}

-1
//I think this way we could go :
int my_atoi(const char* snum)
{
 int nInt(0);
 int index(0);
 while(snum[index])
 {
    if(!nInt)
        nInt= ( (int) snum[index]) - 48;
    else
    {
        nInt = (nInt *= 10) + ((int) snum[index] - 48);
    }
    index++;
 }
 return(nInt);
}

int main()
{
    printf("Returned number is: %d\n", my_atoi("676987"));
    return 0;
}

Il codice non viene compilato in C. Perché nInt = (nInt *= 10) + ((int) snum[index] - 48);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)non necessario.
chux - Ripristina Monica

-3

In C ++, è possibile utilizzare una tale funzione:

template <typename T>
T to(const std::string & s)
{
    std::istringstream stm(s);
    T result;
    stm >> result;

    if(stm.tellg() != s.size())
        throw error;

    return result;
}

Questo può aiutarti a convertire qualsiasi stringa in qualsiasi tipo come float, int, double ...


1
Esiste già una domanda simile relativa al C ++ , in cui vengono spiegati i problemi con questo approccio.
Ben Voigt,

-6

Sì, è possibile memorizzare direttamente l'intero:

int num = 45;

Se devi analizzare una stringa atoio strolstai per vincere il concorso "quantità più breve di codice".


Se vuoi farlo in sicurezza, in strtol()realtà richiede una discreta quantità di codice. Si può tornare LONG_MINo LONG_MAX sia se questo è il valore reale convertito o se c'è un underflow o troppo pieno, e può restituire 0 o se questo è il valore reale o se non ci fosse il numero da convertire. È necessario impostare errno = 0prima della chiamata e controllare il endptr.
Keith Thompson,

Le soluzioni fornite per l'analisi non sono soluzioni praticabili.
BananaAcid
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.