Jak przekonwertować ciąg na liczbę całkowitą w C?

260

Próbuję dowiedzieć się, czy istnieje alternatywny sposób konwersji ciągu na liczbę całkowitą w C.

Regularnie wzoruję następujące elementy w moim kodzie.

char s[] = "45";

int num = atoi(s);

Czy jest więc lepszy czy inny sposób?

użytkownik618677
źródło
21
Twoje tagi i tytuł mówią, że potrzebujesz rozwiązania w C, ale twoje pytanie brzmi C lub C ++. Który chcesz?
In silico
1
@Yann, przepraszam za zamieszanie. Wolę C.
user618677,
1
Działa, ale nie jest to zalecany sposób, ponieważ nie ma sposobu na radzenie sobie z błędami. Nigdy nie używaj tego w kodzie produkcyjnym, chyba że możesz ufać wprowadzeniu w 100%.
Uwe Geuder,
1
Zdefiniuj „lepiej” i jasno określ, dlaczego potrzebujesz innego sposobu.
Markiz Lorne
3
@EJP Aby się poprawić.
user618677,

Odpowiedzi:

185

Jest strtollepszy IMO. Również polubiłem strtonum, więc użyj go, jeśli go masz (ale pamiętaj, że nie jest przenośny):

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

EDYTOWAĆ

Możesz być także zainteresowany strtoumaxistrtoimax które są standardowymi funkcjami w C99. Na przykład możesz powiedzieć:

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

W każdym razie trzymaj się z dala od atoi:

Call atoi (str) jest równoważny z:

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

z wyjątkiem tego, że obsługa błędów może się różnić. Jeśli wartości nie można przedstawić, zachowanie jest niezdefiniowane .

cnicutar
źródło
do czego muszę dołączyć strtonum?
Wciąż
@ trideceth12 W systemach, w których jest dostępny, należy go zadeklarować w #<stdlib.h>. Możesz jednak użyć standardowej strtoumaxalternatywy.
cnicutar
4
Ta odpowiedź nie wydaje się krótsza niż pierwszy kod pytającego.
Azurespot
11
@NoniA. Zwięzłość jest zawsze dobra, ale nie kosztem poprawności.
cnicutar
6
Nie tyle źle, co niebezpieczne. atoi () działa, jeśli dane wejściowe są prawidłowe. Ale co jeśli zrobisz atoi („cat”)? Funkcja strtol () ma zdefiniowane zachowanie, jeśli wartość nie może być reprezentowana jako długa, natomiast atoi () nie.
Daniel B.
27

Solidne strtolrozwiązanie oparte na C89

Z:

  • brak niezdefiniowanego zachowania (jakie można by było z atoirodziną)
  • bardziej rygorystyczna definicja liczby całkowitej niż strtol(np. brak wiodących białych znaków lub końcowych znaków śmieci)
  • klasyfikacja przypadku błędu (np. w celu przekazania użytkownikom przydatnych komunikatów o błędach)
  • „testsuite”
#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 w górę .

Na podstawie: https://stackoverflow.com/a/6154614/895245

Ciro Santilli
źródło
3
Niezły solidny str2int(). Pedantyczny: użycie isspace((unsigned char) s[0]).
chux - Przywróć Monikę
@chux dzięki! Czy możesz wyjaśnić nieco więcej, dlaczego (unsigned char)obsada może mieć znaczenie?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Kompilator IAR C ostrzega o tym l > INT_MAXi l < INT_MINjest bezcelowym porównywaniem liczb całkowitych, ponieważ oba wyniki są zawsze fałszywe. Co się stanie, jeśli zmienię je l >= INT_MAXi l <= INT_MINskasuję ostrzeżenia? W ARM C long i int są 32-bitowymi podstawowymi typami danych w ARM C i C ++
ecle
Zmiana kodu @ecle na l >= INT_MAXniepoprawną funkcjonalność: Przykład powrotu STR2INT_OVERFLOWz wejściem "32767"i 16-bit int. Użyj kompilacji warunkowej. Przykład .
chux - Przywróć Monikę
if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;byłoby lepiej, if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}aby umożliwić wywołanie kodu do używania errnona intout-of-zakresu. To samo dotyczy if (l < INT_MIN....
chux - Przywróć Monikę
24

Nie używaj funkcji z ato...grupy. Są zepsute i praktycznie bezużyteczne. Użycie byłoby lepszym rozwiązaniem sscanf, choć nie jest ono również idealne.

Aby przekonwertować ciąg na liczbę całkowitą, strto...należy użyć funkcji z grupy. W twoim konkretnym przypadku będzie to strtolfunkcja.

Mrówka
źródło
7
sscanffaktycznie ma niezdefiniowane zachowanie, jeśli próbuje przekonwertować liczbę spoza zakresu swojego typu (na przykład sscanf("999999999999999999999", "%d", &n)).
Keith Thompson,
1
@Keith Thompson: Właśnie o to mi chodzi. atoinie zapewnia żadnych istotnych informacji zwrotnych na temat sukcesu / niepowodzenia i ma niezdefiniowane zachowanie w przypadku przepełnienia. sscanfzapewnia informacje zwrotne o sukcesach / niepowodzeniach (wartość zwracana, co czyni ją „umiarkowanie lepszą”), ale nadal ma niezdefiniowane zachowanie w przypadku przepełnienia. Tylko strtolrealne rozwiązanie.
AnT
1
Zgoda; Chciałem tylko podkreślić potencjalnie śmiertelny problem sscanf. (Chociaż przyznaję, że czasami używam atoi, zwykle w przypadku programów, które nie spodziewają się przeżyć dłużej niż 10 minut przed usunięciem źródła).
Keith Thompson
5

Możesz dla zabawy napisać trochę atoi ():

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

Możesz także ustawić go jako rekurencyjny, który może być stary w 3 liniach =)

