实现良好的“itoa()”功能的正确方法是什么?

时间:2010-08-09 13:59:30

标签: c string char

我想知道我执行“itoa”功能是否正确。也许你可以帮我把它变得更“正确”,我很确定我错过了什么。 (也许已经有一个库按照我希望的方式进行转换,但是......找不到任何内容)

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

char * itoa(int i) {
  char * res = malloc(8*sizeof(int));
  sprintf(res, "%d", i);
  return res;
}

int main(int argc, char *argv[]) {
 ...

12 个答案:

答案 0 :(得分:11)

// Yet, another good itoa implementation
// returns: the length of the number string
int itoa(int value, char *sp, int radix)
{
    char tmp[16];// be careful with the length of the buffer
    char *tp = tmp;
    int i;
    unsigned v;

    int sign = (radix == 10 && value < 0);    
    if (sign)
        v = -value;
    else
        v = (unsigned)value;

    while (v || tp == tmp)
    {
        i = v % radix;
        v /= radix; // v/=radix uses less CPU clocks than v=v/radix does
        if (i < 10)
          *tp++ = i+'0';
        else
          *tp++ = i + 'a' - 10;
    }

    int len = tp - tmp;

    if (sign) 
    {
        *sp++ = '-';
        len++;
    }

    while (tp > tmp)
        *sp++ = *--tp;

    return len;
}

// Usage Example:
char int_str[15]; // be careful with the length of the buffer
int n = 56789;
int len = itoa(n,int_str,10);

答案 1 :(得分:7)

唯一的实际错误是您没有检查malloc的返回值为null。

名称itoa已经被用于非标准的功能,但并非罕见。它不分配内存,而是写入调用者提供的缓冲区:

char *itoa(int value, char * str, int base);

如果你不想依赖你的平台,我仍然建议遵循这种模式。在C中返回新分配的内存的字符串处理函数通常比它们长期运行时更麻烦,因为大多数时候你最终会进行进一步的操作,因此你必须释放大量的中间结果。例如,比较:

void delete_temp_files() {
    char filename[20];
    strcpy(filename, "tmp_");
    char *endptr = filename + strlen(filename);
    for (int i = 0; i < 10; ++i) {
        itoa(endptr, i, 10); // itoa doesn't allocate memory
        unlink(filename);
    }
}

VS

void delete_temp_files() {
    char filename[20];
    strcpy(filename, "tmp_");
    char *endptr = filename + strlen(filename);
    for (int i = 0; i < 10; ++i) {
        char *number = itoa(i, 10); // itoa allocates memory
        strcpy(endptr, number);
        free(number);
        unlink(filename);
    }
}

如果您有理由特别关注性能(例如,如果您正在实现包含itoa的stdlib样式库),或者您正在实现sprintf不支持的基础,那么你可能会考虑不调用sprintf。但是如果你想要一个基数为10的字符串,那么你的第一直觉是正确的。关于%d格式说明符绝对没有“错误”。

以下是itoa的可能实现,仅适用于基数10:

char *itobase10(char *buf, int value) {
    sprintf(buf, "%d", value);
    return buf;
}

这是一个将snprintf风格的方法结合到缓冲区长度的方法:

int itobase10n(char *buf, size_t sz, int value) {
    return snprintf(buf, sz, "%d", value);
}

答案 2 :(得分:3)

我认为你分配的内存太多了。 malloc(8*sizeof(int))将在大多数机器上提供32个字节,这对于int的文本表示来说可能过多。

答案 3 :(得分:3)

好的int字符串itoa()具有以下属性;

  • 适用于所有[INT_MIN...INT_MAX],基础[2...36],无缓冲区溢出。
  • 不假设int大小。
  • 不需要补充2。
  • 不要求unsigned的正范围大于int。换句话说,不使用unsigned
  • 允许将'-'用于否定号码,即使base != 10
  • 也是如此

根据需要定制错误处理。 (需要C99或更高版本):

char* itostr(char *dest, size_t size, int a, int base) {
  // Max text needs occur with itostr(dest, size, INT_MIN, 2)
  char buffer[sizeof a * CHAR_BIT + 1 + 1]; 
  static const char digits[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

  if (base < 2 || base > 36) {
    fprintf(stderr, "Invalid base");
    return NULL;
  }

  // Start filling from the end
  char* p = &buffer[sizeof buffer - 1];
  *p = '\0';

  // Work with negative `int`
  int an = a < 0 ? a : -a;  

  do {
    *(--p) = digits[-(an % base)];
    an /= base;
  } while (an);

  if (a < 0) {
    *(--p) = '-';
  }

  size_t size_used = &buffer[sizeof(buffer)] - p;
  if (size_used > size) {
    fprintf(stderr, "Scant buffer %zu > %zu", size_used , size);
    return NULL;
  }
  return memcpy(dest, p, size_used);
}

答案 4 :(得分:2)

我不太确定8*sizeof(int)作为最大可能字符数的位置 - ceil(8 / (log(10) / log(2)))会产生3*的乘数。此外,在C99和一些较旧的POSIX平台下,您可以使用sprintf()创建精确分配的版本:

char *
itoa(int i) 
{
    int n = snprintf(NULL, 0, "%d", i) + 1;
    char *s = malloc(n);

    if (s != NULL)
        snprintf(s, n, "%d", i);
    return s;
}

HTH

答案 5 :(得分:2)

我找到了一个有趣的资源来处理itoa实现的几个不同问题 你可能也想看一下 itoa() implementations with performance tests

答案 6 :(得分:1)

为此,您应该在printf系列中使用一个函数。如果您要将结果写入stdout或文件,请使用printf / fprintf。否则,请使用snprintf缓冲区,其大小足以容纳3*sizeof(type)+2个字节或更多。

答案 7 :(得分:1)

sprintf非常慢,如果性能很重要,它可能不是最好的解决方案。

如果基本参数是2的幂,则可以通过移位和屏蔽来完成转换,并且可以通过记录最高位置的数字来避免反转字符串。例如,base = 16

这样的东西
int  num_iter = sizeof(int) / 4;

const char digits [] = {&#39; 0&#39;,&#39; 1&#39;,&#39; 2&#39;,&#39; 3&#39;,&#39; 4&#39;,&#39; 5&#39;,&#39; 6&#39;,&#39; 7&#39;,&#39; 8&#39;,&#39; 9&#39;, &#39; a&#39;,&#39;&#39;&#39; c&#39;,&#39; d&#39;,&#39; e&#39;,&#39; f& #39;};

/* skip zeros in the highest positions */
int i = num_iter;
for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    if ( digit > 0 )  break;
}

for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    result[len++] = digits[digit];
}

对于小数,有一个很好的想法是使用足够大的静态数组以相反的顺序记录数字,请参阅here

答案 8 :(得分:1)

  • 整数到ASCII需要从标准整数类型转换数据 转换成ASCII字符串。
  • 所有操作都需要使用指针算术而不是数组索引执行。
  • 您希望转换的数字以带符号的32位整数形式传递。
  • 通过指定要转换为(base)的基数的整数值,您应该能够支持2到16的基数。
  • 将转换后的字符串复制到作为参数(ptr)传入的uint8_t *指针。
  • 签名的32位数字将具有最大字符串大小(提示:请以2为底)。
  • 必须在转换后的c字符串的末尾放置一个空终止符。函数应返回转换后数据的长度(包括负号)。
  • 示例my_itoa(ptr,1234,10)应该返回ASCII字符串长度5(包括空终止符)。
  • 此功能需要处理签名数据。
  • 您不得使用任何字符串函数或库。

uint8_t my_itoa(int32_t data, uint8_t *ptr, uint32_t base){
        uint8_t cnt=0,sgnd=0;
        uint8_t *tmp=calloc(32,sizeof(*tmp));
        if(!tmp){exit(1);}
        else{
            for(int i=0;i<32;i++){
            if(data<0){data=-data;sgnd=1;}
            if(data!=0){
               if(data%base<10){
                *(tmp+i)=(data%base)+48;
                data/=base;
               }
               else{
                *(tmp+i)=(data%base)+55;
                data/=base;
               }
            cnt++;     
            }
           }
        if(sgnd){*(tmp+cnt)=45;++cnt;}
        }
     my_reverse(tmp, cnt);
     my_memcopy(tmp,ptr,cnt);
     return ++cnt;
}
  • ASCII到整数需要将数据从ASCII表示的字符串转换回整数类型。
  • 所有操作都需要使用指针算术而不是数组索引执行
  • 要转换的字符串作为uint8_t *指针(ptr)传递。
  • 您的字符集中的位数是作为uint8_t整数(数字)传递的。
  • 您应该能够支持2到16基地。
  • 应返回转换后的32位带符号整数。
  • 此功能需要处理签名数据。
  • 您不得使用任何字符串函数或库。

int32_t my_atoi(uint8_t *ptr, uint8_t digits, uint32_t base){
    int32_t sgnd=0, rslt=0;
    for(int i=0; i<digits; i++){
        if(*(ptr)=='-'){*ptr='0';sgnd=1;}
        else if(*(ptr+i)>'9'){rslt+=(*(ptr+i)-'7');}
        else{rslt+=(*(ptr+i)-'0');}
        if(!*(ptr+i+1)){break;}
        rslt*=base;
    }
    if(sgnd){rslt=-rslt;}
    return rslt;
}

答案 9 :(得分:0)

我可能会提出一些建议。您可以使用静态缓冲区和strdup来避免在后续调用中重复分配过多内存。我还会添加一些错误检查。

char *itoa(int i)
{
  static char buffer[12];

  if (snprintf(buffer, sizeof(buffer), "%d", i) < 0)
    return NULL;

  return strdup(buffer);
}

如果在多线程环境中调用它,请从缓冲区声明中删除“static”。

答案 10 :(得分:0)

这应该有效:

#include <string.h>
#include <stdlib.h>
#include <math.h>

char * itoa_alloc(int x) {
   int s = x<=0 ? 1 ? 0; // either space for a - or for a 0
   size_t len = (size_t) ceil( log10( abs(x) ) );
   char * str = malloc(len+s + 1);

   sprintf(str, "%i", x);

   return str;
}

如果您不想使用数学/浮点函数(并且必须在数学库中链接),您应该能够通过搜索Web找到log10的非浮点版本并执行:< / p>

size_t len = my_log10(abs(x))+ 1;

这可能比你需要多1个字节,但你已经足够了。

答案 11 :(得分:-1)

main()
{
  int i=1234;
  char stmp[10];
#if _MSC_VER
  puts(_itoa(i,stmp,10));
#else
  puts((sprintf(stmp,"%d",i),stmp));
#endif
  return 0;
}