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


260

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

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

char s[] = "45";

int num = atoi(s);

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


21
Ваши теги и заголовок говорят, что вы хотите найти решение на C, но ваш вопрос говорит на C или C ++. Какой вы хотите?
In silico

1
@Yann, прости за эту путаницу. Я предпочитаю C.
user618677

1
Это работает, но это не рекомендуемый способ, потому что нет способа обрабатывать ошибки. Никогда не используйте это в рабочем коде, если вы не можете доверять вводу 100%.
Уве Geuder

1
Определите «лучше» и четко укажите, почему вам нужен другой способ.
Маркиз Лорн

3
@EJP Просто чтобы улучшить себя.
user618677

Ответы:


185

Есть strtolчто лучше ИМО. Также я полюбил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)

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


что мне нужно включить для strtonum? Я продолжаю получать неявное объявление о предупреждении
jsj

@ trideceth12 В системах, где он доступен, он должен быть объявлен в #<stdlib.h>. Тем не менее, вы можете использовать стандартную strtoumaxальтернативу.
cnicutar

4
Этот ответ не кажется короче, чем первый код спрашивающего.
Azurespot

11
@NoniA. Краткость всегда хороша, но не за счет правильности.
cnicutar

6
Не столько неправильно, сколько небезопасно. atoi () работает, если ввод действителен. Но что, если вы делаете atoi ("кошка")? strtol () имеет определенное поведение, если значение не может быть представлено как long, atoi () - нет.
Даниэль Б.

27

Надежное strtolрешение на основе C89

С участием:

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


3
Хороший крепкий str2int(). Педантичный: использовать isspace((unsigned char) s[0]).
chux - Восстановить Монику

@ chux спасибо! Можете ли вы объяснить немного больше, почему (unsigned char)актеры могут иметь значение?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Компилятор IAR C предупреждает об этом l > INT_MAXи l < INT_MINявляется бессмысленным целочисленным сравнением, поскольку любой результат всегда ложен. Что произойдет, если я изменю их l >= INT_MAXи l <= INT_MINуберу предупреждения? В ARM C long и int - это 32-разрядные подписанные базовые типы данных в ARM C и C ++
например,

@ecle, изменяющий код для получения l >= INT_MAXнеправильной функциональности: пример, возвращающийся STR2INT_OVERFLOWс вводом "32767"и 16-битным int. Используйте условную компиляцию. Пример .
chux - Восстановить Монику

if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;было бы лучше, if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}чтобы вызывающий код , чтобы использовать errnoна intвне диапазона. То же самое для if (l < INT_MIN....
chux - Восстановить Монику

24

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

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


7
sscanfна самом деле имеет неопределенное поведение, если он пытается преобразовать число вне диапазона его типа (например, sscanf("999999999999999999999", "%d", &n)).
Кит Томпсон

1
@ Кит Томпсон: Это именно то, что я имею в виду. atoiне обеспечивает значимой обратной связи об успехе / ошибке и имеет неопределенное поведение при переполнении. sscanfобеспечивает обратную связь об успехе / неудаче (возвращаемое значение, что делает его «умеренно лучше»), но при переполнении все еще имеет неопределенное поведение Только strtolжизнеспособное решение.
AnT

1
Согласовано; Я просто хотел подчеркнуть потенциально фатальную проблему с sscanf. (Хотя, признаюсь, я иногда использую atoi, обычно для программ, которые, как я ожидаю, не выживут более 10 минут, прежде чем я удалю источник.)
Кит Томпсон,

5

Вы можете написать немного 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 строки =)


Большое спасибо .. Но не могли бы вы сказать мне, как работает приведенный ниже код? code((* str) - '0')code
user618677

персонаж имеет значение ascii. Если вы используете uner linux, введите в командной строке man ascii или перейдите по адресу : table-ascii.com . Вы увидите, что символ '0' = 68 (я думаю) для int. Таким образом, чтобы получить число «9» (это «0» + 9), вы получите 9 = «9» - «0». Ты понял?
jDourlens

1
1) Код позволяет "----1" 2) Имеет неопределенное поведение с intпереполнением, когда результат должен быть INT_MIN. Рассмотримmy_getnbr("-2147483648")
Chux - Восстановить Монику

Спасибо за точность, это было только для того, чтобы показать маленький пример. Как говорится, для развлечения и учебы. Вы должны обязательно использовать стандартную библиотеку lib для таких задач. Быстрее и безопаснее!
JDourlens

2

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

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 *.
Роланд Иллиг

2
Плюс, что это 48значит? Вы предполагаете, что это значение того, '0'где будет выполняться код? Пожалуйста, не навязывайте миру такие широкие предположения!
Тоби Спейт

@TobySpeight Да, я предполагаю, что 48 представляют «0» в таблице ascii.
Джейкоб

3
Не весь мир является ASCII - просто используйте, '0'как вы должны.
Тоби Спейт

вместо этого рекомендуется использовать функцию strtoul .
быстрый час

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

Вы всегда можете свернуть свое собственное!

#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);
chux - Восстановить Монику

@chux - этот код является демонстрационным. Есть легкие исправления того, что вы назвали потенциальными проблемами.
ButchDean

2
@ButchDean То, что вы описываете как «демонстрационный код», будет использоваться другими, которые не имеют ни малейшего представления о всех деталях. Только отрицательный балл и комментарии к этому ответу защищают их сейчас. На мой взгляд, «демонстрационный код» должен иметь гораздо более высокое качество.
Роланд Иллиг

@RolandIllig Вместо того, чтобы быть критически настроенными, разве не будет более полезным для других предложить собственное решение?
ButchDean

-1

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

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, знают об этих ошибках переполнения. У всего есть свои пределы.
Амит Чинтхака,

-1

Хорошо, у меня была та же проблема. Я нашел это решение. Это сработало для меня лучше всего. Я попробовал 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;
}

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

Код не компилируется в C. Почему nInt = (nInt *= 10) + ((int) snum[index] - 48);против nInt = nInt*10 + snum[index] - '0'; if(!nInt)не нужно.
chux - Восстановить Монику

-3

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

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


1
Уже есть аналогичный вопрос, касающийся C ++ , где объясняются проблемы с этим подходом.
Бен Фойгт

-6

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

int num = 45;

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


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

Решения, данные для разбора, не являются жизнеспособными решениями.
BananaAcid
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.