jDourlens
źródło
Dziękuję bardzo .. Ale czy możesz mi powiedzieć, jak działa poniższy kod? code((* str) - „0”)code
user618677
postać ma wartość ascii. Jeśli nie masz Linuksa, wpisz: man ascii w powłoce lub jeśli nie, przejdź do: table-ascii.com . Zobaczysz, że znak „0” = 68 (myślę) dla int. Aby uzyskać liczbę „9” (to „0” + 9), otrzymujesz 9 = „9” - „0”. Czaisz?
jDourlens,
1
1) Kod pozwala "----1" 2) Ma niezdefiniowane zachowanie z intprzepełnieniem, kiedy wynik powinien być INT_MIN. Zastanów sięmy_getnbr("-2147483648")
chux - Przywróć Monikę
Dzięki za precyzję, to tylko za pokazanie małego przykładu. Jak powiedziano, dla zabawy i nauki. Zdecydowanie powinieneś używać standardowej biblioteki lib do tego rodzaju zadań. Szybciej i bezpieczniej!
jDourlens
2

Chciałem tylko udostępnić rozwiązanie dla niepodpisanego na długo.

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;
}
Jakub
źródło
1
Nie obsługuje przepełnienia. Parametr powinien również wynosić const char *.
Roland Illig,
2
Plus, co to 48znaczy? Czy zakładasz, że jest to wartość miejsca, w '0'którym kod będzie działał? Proszę nie narzucać światu tak szerokich założeń!
Toby Speight
@TobySpeight Tak Zakładam, że 48 reprezentuje „0” w tabeli ascii.
Jacob
3
Nie cały świat jest ASCII - używaj go '0'tak, jak powinieneś.
Toby Speight,
zamiast tego zaleca się użycie funkcji strtoul .
szybki zegar
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;
}
Biswajit Karmakar
źródło
-1

Zawsze możesz rzucić własne!

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

To zrobi, co chcesz bez bałaganu.

ButchDean
źródło
2
Ale dlaczego? To nie sprawdza przepełnienia i po prostu ignoruje wartości śmieci. Nie ma powodu, aby nie korzystać z strto...rodziny funkcji. Są przenośne i znacznie lepsze.
czad
1
Dziwne w użyciu 0x2d, 0x30zamiast '-', '0'. Nie pozwala na '+'znak. Po co (int)rzucać (int)strlen(snum)? UB, jeśli wejście jest "". UB, gdy wynik jest INT_MINspowodowany intprzepełnieniemaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Przywróć Monikę
@chux - Ten kod to kod demonstracyjny. Istnieją proste poprawki do tego, co opisałeś jako potencjalne problemy.
ButchDean
2
@ButchDean To, co określisz jako „kod demonstracyjny”, będzie używane przez innych, którzy nie mają pojęcia o wszystkich szczegółach. Teraz chronią je tylko wynik ujemny i komentarze do tej odpowiedzi. Moim zdaniem „kod demonstracyjny” musi mieć znacznie wyższą jakość.
Roland Illig,
@RolandIllig Zamiast być krytycznym, czy nie byłoby bardziej pomocne dla innych, aby rzeczywiście stworzyć własne rozwiązanie?
ButchDean
-1

Ta funkcja ci pomoże

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

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

Amith Chinthaka
źródło
1
Dlaczego to robisz? Jednym z największych problemów atoii przyjaciół jest to, że jeśli występuje przepełnienie, jest to niezdefiniowane zachowanie. Twoja funkcja tego nie sprawdza. strtoli przyjaciele tak.
czad
1
Tak. Ponieważ C nie jest Pythonem, mam nadzieję, że ludzie używający języka C są świadomi tego rodzaju błędów przepełnienia. Wszystko ma swoje granice.
Amith Chinthaka,
-1

Ok, miałem ten sam problem. Wymyśliłem to rozwiązanie, działało dla mnie najlepiej, próbowałem atoi (), ale nie działało dobrze dla mnie, więc oto moje rozwiązanie:

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;
}
Khaled Mohammad
źródło
-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;
}
Aditya Kumar
źródło
Kod nie kompiluje się w C. Dlaczego nInt = (nInt *= 10) + ((int) snum[index] - 48);vs. nInt = nInt*10 + snum[index] - '0'; if(!nInt)nie jest potrzebny.
chux - Przywróć Monikę
-3

W C ++ możesz użyć takiej funkcji:

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

Może to pomóc w konwersji dowolnego łańcucha na dowolny typ, taki jak float, int, double ...

neodelphi
źródło
1
Istnieje już podobne pytanie dotyczące C ++ , w którym wyjaśniono problemy związane z tym podejściem.
Ben Voigt,
-6

Tak, możesz zapisać liczbę całkowitą bezpośrednio:

int num = 45;

Jeśli musisz przeanalizować ciąg znaków atoilub strolwygrywa konkurs „Najkrótsza ilość kodu”.

Yann Ramin
źródło
Jeśli chcesz to zrobić bezpiecznie, strtol()faktycznie wymaga sporej ilości kodu. Może on powrócić LONG_MINalbo LONG_MAX też , jeśli to wartość rzeczywista przerobiony lub nie występuje niedomiar lub nadmiar, i może zwrócić 0 albo jeśli jest to wartość rzeczywistą lub jeśli nie ma numer konwersji. Musisz ustawić errno = 0przed połączeniem i sprawdzić endptr.
Keith Thompson,
Rozwiązania podane do analizy, nie są realnymi rozwiązaniami.
BananaAcid