使用C将字符串常量转换为数值

时间:2019-03-07 12:54:39

标签: c algorithm debugging optimization

我编写了一个C程序,该程序使用两种不同的算法将代表数字值的字符串常量转换为其整数值。由于某些原因,第一种算法atoi()无法对大值正确执行,而第二种算法atoi_imp()可以正常工作。这是优化问题还是其他错误?问题在于第一个函数使程序的进程因错误而终止。

#include <stdio.h>
#include <string.h>

unsigned long long int atoi(const char[]);
unsigned long long int atoi_imp(const char[]);

int main(void) {
    printf("%llu\n", atoi("9417820179"));
    printf("%llu\n", atoi_imp("9417820179"));
    return 0;
}

unsigned long long int atoi(const char str[]) {
    unsigned long long int i, j, power, num = 0;
    for (i = strlen(str) - 1; i >= 0; --i) {
        power = 1;
        for (j = 0; j < strlen(str) - i - 1; ++j) {
            power *= 10;
        }
        num += (str[i] - '0') * power;
    }
    return num;
}

unsigned long long int atoi_imp(const char str[]) {
    unsigned long long int i, num = 0;
    for (i = 0; str[i] >= '0' && str[i] <= '9'; ++i) {
        num = num * 10 + (str[i] - '0');
    }
    return num;
}

4 个答案:

答案 0 :(得分:3)

atoi是C标准库的一部分,带有签名int atoi(const char *);

您要声明存在具有该名称的函数,但请给其不同的返回类型。请注意,在C语言中,函数名称是唯一重要的事情,并且工具链只能信任您在源代码中告诉的内容。如果您对编译器撒谎(如此处所示),则所有选择均不可行。

您应该为自己的实现选择其他名称,以避免出现问题。


@pmg研究表明,C标准(链接到C99.7.1.3)表示,将C标准库中的名称用于您自己的全局符号(函数或全局变量)显然是未定义行为 。当心鼻恶魔!

答案 1 :(得分:2)

好的,函数atoi至少存在一个问题。
您正在遍历一个无符号值,并检查它的较大值是否等于零,这应该是下溢。

最简单的解决方法是索引移位,即:

unsigned long long int my_atoi(const char str[]) {
    unsigned long long int i, j, power, num = 0;
    for (i = strlen(str); i != 0; --i) {
        power = 1;
        for (j = 0; j < strlen(str) - i; ++j) {
            power *= 10;
        }
        num += (str[i-1] - '0') * power;
    }
    return num;
}

答案 2 :(得分:1)

为时已晚,但可能会有所帮助。我为10为基数,如果您更改了基数,则需要注意如何计算*p-'0'中的数字0。

我将使用Horner's rule来计算值。

#include <stdio.h>
void main(void)
{
  char *a = "5363", *p = a;
  int unsigned base = 10;
  long unsigned x = 0;
  while(*p) {
    x*=base;
    x+=(*p-'0');
    p++;
  }
  printf("%lu\n", x);
}

答案 3 :(得分:1)

您的函数有一个无限循环:i是无符号的,i >= 0始终为真。

可以通过不同方式进行改进:

  • 您应该只计算一次str的长度。 strlen()并不便宜,它必须扫描字符串,直到找到空终止符。编译器并不总是能够优化针对同一参数的冗余调用。

  • power可以进行增量计算,而无需嵌套循环。

  • 您不应使用名称atoi,因为它是C库中的标准函数。除非您正确正确地实现其规范,否则应使用其他名称。

以下是经过纠正和改进的版本:

unsigned long long int atoi_power(const char str[]) {
    size_t i, len = strlen(str);
    unsigned long long int power = 1, num = 0;
    for (i = len; i-- > 0; ) {
        num += (str[i] - '0') * power;
        power *= 10;
    }
    return num;
}

通过这种方式修改后,该功能应具有与atoi_imp版本相似的性能。但是请注意,它们没有实现相同的语义。 atoi_pow必须提供一个数字字符串,而atoi_imp可以包含结尾字符。

事实上,atoi_impatoi_pow都未实现atoi的规范以扩展为处理较大的无符号整数:

  • atoi忽略了所有前导空格字符,
  • atoi接受'+''-'的可选符号。
  • atoi使用以下所有十进制数字,溢出时的行为未定义。
  • atoi忽略和尾随非十进制数字的字符。

鉴于这些语义,自然实现或atoiatoi_imp的自然实现,并带有额外的测试。请注意,即使strtoull()(可用于实现函数的功能)也可以处理空格和可选符号,尽管负值的转换可能会产生令人惊讶的结果。