[英]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.