簡體   English   中英

如何從C中的字符串解析整數序列?

[英]How to parse sequence of integers from string in C?

嗨,我對 C 很陌生,我遇到了一個問題,我對如何將字符串行解析為整數感到困惑。 到目前為止,我的方法只是將第一個字符串解析為整數。 所以如果我的輸入是10 20 30它只會取第一個字符串並將其解析為整數。 我正在尋找關於如何提出一個可以讀取所有行並使用getline()其全部解析為整數值的解決方案的想法。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *line = NULL;
    size_t len = 0; 
    int val =0;
    int sum = 0;

    while (getline(&line, &len, stdin) != EOF) {
    
        printf("Line input : %s\n", line);
        //printf("Test %d", val);

        //parse char into integer 
        val = atoi(line);

        printf("Parsed integer: %d\n", val);
    }
    free(line); 
    return 0;
}

正如我在評論中指出的那樣,最好使用strtol() (或strtoX()系列函數的其他成員之一)將字符串轉換為整數。 這是注意strtol()正確用法的代碼。

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *line = NULL;
    size_t len = 0;

    while (getline(&line, &len, stdin) != -1)
    {
        printf("Line input : [%s]\n", line);
        int val = atoi(line);
        printf("Parsed integer: %d\n", val);

        char *start = line;
        char *eon;
        long value;
        errno = 0;
        while ((value = strtol(start, &eon, 0)),
               eon != start &&
               !((errno == EINVAL && value == 0) ||
                 (errno == ERANGE && (value == LONG_MIN || value == LONG_MAX))))
        {
            printf("%ld\n", value);
            start = eon;
            errno = 0;
        }
        putchar('\n');
    }
    free(line);
    return 0;
}

問題中使用 POSIX getline()讀取行的代碼幾乎是正確的; 將指向空指針的指針傳遞給函數並將指針傳遞給 0 是合法的。但是,從技術上講, getline()返回-1而不是EOF ,盡管很少(如果有)系統有有區別。 盡管如此,標准 C 允許EOF為任何負值 - 不需要為-1

對於極端的挑剔者,盡管strtol()的 Linux 和 macOS 手冊頁聲明“返回 0 並將errnoEINVAL ”,當它無法轉換字符串時,C 標准並不要求為此errno 但是,當轉換失敗時, eon將被設置為start ——這是由標准保證的。 因此,有理由認為EINVAL的測試部分是多余的。

while循環使用逗號運算符調用strtol()以解決其副作用(分配給valueeon ),並忽略結果 - 忽略它是必要的,因為所有可能的返回值都是有效的。 條件的其他三行(逗號運算符的 RHS)評估轉換是否成功。 這避免了兩次對strtol()的調用。 這可能是 DRY(不要重復自己)編程的一個極端情況。

運行代碼的小樣本(程序名稱rn89 ):

$ rn89
   1  2    4  5       5  6
Line input : [   1  2    4  5       5  6
]
Parsed integer: 1
1
2
4
5
5
6

232443 432435423 12312 1232413r2  
Line input : [232443 432435423 12312 1232413r2
]
Parsed integer: 232443
232443
432435423
12312
1232413

324d
Line input : [324d
]
Parsed integer: 324
324

$

在正確性和全面性之間存在權衡。 因此我創建了兩個版本的程序:

  • 首先是廣泛的錯誤處理
  • 第二個很簡單 - 僅假設正面場景(輸入不包含錯誤)以保持全面。

可以在循環標准 C 函數strtol 中調用 <stdlib.h> 中的循環標准 C 函數strtol來解析包含在 C 字符串中的整數序列:

long int strtol (const char* str, char** endptr, int base);

解析 C 字符串 str 並將其內容解釋為指定基數的整數。 strtol 跳過空格,解釋整數並將指針 *endptr 設置為整數后面的第一個字符。

由於作者在他的代碼中有變量 sum,讓我們演示整數序列的解析作為這個序列的總和。 我從GNU 手冊 20.11.1 Parsing of Integers Code in manual 中采用函數 sum_ints_from_string() 假設正面場景 因此我將其更改為第一個版本。

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int
sum_ints_from_string (char *string)
{
    int sum = 0;

    while (1) {
        char *tail;
        int next;

        /* Skip whitespace by hand, to detect the end.  */
        while (string && isspace (*string)) string++;
        if (!string || *string == 0)
            break;

        /* There is more nonwhitespace,  */
        /* so it ought to be another number.  */
        errno = 0;
        /* Parse it.  */
        next = strtol (string, &tail, 0);
        /* Add it in, if possible.  */
        if (string == tail)
        {
            while (tail && !isspace (*tail)) tail++;
            printf("error: %s\n", strerror(errno));
            printf ("does not have the expected form: %s\n", string);
        }
        else if(errno == 0)
        {
            printf("%d\n", next);
            sum += next;
        }
        else
        {
            printf("error: %s\n", strerror(errno));
            printf ("error: %s\n", string);
        }
        /* Advance past it.  */
        string = tail;
    }

    return sum;
}

