Làm thế nào để chuyển đổi một chuỗi thành số nguyên trong C?


260

Tôi đang cố gắng tìm hiểu xem có cách nào khác để chuyển đổi chuỗi thành số nguyên trong C.

Tôi thường xuyên mô hình hóa sau đây trong mã của tôi.

char s[] = "45";

int num = atoi(s);

Vì vậy, có một cách tốt hơn hay cách khác?


21
Thẻ và tiêu đề của bạn nói rằng bạn muốn có giải pháp trong C, nhưng câu hỏi của bạn cho biết C hoặc C ++. Bạn muốn cái nào
Trong silico

1
@Yann, Xin lỗi vì sự nhầm lẫn đó. Tôi sẽ thích C.
dùng618677

1
Nó hoạt động, nhưng đó không phải là cách được khuyến nghị, vì không có cách nào để xử lý lỗi. Không bao giờ sử dụng điều này trong mã sản xuất trừ khi bạn có thể tin tưởng 100% đầu vào.
Uwe Geuder 5/11/2015

1
Xác định 'tốt hơn' và nêu rõ lý do tại sao bạn cần một cách khác.
Hầu tước Lorne

3
@EJP Chỉ để cải thiện bản thân.
dùng618677

Câu trả lời:


185

strtol IMO tốt hơn. Ngoài ra tôi cũng rất thích strtonum, vì vậy hãy sử dụng nó nếu bạn có nó (nhưng hãy nhớ rằng nó không di động):

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

BIÊN TẬP

Bạn cũng có thể quan tâm strtoumaxstrtoimax đó là các chức năng tiêu chuẩn trong C99. Ví dụ bạn có thể nói:

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

Dù sao, tránh xa atoi :

Cuộc gọi atoi (str) sẽ tương đương với:

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

ngoại trừ việc xử lý các lỗi có thể khác nhau. Nếu giá trị không thể được biểu diễn, hành vi không được xác định .


Tôi cần bao gồm những strtonumgì? Tôi tiếp tục nhận được một cảnh báo tuyên bố ngầm
jsj

@ trideceth12 Trên các hệ thống có sẵn nó nên được khai báo #<stdlib.h>. Tuy nhiên, bạn có thể sử dụng strtoumaxthay thế tiêu chuẩn .
cnicutar

4
Câu trả lời này dường như không ngắn hơn mã đầu tiên của người hỏi.
Azurespot

11
@NoniA. Sự đồng tình luôn luôn tốt, nhưng không phải trả giá cho sự đúng đắn.
cnicutar

6
Không sai nhiều đến mức không an toàn. atoi () hoạt động nếu đầu vào hợp lệ. Nhưng nếu bạn làm atoi ("con mèo") thì sao? strtol () đã xác định hành vi nếu giá trị không thể được biểu diễn dưới dạng dài, atoi () thì không.
Daniel B.

27

strtolGiải pháp mạnh mẽ dựa trên C89

Với:

  • không có hành vi không xác định (như có thể có với atoigia đình)
  • một định nghĩa chặt chẽ hơn về số nguyên hơn strtol(ví dụ: không có khoảng trắng hàng đầu cũng như các ký tự rác)
  • phân loại trường hợp lỗi (ví dụ: để đưa ra thông báo lỗi hữu ích cho người dùng)
  • một "lời khuyên"
#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 ngược dòng .

Dựa trên: https://stackoverflow.com/a/6154614/895245


3
Đẹp mạnh mẽ str2int(). Pedantic: sử dụng isspace((unsigned char) s[0]).
chux - Phục hồi Monica

@chux cảm ơn! Bạn có thể giải thích thêm một chút tại sao (unsigned char)dàn diễn viên có thể tạo ra sự khác biệt?
Ciro Santilli 郝海东 冠状 病 事件

Trình biên dịch IAR C cảnh báo rằng l > INT_MAXl < INT_MINlà so sánh số nguyên vô nghĩa vì một trong hai kết quả luôn luôn là sai. Điều gì xảy ra nếu tôi thay đổi chúng l >= INT_MAXl <= INT_MINđể xóa các cảnh báo? Trên ARM C, dàiint là các kiểu dữ liệu Cơ bản có
ecle

@ecle thay đổi mã để l >= INT_MAXphát sinh chức năng không chính xác: Ví dụ trả về STR2INT_OVERFLOWvới đầu vào "32767"và 16 bit int. Sử dụng một biên dịch có điều kiện. Ví dụ .
chux - Phục hồi Monica

if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) return STR2INT_OVERFLOW;Sẽ tốt hơn là if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) { errno = ERANGE; return STR2INT_OVERFLOW;}để cho phép gọi mã để sử dụng errnotrên intout-of-range. Tương tự cho if (l < INT_MIN....
chux - Phục hồi Monica

24

Đừng sử dụng các chức năng từ ato...nhóm. Chúng bị hỏng và hầu như vô dụng. Một giải pháp tốt hơn vừa phải sẽ được sử dụng sscanf, mặc dù nó cũng không hoàn hảo.

Để chuyển đổi chuỗi thành số nguyên, các hàm từ strto...nhóm nên được sử dụng. Trong trường hợp cụ thể của bạn, nó sẽ là strtolchức năng.


7
sscanfthực sự có hành vi không xác định nếu nó cố gắng chuyển đổi một số bên ngoài phạm vi của loại của nó (ví dụ sscanf("999999999999999999999", "%d", &n):).
Keith Thompson

1
@Keith Thompson: Đó chính xác là điều tôi muốn nói. atoikhông cung cấp phản hồi thành công / thất bại có ý nghĩa và có hành vi không xác định khi tràn. sscanfcung cấp phản hồi thành công / thất bại của các loại (giá trị trả về, đó là điều làm cho nó "tốt hơn vừa phải"), nhưng vẫn có hành vi không xác định khi tràn. Chỉ strtollà một giải pháp khả thi.
AnT

1
Đã đồng ý; Tôi chỉ muốn nhấn mạnh vấn đề có thể gây tử vong với sscanf. (Mặc dù tôi thú nhận đôi khi tôi sử dụng atoi, thường là cho các chương trình mà tôi không mong đợi tồn tại hơn 10 phút trước khi tôi xóa nguồn.)
Keith Thompson

5

Bạn có thể mã một ít atoi () cho vui:

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

Bạn cũng có thể làm cho nó đệ quy có thể cũ trong 3 dòng =)


Cảm ơn rất nhiều .. Nhưng bạn có thể cho tôi biết đoạn mã dưới đây hoạt động như thế nào không? code((* str) - '0')code
dùng618677

một nhân vật có giá trị ascii. Nếu bạn chưa mở loại linux: man ascii trong shell hoặc nếu không truy cập: table-ascii.com . Bạn sẽ thấy ký tự '0' = 68 (tôi nghĩ) cho một số nguyên. Vì vậy, để có được số '9' (đó là '0' + 9), do đó bạn nhận được 9 = '9' - '0'. Bạn nhận được nó?
jDourlens

1
1) Mã cho phép "----1" 2) Có hành vi không xác định với inttràn khi kết quả phải INT_MIN. Hãy xem xétmy_getnbr("-2147483648")
chux - Phục hồi lại

Cảm ơn cho sự chính xác, nó chỉ là để hiển thị một ví dụ nhỏ. Như đã nói nó cho vui và học tập. Bạn chắc chắn nên sử dụng lib standart cho loại nhiệm vụ này. Nhanh hơn và an toàn hơn!
jDourlens

2

Chỉ muốn chia sẻ một giải pháp cho lâu không dấu.

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
Không xử lý tràn. Ngoài ra, các tham số nên được const char *.
Roland Illig

2
Ngoài ra, điều đó 48có nghĩa là gì? Bạn có cho rằng đó là giá trị của '0'nơi mã sẽ chạy? Xin đừng gây ra những giả định rộng lớn như vậy trên thế giới!
Toby Speight

@TobySpeight Có Tôi giả sử 48 đại diện cho '0' trong bảng ascii.
Jacob

