[英]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 并将errno
为EINVAL
”,当它无法转换字符串时,C 标准并不要求为此errno
。 但是,当转换失败时, eon
将被设置为start
——这是由标准保证的。 因此,有理由认为EINVAL
的测试部分是多余的。
while
循环使用逗号运算符调用strtol()
以解决其副作用(分配给value
和eon
),并忽略结果 - 忽略它是必要的,因为所有可能的返回值都是有效的。 条件的其他三行(逗号运算符的 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 :
返回值
因此,出于求和的目的,我们只感兴趣:是否可以添加下一个 val -我们不需要在这里进行细粒度和复杂的错误检查。 我们没有任何可求和和打印错误的情况:超出范围或 strtol 返回 0(零返回值意味着:整数等于 0 或无法执行转换)。 否则我们添加下一个。
如您所见, atoi()
只能用于解析getline()
读取的行上的第一个值,它还有其他缺点:如果字符串没有转换为整数,则返回值为0
,即与字符串以0
的有效表示开头的情况没有区别。
<stdlib.h>
有更复杂的函数,可以将整数从不同基数(从 2 到 36)的表示形式转换,检测转换错误并提供指向字符串其余部分的指针: strtol
、 strtoul
、 strtoll
、 strtoull
等。
如注释中所述, 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_MIN
或LONG_MAX
取决于溢出的方向,并且errno
设置为ERANGE
。sum += val;
也可能导致算术溢出。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.