int main ()
{
    int sum = 0;
    size_t len = 0;
    char * line;
    FILE *f = fopen("file.txt", "w+");
    assert(f != NULL && "Error opening file");

    const char *text =  "010 0x10 -10 1111111111111111111111111111 0 30 A 10 +5 + 10 30\n"
                        "20 20B 6 ABC - 20 10 0";

    assert(fputs(text, f) > 0 && "error writing to file");
    rewind(f);
    errno = 0;
    while (getline(&line, &len, f) != -1)
    {
        sum += sum_ints_from_string(line);
        printf("%d\n", sum);

        free(line);
        line = NULL;
        len = 0;
    }
    assert(sum == 175);
    return 0;
}

第二個版本 - 積極的場景:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int
sum_ints_from_string (char *string)
{
    int sum = 0;

    while (1) {
        char *tail;
        int next;

        /* Skip whitespace by hand, to detect the end.  */
        while (isspace (*string)) string++;
        if (*string == 0)
            break;

        /* There is more nonwhitespace,  */
        /* so it ought to be another number.  */
        errno = 0;
        /* Parse it.  */
        next = strtol (string, &tail, 0);
        /* Add it in, if not overflow.  */
        if (errno) // returned value is not tested in GNU original
            printf ("Overflow\n");
        else
            sum += next;
        /* Advance past it.  */
        string = tail;
    }

    return sum;
}

int main ()
{
    int sum = 0;
    size_t len = 0;
    char * line;
    while (getline(&line, &len, stdin) != -1)
    {
        sum += sum_ints_from_string(line);
        /*
    `   If line is set to NULL and len is set 0 before the call, then
        getline() will allocate a buffer for storing the line.  This buffer
        should be freed by the user program even if getline() failed.
         */
        free(line);
        line = NULL;
        len = 0;
    }
    return 0;
}

幾乎跳過了 GNU 手冊中版本的錯誤檢查。 根據CppReference.com strtol

返回值

  • 如果成功,則返回與 str 內容對應的整數值。
  • 如果轉換后的值超出相應返回類型的范圍,則會發生范圍錯誤(將errno 設置為ERANGE)並返回LONG_MAX、LONG_MIN、LLONG_MAX 或LLONG_MIN。
  • 如果無法進行轉換,則返回 0 。

因此,出於求和的目的,我們只感興趣:是否可以添加下一個 val -我們不需要在這里進行細粒度和復雜的錯誤檢查 我們沒有任何可求和和打印錯誤的情況:超出范圍或 strtol 返回 0(零返回值意味着:整數等於 0 或無法執行轉換)。 否則我們添加下一個。

如您所見, atoi()只能用於解析getline()讀取的行上的第一個值,它還有其他缺點:如果字符串沒有轉換為整數,則返回值為0 ,即與字符串以0的有效表示開頭的情況沒有區別。

<stdlib.h>有更復雜的函數,可以將整數從不同基數(從 2 到 36)的表示形式轉換,檢測轉換錯誤並提供指向字符串其余部分的指針: strtolstrtoulstrtollstrtoull等。

如注釋中所述, getline()被指定為返回從文件讀取的字節數或錯誤時返回-1 不要與EOF進行比較。

這是使用函數strtol()修改后的代碼版本:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *line = NULL;
    size_t len = 0; 

    while (getline(&line, &len, stdin) >= 0) {
        char *p, *end;
        long sum = 0;
    
        printf("Line input: %s\n", line);

        printf("Parsed integers:");
        for (p = line; *p != '\0'; p = end) {
            long val = strtol(p, &end, 10);
            if (end == p)
               break;
            printf(" %ld", val);
            sum += val;
        }
        printf("\nSum: %ld\n", sum);
        /* check if loop stopped on conversion error or end of string */
        p += strspn(p, " \t\r\n");  /* skip white space */
        if (*p) {
            printf("Invalid input: %s", p);
        }
    }
    free(line); 
    return 0;
}

筆記:

  • getline不是 C 標准的一部分,它是 POSIX 擴展,它可能不適用於所有系統或可能具有不同的語義。
  • strtol()執行范圍檢查:如果轉換的值超出long類型的范圍,則返回的值是LONG_MINLONG_MAX取決於溢出的方向,並且errno設置為ERANGE
  • sum += val; 也可能導致算術溢出。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM