Як перетворити рядок в ціле число в C?


260

Я намагаюся з'ясувати, чи існує альтернативний спосіб перетворення рядка в ціле число в C.

Я регулярно викладаю наступне у своєму коді.

char s[] = "45";

int num = atoi(s);

Отже, є кращий чи інший спосіб?


21
Ваші теги та заголовок говорять, що ви хочете вирішити C, але у вашому питанні написано C або C ++ Котрий саме ви хочете?
У силіко

1
@Yann, Вибачте за цю плутанину Я віддаю перевагу С.
user618677

1
Це працює, але це не рекомендований спосіб, оскільки немає можливості впоратися з помилками. Ніколи не використовуйте це у виробничому коді, якщо ви не можете довіряти вкладеним на 100%.
Uwe Geuder

1
Визначте «краще» та чітко сформулюйте, чому вам потрібен інший шлях.
Маркіз Лорн

3
@EJP Просто для вдосконалення себе.
user618677

Відповіді:


185

Є, strtolщо краще ІМО. Також мені сподобалося strtonum, тому використовуйте його, якщо у вас є (але пам’ятайте, що це не портативний):

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

EDIT

Можливо, вас також зацікавлять 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 () працює, якщо вхід правильний. Але що робити, якщо ви займаєтеся атоєм ("котом")? strtol () має певну поведінку, якщо значення не можна представити як довге, 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

@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
@Keith Thompson: Це саме те, що я маю на увазі. 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

Дякую за точність, це було лише для показу невеликого прикладу. Як сказано це для розваги та навчання. Ви, безумовно, повинні використовувати стандартні ліб для подібних завдань. Швидше і безпечніше!
jDourlens

2

Просто хотів поділитися рішенням для неподписаного довго.

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 *.
Roland Illig

2
Плюс, що це 48означає? Ви припускаєте, що це значення, '0'де буде працювати код? Будь ласка, не викладайте на світ таких широких припущень!
Toby Speight

@TobySpeight Так, я припускаю, що 48 представляють "0" в таблиці ascii.
Яків

3
Не весь світ ASCII - просто використовуйте '0'як слід.
Toby Speight

рекомендується замість цього використовувати функцію 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 Те, що ви описуєте як "демонстраційний код", використовуватиметься іншими, хто не має поняття про всі деталі. Лише негативна оцінка та коментарі до цієї відповіді захищають їх зараз. На мою думку, "демонстраційний код" повинен мати набагато більш високу якість.
Roland Illig

@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 ++ , де пояснюються проблеми з таким підходом.
Ben Voigt

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