将整数转换为字符串,无需使用库

12

我最近看到一道面试题:

编写一个将整数转换为字符串的函数。假设您没有访问库函数(如itoa()等)…

你会怎么做呢?


3
作业?如果要将一个整数转换为七进制,你需要怎么做?计算机也需要进行相同的操作(使用十进制)。 - pmg
1
atoi()更有趣,因为你需要处理前导空格、一元加或减以及正负溢出(等等)。 - James McNellis
14个回答

10

快速尝试一下:(已编辑以处理负数)

int n = INT_MIN;
char buffer[50];
int i = 0;

bool isNeg = n<0;

unsigned int n1 = isNeg ? -n : n;

while(n1!=0)
{
    buffer[i++] = n1%10+'0';
    n1=n1/10;
}

if(isNeg)
    buffer[i++] = '-';

buffer[i] = '\0';

for(int t = 0; t < i/2; t++)
{
    buffer[t] ^= buffer[i-t-1];
    buffer[i-t-1] ^= buffer[t];
    buffer[t] ^= buffer[i-t-1];
}

if(n == 0)
{
    buffer[0] = '0';
    buffer[1] = '\0';
}   

printf(buffer);

1
即将建议同样的事情。输入字符串并反转字符串。 - Manoj R
6
在二进制补码机器上,这种方式未能正确处理 INT_MIN - schot
@Mike 最简单的方法是将其转换为十进制并执行相同操作。 - igauravsehrawat
1
当n为0(零)时,此实现也将失败。 - wonder.mice
一些注释来解释正在发生的事情可以使它更容易理解。 - Haggra
显示剩余4条评论

10
在网上搜索itoa实现的例子,会给你提供很好的参考。下面是其中一个示例,它避免了在最后反转字符串的操作。它依赖于静态缓冲区,所以如果你将其用于不同的值,请小心处理。

在网络上搜索itoa实现的例子,会给您提供很好的示例。这里是一个示例,它避免了在最后反转字符串的操作。它依赖于静态缓冲区,因此请注意如果您想为不同的值重复使用它。

char* itoa(int val, int base){

    static char buf[32] = {0};

    int i = 30;

    for(; val && i ; --i, val /= base)

        buf[i] = "0123456789abcdef"[val % base];

    return &buf[i+1];

}

3
当val为0时,该实现会失败。 - wonder.mice
1
这很聪明。我喜欢它。 - Ekrem Dinçel
它也不能处理负数,但因为简短而受到赞扬。 - mercury0114

7

这个算法很容易用英语来描述。

给定一个整数,例如123

  1. 将其除以10 => 123/10。得到结果为12和余数为3

  2. 将3加上30h并压入堆栈中(加上30h将3转换为ASCII表示)

  3. 重复步骤1直到结果小于10

  4. 将结果加上30h并存储在堆栈中

  5. 堆栈按顺序包含数字 | 1 | 2 | 3 | ...


8
与其使用在常见编码中正确的神奇十六进制常量,不如添加“0”更好。没有必要将代码与ASCII绑定在一起,除非必须这样做。 - unwind
2
添加 '0' 是将代码与 ASCII 绑定在一起——您假定 '0'..'9' 依次排列。添加 0x30 可以生成 ASCII 输出,而不考虑编译器的代码集。如果编译器的代码集是 ASCII,则添加 '0' 会产生相同的输出,否则可能会产生乱码输出。话虽如此,如果我的编译器不使用字符常量的 ASCII 值,我会更换它! - Nicholas Wilson
4
C标准要求数字必须按照源字符集和执行字符集的顺序排列。 - user694733

2
我会记住在ASCII字符集中,所有数字字符都按递增顺序排列,并且它们之间没有其他字符。
我还会反复使用“/”和“%”运算符。
如何获取字符串的内存取决于您未提供的信息。

2
为什么会有踩?数字值之间的关系并不是ASCII特定的,这是C标准所要求的。虽然我认为这个答案并不是很好,但我认为它故意模糊了问题,因为这是一道作业题。+1来弥补荒谬的踩。 - R.. GitHub STOP HELPING ICE
1
我认为基于当前得分过高或过低的感觉投票并不是不合理的;天真地说,我会期望很多人这样做。当然,这似乎更适合在元社区进行讨论,所以如果您认为它值得进一步讨论,也许可以在那里提出问题..? - R.. GitHub STOP HELPING ICE
回复你的第一篇帖子,说答案“不太好”,那是有意为之的,因为我非常怀疑这个问题是某人的家庭作业。我的意图是提供足够的指导,让他们自己实现,而不是替他们完成。感谢你的中立投票。 - nategoose
1
@LightnessRacesinOrbit 每个用户的责任是有效地使用自己的投票来推广有用的信息,同时埋葬垃圾。 - MickLH
@MickLH:在推广他们认为有用的信息的同时,深埋他们认为是垃圾的信息。当你仅仅为了撤销别人的投票而投票时,你是在故意违反你刚刚描述的投票系统的目的。 - Lightness Races in Orbit
显示剩余2条评论

1
越快越好?
unsigned countDigits(long long x)
{
    int i = 1;
    while ((x /= 10) && ++i);
    return i;
}
unsigned getNumDigits(long long x)
{
    x < 0 ? x = -x : 0;
    return
        x < 10 ? 1 :
        x < 100 ? 2 :
        x < 1000 ? 3 :
        x < 10000 ? 4 :
        x < 100000 ? 5 :
        x < 1000000 ? 6 :
        x < 10000000 ? 7 :
        x < 100000000 ? 8 :
        x < 1000000000 ? 9 :
        x < 10000000000 ? 10 : countDigits(x);
}
#define tochar(x) '0' + x
void tostr(char* dest, long long x)
{
    unsigned i = getNumDigits(x);
    char negative = x < 0;
    if (negative && (*dest = '-') & (x = -x) & i++);
    *(dest + i) = 0;
    while ((i > negative) && (*(dest + (--i)) = tochar(((x) % 10))) | (x /= 10));
}

如果你想要调试,可以将条件(指令)拆分成在while范围内的代码行{}

1
我很喜欢你的答案,但我认为通过一些微调它可以变得更好。例如,char tochar(unsigned short from) 可以被优化:inline char tochar(unsigned char from) { if (from > 9) return '0'; return '0' + from; } 在这个特定的例子中,可以删除边界情况,因为你确定将始终传递从 0 到 9 的数字给 tochar - Cristian
1
@Cristian 那是我很久以前的答案了,所以相信我,当我看到我的答案时,我也想到了完全相同的事情!哈哈..现在我已经编辑了它,包括一些其他的东西,因为它提到了“没有库”,而两年前我显然盲目地在这里使用了 calloc,这显然需要一个库,我还记得~所以,谢谢你。现在它甚至更好了。 :) - Beyondo

1

我看到了这个问题,所以决定提供我通常用于此的代码:

char *SignedIntToStr(char *Dest, signed int Number, register unsigned char Base) {
    if (Base < 2 || Base > 36) {
        return (char *)0;
    }
    register unsigned char Digits = 1;
    register unsigned int CurrentPlaceValue = 1;
    for (register unsigned int T = Number/Base; T; T /= Base) {
        CurrentPlaceValue *= Base;
        Digits++;
    }
    if (!Dest) {
        Dest = malloc(Digits+(Number < 0)+1);
    }
    char *const RDest = Dest;
    if (Number < 0) {
        Number = -Number;
        *Dest = '-';
        Dest++;
    }
    for (register unsigned char i = 0; i < Digits; i++) {
        register unsigned char Digit = (Number/CurrentPlaceValue);
        Dest[i] = (Digit < 10? '0' : 87)+Digit;
        Number %= CurrentPlaceValue;
        CurrentPlaceValue /= Base;
    }
    Dest[Digits] = '\0';
    return RDest;
}
#include <stdio.h>
int main(int argc, char *argv[]) {
    char String[32];
    puts(SignedIntToStr(String, -100, 16));
    return 0;
}

如果将NULL传入Dest,则会自动分配内存。否则,它将写入Dest。

1

假设它是十进制的,那么就像这样:

   int num = ...;
   char res[MaxDigitCount];
   int len = 0;
   for(; num > 0; ++len)
   {
      res[len] = num%10+'0';
      num/=10; 
   }
   res[len] = 0; //null-terminating

   //now we need to reverse res
   for(int i = 0; i < len/2; ++i)
   {
       char c = res[i]; res[i] = res[len-i-1]; res[len-i-1] = c;
   }   

1
这个程序没有处理负数,而且 MaxDigitCount 需要设置为 MaxDigitCountPlusTwo 来考虑一元减号和空终止符。 - James McNellis
当num为0(零)时,此实现也会失败。 - wonder.mice

1

将整数转换为字符串,无需访问库

首先将最低位数字转换为字符,然后再处理更高位的数字。


通常我会将结果的字符串移动到指定位置,但是通过递归可以使用一些紧凑的代码跳过这个步骤。
在myitoa_helper()中使用neg_a可以避免INT_MIN的未定义行为。
// Return character one past end of character digits.
static char *myitoa_helper(char *dest, int neg_a) {
  if (neg_a <= -10) {
    dest = myitoa_helper(dest, neg_a / 10);
  }
  *dest = (char) ('0' - neg_a % 10);
  return dest + 1;
}

char *myitoa(char *dest, int a) {
  if (a >= 0) {
    *myitoa_helper(dest, -a) = '\0';
  } else {
    *dest = '-';
    *myitoa_helper(dest + 1, a) = '\0';
  }
  return dest;
}

void myitoa_test(int a) {
  char s[100];
  memset(s, 'x', sizeof s);
  printf("%11d <%s>\n", a, myitoa(s, a));
}

测试代码 & 输出

#include "limits.h"
#include "stdio.h"

int main(void) {
  const int a[] = {INT_MIN, INT_MIN + 1, -42, -1, 0, 1, 2, 9, 10, 99, 100,
      INT_MAX - 1, INT_MAX};
  for (unsigned i = 0; i < sizeof a / sizeof a[0]; i++) {
    myitoa_test(a[i]);
  }
  return 0;
}

-2147483648 <-2147483648>
-2147483647 <-2147483647>
        -42 <-42>
         -1 <-1>
          0 <0>
          1 <1>
          2 <2>
          9 <9>
         10 <10>
         99 <99>
        100 <100>
 2147483646 <2147483646>
 2147483647 <2147483647>

1

itoa()函数的实现似乎是一项容易的任务,但实际上你必须注意许多与你的确切需求相关的方面。我猜想在面试中,你应该详细说明解决方案的方式,而不是复制可以在Google(http://en.wikipedia.org/wiki/Itoa)中找到的解决方案。

以下是您可能想要问自己或面试官的一些问题:

  • 字符串应该位于哪里(malloced?由用户传递?静态变量?)
  • 是否应支持有符号数字?
  • 是否应支持浮点数?
  • 是否应支持除10以外的其他基数?
  • 我们需要任何输入检查吗?
  • 输出字符串的长度是否有限制?

等等。


为什么 itoa 能够处理浮点数?itoa 是 Integer to ASCII。 - John Boker
我只是想强调一点,在面试中,你提出的问题和解决问题的方式与代码一样重要。当然,代码本身也应该是完美的。 - eyalm

0
//Fixed the answer from [10]

#include <iostream>

void CovertIntToString(unsigned int n1)
{
    unsigned int n = INT_MIN;
    char buffer[50];
    int i = 0;
    n = n1;

    bool isNeg = n<0;
    n1 = isNeg ? -n1 : n1;

    while(n1!=0)
    {
        buffer[i++] = n1%10+'0';
        n1=n1/10;
    }

    if(isNeg)
        buffer[i++] = '-';

    buffer[i] = '\0';

    // Now we must reverse the string
    for(int t = 0; t < i/2; t++)
    {
        buffer[t] ^= buffer[i-t-1];
        buffer[i-t-1] ^= buffer[t];
        buffer[t] ^= buffer[i-t-1];
    }

    if(n == 0)
    {
        buffer[0] = '0';
        buffer[1] = '\0';
    }

    printf("%s", buffer);
}

int main() {
    unsigned int x = 4156;
    CovertIntToString(x);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接