Каков надлежащий способ реализации хорошей функции "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[]) {
...
Ответы
Ответ 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;
}