繁体   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