Подтвердить что ты не робот

Каков надлежащий способ реализации хорошей функции "itoa()"?

Мне было интересно, правильно ли реализована функция 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[]) {
 ...
4b9b3361

Ответ 1

Единственная действительная ошибка заключается в том, что вы не проверяете возвращаемое значение 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);
    }
}

против.

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);
    }
}

Если у вас были причины особенно беспокоиться о производительности (например, если вы используете библиотеку стиля stdlib, включая itoa), или если вы реализуете базы, которые 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

// 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);

Ответ 3

Я думаю, что вы выделяете, возможно, слишком много памяти. malloc(8*sizeof(int)) даст вам 32 байта на большинстве машин, что, вероятно, является чрезмерным для текстового представления int.

Ответ 4

Хороший 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);
}

Ответ 5

Я не совсем уверен, где вы получаете 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;
}

НТН

Ответ 6

я нашел интересный ресурс, посвященный нескольким различным проблемам с реализацией itoa
вы можете захотеть посмотреть его тоже
itoa() с тестами производительности

Ответ 7

Для этой цели вы должны использовать функцию в семействе printf. Если вы напишете результат в stdout или в файле, используйте printf/fprintf. В противном случае используйте snprintf с достаточно большим буфером для хранения 3*sizeof(type)+2 байтов или более.

Ответ 8

sprintf довольно медленный, если производительность имеет значение, это, вероятно, не лучшее решение.

если базовый аргумент равен 2, преобразование может быть выполнено со сдвигом и маскировкой, и можно избежать изменения строки, записав цифры с самых высоких позиций. Например, что-то вроде этого для base = 16

int  num_iter = sizeof(int) / 4;

const char цифры [] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9 ',' a ',' b ',' c ',' d ',' e ',' f '};

/* 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];
}

Для десятичных знаков есть хорошая идея использовать статический массив, достаточно большой для записи чисел в обратном порядке, см. здесь

Ответ 9

  • Integer-to-ASCII должен преобразовывать данные из стандартного целочисленного типав строку ASCII.
  • Все операции должны выполняться с использованием арифметики указателей, а не индексации массива.
  • Число, которое вы хотите преобразовать, передается как 32-разрядное целое число со знаком.
  • Вы должны иметь возможность поддерживать базы от 2 до 16, указав целочисленное значение базы, в которую вы хотите преобразовать (базу).
  • Скопируйте преобразованную символьную строку в указатель uint8_t *, переданный в качестве параметра (ptr).
  • 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-to-Integer необходимо преобразовать данные обратно из строки, представленной в 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;
}

Ответ 10

Вот несколько предложений, которые я мог бы сделать. Вы можете использовать статический буфер и strdup, чтобы избежать многократного выделения слишком большого количества памяти при последующих вызовах. Я бы также добавил некоторые проверки ошибок.

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

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

  return strdup(buffer);
}

Если это будет вызываться в многопоточной среде, удалите "static" из объявления буфера.

Ответ 11

Это должно работать:

#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;
}

Если вы не хотите использовать функции математики/с плавающей запятой (и должны ссылаться в математических библиотеках), вы можете найти версии log10 без плавающей запятой, выполнив поиск в Интернете и выполните:

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

Это может дать вам еще 1 байт, чем вам нужно, но вам будет достаточно.

Ответ 12

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;
}