Как преобразовать строку в целое число в C?

189

Я пытаюсь выяснить, есть ли альтернативный способ преобразования строки в integer в C.

Я регулярно рисую следующее в своем коде.

char s[] = "45";

int num = atoi(s);

Итак, есть ли лучший способ или другой способ?

  • 20
    Ваши теги и заголовок говорят, что вы хотите найти решение на C, но ваш вопрос говорит на C или C ++. Какой вы хотите?
  • 1
    @Yann, прости за эту путаницу. Я предпочитаю C.
Показать ещё 4 комментария
Теги:
string
atoi

11 ответов

142
Лучший ответ

Существует strtol, который лучше IMO. Также мне понравилось в strtonum, поэтому используйте его, если он у вас есть (но помните, что он не переносится):

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

ИЗМЕНИТЬ

Вас также может заинтересовать strtoumax и strtoimax, которые являются стандартными функциями на C99. Например, вы могли бы сказать:

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

Во всяком случае, держитесь подальше от atoi:

Вызов atoi (str) должен быть эквивалентен:

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

за исключением того, что обработка ошибок может отличаться. Если значение не может быть представленное поведение undefined.

  • 0
    что мне нужно включить для strtonum ? Я продолжаю получать неявное объявление о предупреждении
  • 0
    @ trideceth12 В системах, где это доступно, это должно быть объявлено в #<stdlib.h> . Однако вы можете использовать стандартную альтернативу strtoumax .
Показать ещё 5 комментариев
20

Надежное strtol C89 strtol -based

С:

  • нет неопределенного поведения (как это могло бы быть с семьей atoi)
  • более строгое определение целых чисел, чем strtol (например, отсутствие пробелов в strtol отсутствие strtol мусора)
  • классификация случая ошибки (например, чтобы дать пользователям полезные сообщения об ошибках)
  • "testuite"
#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 вверх по течению.

На основании: https://stackoverflow.com/questions/194465/how-to-parse-a-string-to-an-int-in-c

  • 3
    Хороший надежный str2int() . Pedantic: использовать isspace((unsigned char) s[0]) .
  • 0
    @ chux спасибо! Можете ли вы объяснить немного больше, почему приведение (unsigned char) может иметь значение?
Показать ещё 6 комментариев
19

Не используйте функции из группы ato.... Они сломаны и практически бесполезны. Менее хорошим решением было бы использовать sscanf, хотя он также не идеален.

Чтобы преобразовать строку в целое число, следует использовать функции из группы strto.... В вашем конкретном случае это будет функция strtol.

  • 7
    sscanf самом деле sscanf имеет неопределенное поведение, если пытается преобразовать число за пределы диапазона своего типа (например, sscanf("999999999999999999999", "%d", &n) ).
  • 1
    @ Кит Томпсон: Это именно то, что я имею в виду. atoi обеспечивает значимой обратной связи об успехе / неудаче и имеет неопределенное поведение при переполнении. sscanf обеспечивает обратную связь об успехе / неудаче (возвращаемое значение, которое делает его «умеренно лучше»), но все еще имеет неопределенное поведение при переполнении. Только strtol является жизнеспособным решением.
Показать ещё 1 комментарий
4

Вы можете закодировать немного 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);
}

Вы также можете сделать его рекурсивным, который может быть старым в 3 строках =)

  • 0
    Большое спасибо .. Но не могли бы вы сказать мне, как работает приведенный ниже код? code ((* str) - '0') code
  • 0
    персонаж имеет значение ascii. Если вы не используете Linux, наберите: man ascii в оболочке или, если нет, перейдите по адресу : table-ascii.com . Вы увидите, что символ '0' = 68 (я думаю) для int. Таким образом, чтобы получить число «9» (это «0» + 9), вы получите 9 = «9» - «0». Ты понял?
Показать ещё 2 комментария
2

Просто хотел совместно использовать решение для unsigned long.

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
    Не обрабатывает переполнение. Кроме того, параметр должен быть const char * .
  • 1
    Плюс, что это за 48 ? Вы предполагаете, что это значение '0' где будет выполняться код? Пожалуйста, не навязывайте миру такие широкие предположения!
Показать ещё 2 комментария
0
//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;
}
  • 0
    Код не компилируется в C. Почему nInt = (nInt *= 10) + ((int) snum[index] - 48); против nInt = nInt*10 + snum[index] - '0'; if(!nInt) не требуется.
0

Хорошо, у меня была та же проблема. Я придумал это решение. Он работал у меня лучше. Я пытался использовать atoi(), но не работал у меня. Вот мое решение:

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

Вы всегда можете катиться самостоятельно!

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

Это сделает то, что вы хотите, без беспорядка.

  • 2
    Но почему? Это не проверяет переполнение и просто игнорирует значения мусора. Нет причин не использовать семейство функций strto... Они портативны и значительно лучше.
  • 1
    Странно использовать 0x2d, 0x30 вместо '-', '0' . Не допускает знак '+' . Почему (int) приведен в (int)strlen(snum) ? UB, если ввод "" . UB, когда результатом является INT_MIN из-за переполнения int с accum += (snum[strIdx] - 0x30) * pow(10, idx);
Показать ещё 9 комментариев
0

Эта функция поможет вам

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

Ссылка: http://amscata.blogspot.com/2013/09/strnumstr-version-2.html

  • 1
    Почему это все же? Одна из самых больших проблем с atoi и друзьями состоит в том, что если есть переполнение, это неопределенное поведение. Ваша функция не проверяет это. strtol и друзья делают.
  • 1
    Ага. Поскольку C - это не Python, я надеюсь, что люди, использующие язык C, знают об этих ошибках переполнения. У всего есть свои пределы.
-2

В С++ вы можете использовать такую ​​функцию:

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

Это может помочь вам преобразовать любую строку в любой тип, такой как float, int, double...

-3

Да, вы можете сохранить целое число напрямую:

int num = 45;

Если вы должны проанализировать строку, atoi или strol собирается выиграть конкурс "Самый короткий код".

  • 0
    Если вы хотите сделать это безопасно, strtol() самом деле требует достаточного количества кода. Он может возвращать LONG_MIN или LONG_MAX либо, если это фактическое преобразованное значение, либо в случае недостаточного или переполнения, и он может возвращать 0, если это фактическое значение или если не было числа для преобразования. Вам нужно установить errno = 0 перед вызовом и проверить endptr .
  • 0
    Решения, данные для разбора, не являются жизнеспособными решениями.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню