วิธีการแปลงสตริงเป็นจำนวนเต็มใน C?


260

ฉันพยายามค้นหาว่ามีวิธีอื่นในการแปลงสตริงเป็นจำนวนเต็มใน C หรือไม่

ฉันทำรูปแบบต่อไปนี้เป็นประจำในรหัสของฉัน

char s[] = "45";

int num = atoi(s);

ดังนั้นจะมีวิธีที่ดีกว่าหรืออีกวิธีหนึ่งหรือไม่


21
แท็กและชื่อของคุณบอกว่าคุณต้องการโซลูชันใน C แต่คำถามของคุณบอกว่า C หรือ C ++ คุณต้องการแบบไหน
ใน silico

1
@Yann ขออภัยในความสับสนนั้น ฉันจะชอบค
user618677

1
ใช้งานได้ แต่ไม่ใช่วิธีที่แนะนำเนื่องจากไม่มีวิธีจัดการข้อผิดพลาด อย่าใช้สิ่งนี้ในรหัสการผลิตเว้นแต่คุณจะเชื่อถืออินพุตได้ 100%
Uwe Geuder

1
กำหนด 'ดีกว่า' และระบุอย่างชัดเจนว่าทำไมคุณถึงต้องการวิธีอื่น
มาร์ควิสแห่ง Lorne

3
@EJP เพียงเพื่อปรับปรุงตัวเอง
user618677

คำตอบ:


185

นอกจากนี้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)

ยกเว้นว่าการจัดการข้อผิดพลาดอาจแตกต่างกัน ถ้าค่าไม่สามารถแสดงพฤติกรรมจะไม่ได้กำหนด


ฉันต้องรวมstrtonumอะไรเพื่อ ฉันได้รับคำเตือนเรื่องการประกาศโดยปริยาย
jsj

@ trideceth12 #<stdlib.h>ในระบบที่มันใช้ได้ก็ควรจะประกาศใน อย่างไรก็ตามคุณสามารถใช้strtoumaxทางเลือกมาตรฐาน
cnicutar

4
คำตอบนี้ดูเหมือนจะสั้นกว่ารหัสแรกของผู้ถาม
Azurespot

11
@NoniA ความรัดกุมดีเสมอ แต่ไม่ใช่ความถูกต้อง
cnicutar

6
ไม่ผิดเท่าไรนัก atoi () ทำงานถ้าอินพุตถูกต้อง แต่ถ้าคุณทำอาโออิ ("แมว") ล่ะ? strtol () ได้กำหนดพฤติกรรมหากค่าไม่สามารถแสดงเป็นยาว atoi () ไม่ได้
Daniel B.

27

strtolโซลูชันพื้นฐานที่แข็งแกร่ง C89

ด้วย:

  • ไม่มีพฤติกรรมที่ไม่ได้กำหนด (อย่างที่ควรมีกับatoiครอบครัว)
  • คำจำกัดความที่เข้มงวดของจำนวนเต็มกว่า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/a/6154614/895245


3
แข็งแรงstr2int()ดี อวดความรู้: isspace((unsigned char) s[0])การใช้งาน
chux - Reinstate Monica

@ chux ขอบคุณ! คุณช่วยอธิบายอีกเล็กน้อยได้(unsigned char)ไหมว่าทำไมนักแสดงถึงสร้างความแตกต่าง?
Ciro Santilli 郝海东冠状病六四事件法轮功

คอมไพเลอร์ IAR C เตือนว่าl > INT_MAXและl < INT_MINเป็นการเปรียบเทียบจำนวนเต็มแบบไม่มีจุดหมายเนื่องจากผลลัพธ์ใด ๆ เป็นเท็จเสมอ จะเกิดอะไรขึ้นถ้าฉันเปลี่ยนพวกเขาเป็นl >= INT_MAXและl <= INT_MINเพื่อล้างคำเตือน? บนแขน C, ยาวและintเป็น 32 บิตลงนามชนิดข้อมูลพื้นฐานใน ARM C และ C ++
ecle

@ecle เปลี่ยนรหัสเพื่อl >= INT_MAXincurs ฟังก์ชันการทำงานที่ไม่ถูกต้อง: ตัวอย่างกลับมาSTR2INT_OVERFLOWด้วยการป้อนข้อมูล"32767"และ int16 ใช้การคอมไพล์แบบมีเงื่อนไข ตัวอย่าง
chux - Reinstate Monica

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 - Reinstate Monica

24

อย่าใช้ฟังก์ชั่นจากato...กลุ่ม สิ่งเหล่านี้แตกและไร้ประโยชน์อย่างแท้จริง ทางออกที่ดีกว่าในระดับปานกลางคือการใช้งานsscanfแม้ว่ามันจะไม่สมบูรณ์ก็ตาม

ในการแปลงสตริงเป็นจำนวนเต็มstrto...ควรใช้ฟังก์ชันจากกลุ่ม ในกรณีเฉพาะของคุณมันจะเป็นstrtolฟังก์ชั่น


7
sscanfที่จริงแล้วมีพฤติกรรมที่ไม่ได้กำหนดหากพยายามแปลงตัวเลขที่อยู่นอกช่วงของประเภท (ตัวอย่างเช่นsscanf("999999999999999999999", "%d", &n))
Keith Thompson

1
@ Keith Thompson: นั่นคือสิ่งที่ฉันหมายถึง atoiไม่ให้ข้อเสนอแนะความสำเร็จ / ล้มเหลวที่มีความหมายและมีพฤติกรรมที่ไม่ได้กำหนดเกี่ยวกับการล้น sscanfแสดงความคิดเห็นย้อนกลับที่ประสบความสำเร็จ / ล้มเหลว (ค่าส่งคืนซึ่งเป็นสิ่งที่ทำให้ "ดีขึ้นพอสมควร") แต่ก็ยังมีพฤติกรรมที่ไม่ได้กำหนดในการล้น เพียงstrtolเป็นโซลูชั่นที่ทำงานได้
AnT

1
ตกลงกัน; sscanfฉันแค่อยากจะเน้นปัญหาร้ายแรงอาจเกิดขึ้นกับ (แม้ว่าฉันจะยอมรับว่าบางครั้งฉันก็atoiมักใช้สำหรับโปรแกรมที่ฉันไม่คาดหวังว่าจะอยู่รอดนานกว่า 10 นาทีก่อนที่ฉันจะลบซอร์ส)
Keith Thompson

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 ถ้าคุณเป็นประเภทลินุกซ์อูเนอร์: คน 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 - Reinstate Monica

ขอบคุณสำหรับความแม่นยำมันเป็นเพียงการแสดงตัวอย่างเล็กน้อย ตามที่กล่าวไว้เพื่อความสนุกสนานและการเรียนรู้ คุณควรใช้ lib lib สำหรับงานประเภทนี้อย่างแน่นอน เร็วขึ้นและปลอดภัยยิ่งขึ้น!
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แทน
Rapidclock

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 - Reinstate Monica

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

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


1
ทำไมถึงทำเช่นนี้? หนึ่งในปัญหาที่ใหญ่ที่สุดกับatoiและเพื่อนคือถ้ามีมากเกินไปก็เป็นพฤติกรรมที่ไม่ได้กำหนด ฟังก์ชั่นของคุณไม่ได้ตรวจสอบสิ่งนี้ strtolและเพื่อนทำ
ชาด

1
ได้. เนื่องจาก C ไม่ใช่ Python ฉันหวังว่าผู้ที่ใช้ภาษา C จะทราบถึงข้อผิดพลาดล้นเหล่านี้ ทุกอย่างมีขีด จำกัด ของตัวเอง
Amith Chinthaka

-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 - Reinstate Monica

-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 อย่างใดอย่างหนึ่งถ้าว่าเป็นค่าที่แปลงที่เกิดขึ้นจริงหรือถ้ามีการ underflow หรือล้นและมันสามารถกลับ 0 ทั้งถ้านั่นคือค่าจริงหรือถ้ามีเป็นจำนวนที่จะแปลง คุณจะต้องตั้งก่อนที่จะเรียกและตรวจสอบerrno = 0 endptr
Keith Thompson

การแก้ปัญหาที่มอบให้กับการแยกวิเคราะห์ไม่มีโซลูชั่นที่ทำงานได้
BananaAcid
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.