继续您对能够将缓冲区中的任何整数值表示形式转换为整数的评论,strtol 没有替代品。虽然您可以使用sscanf 使用%n 说明符来获取消耗的字符数以了解指针前进多少——为什么?除了成功/失败之外,您的任何scanf 转换几乎都没有错误报告功能。 strtol 具有内置的能力,可以在你使用时将缓冲区转换为 long。
怎么样? strtol 的原型是:
long int strtol(const char *nptr, char **endptr, int base);
如果包含数字的字符串由nptr 提供,endptr 将设置为在成功转换后使用的最后一个数字之后的一个字符,base 提供转换数字的基数(例如 base 2, 8, 10, 16 等),其中0 的基数将允许从八进制、十进制或十六进制转换,具体取决于字符串表示的开头是0(八进制)还是0x(十六进制)或1-9(十进制)。
因此,如果您给strtol 一个包含数字的字符串,则在成功转换时,返回一个long 值,可以针对INT_MIN/INT_MAX 进行测试以确定它是否在int、@ 的范围内987654342@ 将设置为最后一位转换后的数字(设置为用于下一个值),您只需设置p = endptr; 并继续。
此外,您知道nptr == endptr 在转换之后 - 没有数字被转换。 strtol 还将errno 设置为上溢/下溢,因此您可以验证是否成功转换为long。此外,如果您想检查接下来会发生什么,endptr 指向缓冲区中包含的其余内容的开始字符。您知道转换过程中发生了什么以及需要转换的内容。
那么你如何使用它呢?这真的很简单。首先让我们为数组中的整数个数和缓冲区中的最大字符数定义几个常量,例如
#include <stdio.h>
#include <stdlib.h> /* for strtol */
#include <limits.h> /* for INT_MIN/INT_MAX */
#include <errno.h> /* for errno */
#define ARSZ 100
#define MAXC 1024
现在声明你的数组,你的缓冲区来保存你的输入,和一个计数器来跟踪转换和存储在数组中的整数值的数量:
int arr[ARSZ] = {0};
char buf[MAXC] = "";
size_t n = 0;
现在让我们使用作为程序第一个参数提供的文件名打开一个文件进行读取(如果没有提供参数,则默认从stdin 读取)并验证我们有一个有效的打开文件流,例如
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
现在是时候着手处理读取输入行并将任何字符引用转换为数值到 long 和 strtol 的业务了。首先,使用fgets 将一行输入读入您的缓冲区并声明您的nptr(缩短为p)、endptr 和一个临时的long 值tmp 以保存来自strtol 的返回,例如
while (fgets (buf, MAXC, fp)) { /* read each line of input */
char *p = buf, *endptr; /* nptr & endptr for strtol */
long tmp; /* temp long for strtol */
使用一行输入,您现在继续使用 strtol 循环转换值,验证 (1) 数字已转换,(2) 没有发生上溢/下溢(例如,适合 long 的值),( 3) 该值在int 的范围内,或 (4) 警告该值超出int 的大小。 (后面会讲到nextdigit这个辅助函数):
/* protect array bounds, loop while not end of buf */
while (n < ARSZ && *p && *p != '\n') {
errno = 0; /* reset errno each iteration */
tmp = strtol (p, &endptr, 0); /* call strtol, update endptr */
if (p == endptr) /* validate digits converted */
fputs ("error: no digits converted.\n", stderr);
else if (errno) /* validate conversion */
fputs ("error: over/underflow occurred.\n", stderr);
/* validate tmp is in range of integer */
else if (INT_MIN <= tmp && tmp <= INT_MAX)
arr[n++] = tmp;
else
fputs ("error: value exceeds range of int.\n", stderr);
if (!(p = (char *)nextdigit (endptr))) /* get next digit */
break;
}
(注意:while 循环的条件,n < ARSZ——不要存储超过你有空间的整数,而*p && *p != '\n'(你不在末尾字符串,例如 nul-character 或换行符)。你可以省略换行符检查,它会在循环体中被无害地处理,但是为什么?一个简单的检查避免了循环体完全循环)
什么是nextdigit() 辅助函数?从您对strtol(3) - Linux manual page 的阅读中,您知道strtol 将跳过任何前导空格直到要转换的下一个数字的开头,但是如果您有一个逗号分隔的文件,或者您的数字之间有其他字符怎么办?处理起来非常简单,只需在缓冲区中向前扫描,检查字符直到找到下一个0-9,或者找到下一个+/-,而显式符号之后的下一个字符是0-9。
这就是nextdigit 所做的一切,返回下一个要转换的值的开头地址,如果没有其他数字要转换,则返回NULL。
/* scan forward in 'p' to find next valid signed integer beginning */
const char *nextdigit (const char *p)
{
while (*p) {
if (('0' <= *p && *p <= '9') ||
((*p == '-' || *p == '+') && '0' <= *(p + 1) && *(p + 1) <= '9'))
return p;
p++;
}
return NULL;
}
(注意:您可以并且应该包含ctype.h 并将('0' <= *p && *p <= '9') 的检查替换为简单的isdigit(*p),与isdigit(*(p+1)) 相同,但完整的手册测试仅用于说明目的)
注意代码中endptr(前向扫描的起始地址)如何传递给nextdigit(),结果是分配给p,并且在下一次迭代开始之前验证不是NULL(使用p 准备再次传递给 strtol 以进行下一次转换)- 冲洗并重复,直到缓冲区中的字符用完。
以这种方式将缓冲区转换为整数值可以让您挑选并转换行中的所有整数值——无论格式多么混乱。
总之你可以做到:
#include <stdio.h>
#include <stdlib.h> /* for strtol */
#include <limits.h> /* for INT_MIN/INT_MAX */
#include <errno.h> /* for errno */
#define ARSZ 100
#define MAXC 1024
/* scan forward in 'p' to find next valid signed integer beginning */
const char *nextdigit (const char *p)
{
while (*p) {
if (('0' <= *p && *p <= '9') ||
((*p == '-' || *p == '+') && '0' <= *(p + 1) && *(p + 1) <= '9'))
return p;
p++;
}
return NULL;
}
int main (int argc, char **argv) {
int arr[ARSZ] = {0};
char buf[MAXC] = "";
size_t n = 0;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line of input */
char *p = buf, *endptr; /* nptr & endptr for strtol */
long tmp; /* temp long for strtol */
/* protect array bounds, loop while not end of buf */
while (n < ARSZ && *p && *p != '\n') {
errno = 0; /* reset errno each iteration */
tmp = strtol (p, &endptr, 0); /* call strtol, update endptr */
if (p == endptr) /* validate digits converted */
fputs ("error: no digits converted.\n", stderr);
else if (errno) /* validate conversion */
fputs ("error: over/underflow occurred.\n", stderr);
/* validate tmp is in range of integer */
else if (INT_MIN <= tmp && tmp <= INT_MAX)
arr[n++] = tmp;
else
fputs ("error: value exceeds range of int.\n", stderr);
if (!(p = (char *)nextdigit (endptr))) /* get next digit */
break;
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < n; i++) { /* output results */
if (i && i %10 == 0) /* 10-values per row */
putchar ('\n');
printf (" %4d", arr[i]);
}
putchar ('\n'); /* tidy up with newline */
}
现在让我们看看这种方法可以处理转换为整数值的行,例如
输入文件示例
空格分隔值:
$ cat dat/10int_space.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
换行分隔值:
$ cat dat/10int_nl.txt
8572
-2213
6434
16330
3034
12346
4855
16985
11250
1495
混乱中的整数:
$ cat dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a
- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495
使用/输出示例
转换例程如何公平?
空格分隔:
$ ./bin/fgets_strtol_any_fixed <dat/10int_space.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
换行符分隔:
$ ./bin/fgets_strtol_any_fixed <dat/10int_nl.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
邪恶的混乱:
$ ./bin/fgets_strtol_any_fixed <dat/10intmess.txt
error: no digits converted.
error: no digits converted.
error: no digits converted.
error: no digits converted.
error: no digits converted.
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
所有值,无论是空格分隔、换行分隔,还是散布在“A quick brown fox...”中间,均已正确转换。
花时间消化strtol 的手册页,然后消化上面的实现方式。一旦你掌握了strtol(和strtoul(对于unsigned long)、strtod(对于double)等等......它们的工作原理都非常相似,几乎没有数字转换可以' t 处理。如果您还有其他问题,请告诉我。