安全地将字符串转换为数字 strtoX 函数

Version >= C99

从 C99 开始,C 库有一组安全转换函数,将字符串解释为数字。它们的名称形式为 strtoX,其中 Xluld 等之一,用于确定转换的目标类型

double strtod(char const* p, char** endptr);
long double strtold(char const* p, char** endptr);

它们会检查转换是否存在溢出或下溢:

double ret = strtod(argv[1], 0); /* attempt conversion */

/* check the conversion result. */
if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return;  /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

如果字符串实际上根本不包含任何数字,则 strtod 的这种用法将返回 0.0

如果这不令人满意,可以使用附加参数 endptr。它是指向指针的指针,指针指向字符串中检测到的数字的末尾。如果设置为 0,如上所述,或者 NULL,则会被忽略。

endptr 参数提供指示是否已成功转换,如果成功,则表示数字结束的位置:

char *check = 0;
double ret = strtod(argv[1], &check); /* attempt conversion */

/* check the conversion result. */
if (argv[1] == check) 
    return; /* No number was detected in string */
else if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

有类似的函数可以转换为更宽的整数类型:

long strtol(char const* p, char** endptr, int nbase);
long long strtoll(char const* p, char** endptr, int nbase);
unsigned long strtoul(char const* p, char** endptr, int nbase);
unsigned long long strtoull(char const* p, char** endptr, int nbase);

这些函数有第三个参数 nbase,它保存写入数字的数字基数。

long a = strtol("101",   0, 2 ); /* a = 5L */
long b = strtol("101",   0, 8 ); /* b = 65L */
long c = strtol("101",   0, 10); /* c = 101L */
long d = strtol("101",   0, 16); /* d = 257L */
long e = strtol("101",   0, 0 ); /* e = 101L */
long f = strtol("0101",  0, 0 ); /* f = 65L */
long g = strtol("0x101", 0, 0 ); /* g = 257L */

nbase 的特殊值 0 表示字符串的解释方式与在 C 程序中解释数字文字的方式相同:0x 的前缀对应十六进制表示,否则前导 0 是八进制,所有其他数字看作十进制。

因此,将命令行参数解释为数字的最实用方法是

int main(int argc, char* argv[] {
    if (argc < 1)
        return EXIT_FAILURE; /* No number given. */

    /* use strtoull because size_t may be wide */
    size_t mySize = strtoull(argv[1], 0, 0);

    /* then check conversion results. */

     ...

    return EXIT_SUCCESS;
}

这意味着可以使用八进制,十进制或十六进制参数调用程序。