3
Không phải tất cả thế giới là ASCII - chỉ cần sử dụng '0'như bạn nên.
Toby Speight

thay vào đó, nên sử dụng hàm strtoul .
quickclock

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

Bạn luôn có thể cuộn của riêng bạn!

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

Điều này sẽ làm những gì bạn muốn mà không lộn xộn.


2
Nhưng tại sao? Điều này không kiểm tra tràn và chỉ đơn giản là bỏ qua các giá trị rác. Không có lý do gì để không sử dụng strto...họ chức năng. Chúng là hàng xách tay và tốt hơn đáng kể.
chad

1
Lạ để sử dụng 0x2d, 0x30thay vì '-', '0'. Không cho phép '+'ký. Tại sao (int)đúc trong (int)strlen(snum)? UB nếu đầu vào là "". UB khi kết quả là INT_MINdo inttràn vớiaccum += (snum[strIdx] - 0x30) * pow(10, idx);
chux - Phục hồi Monica

@chux - Mã này là mã trình diễn. Có những sửa chữa dễ dàng cho những gì bạn mô tả là vấn đề tiềm năng.
ButchDean

2
@ButchDean Những gì bạn mô tả là "mã trình diễn" sẽ được sử dụng bởi những người khác không có đầu mối về tất cả các chi tiết. Chỉ có điểm số tiêu cực và các ý kiến ​​về câu trả lời này bảo vệ chúng bây giờ. Theo tôi, "mã trình diễn" phải có chất lượng cao hơn nhiều.
Roland Illig

@RolandIllig Thay vì tất cả quan trọng, sẽ không hữu ích hơn cho những người khác thực sự đưa ra giải pháp của riêng bạn?
ButchDean

-1

Chức năng này sẽ giúp bạn

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

Tham chiếu: http://amscata.blogspot.com/2013/09/strnumstr-version-2.html


1
Tại sao làm điều này mặc dù? Một trong những vấn đề lớn nhất với atoibạn bè là nếu có tràn, đó là hành vi không xác định. Chức năng của bạn không kiểm tra điều này. strtolvà bạn bè làm.
chad

1
Vâng Vì C không phải là Python, tôi hy vọng rằng những người sử dụng ngôn ngữ C sẽ biết về các lỗi tràn này. Mọi thứ đều có giới hạn của riêng nó.
Amith Chinthaka

-1

Ok, tôi đã có cùng một vấn đề. Tôi đã đưa ra giải pháp này. Nó hoạt động tốt nhất cho tôi. Tôi đã thử atoi () nhưng không hiệu quả với tôi. Đây là giải pháp của tôi:

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

Mã không biên dịch trong C. Tại sao nInt = (nInt *= 10) + ((int) snum[index] - 48);so với nInt = nInt*10 + snum[index] - '0'; if(!nInt)không cần thiết.
chux - Phục hồi lại

-3

Trong C ++, bạn có thể sử dụng một chức năng như vậy:

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

Điều này có thể giúp bạn chuyển đổi bất kỳ chuỗi nào thành bất kỳ loại nào như float, int, double ...


1
Đã có một câu hỏi tương tự bao gồm C ++ , trong đó các vấn đề với phương pháp này được giải thích.
Ben Voigt

-6

Có, bạn có thể lưu trữ số nguyên trực tiếp:

int num = 45;

Nếu bạn phải phân tích một chuỗi, atoihoặc strolsẽ giành chiến thắng trong cuộc thi "số lượng mã ngắn nhất".


Nếu bạn muốn làm điều đó một cách an toàn, strtol()thực sự đòi hỏi một số lượng mã hợp lý. Nó có thể trở lại LONG_MINhoặc LONG_MAX hoặc nếu đó là giá trị chuyển đổi thực tế hoặc nếu có một underflow hoặc tràn, và nó có thể trở về 0 hoặc nếu đó là giá trị thực tế hoặc nếu không có số để chuyển đổi. Bạn cần đặt errno = 0trước cuộc gọi và kiểm tra endptr.
Keith Thompson

Các giải pháp được đưa ra để phân tích, không có giải pháp khả thi.
BananaAcid
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.