在C#中如何将Int转换为String而不使用ToString()函数?

57
将以下整数参数转换为字符串,不使用任何本地的toString功能。
public string integerToString(int integerPassedIn){    
    //Your code here
}
由于所有的类都继承自Object并且Object有一个ToString()方法,那么如果不使用内置的ToString()方法,你如何将一个int类型转换为string类型呢?
使用字符串拼接的问题在于它会沿着继承链一直调用ToString()方法,直到到达最顶层或者到达Object类。
在C#中,如果不使用ToString()方法,你该如何将整数转换为字符串?

4
遍历整数模10,将它们连接成新字符串似乎是他们所寻找的,但你说连接调用了toString函数? - Gray
2
String str = "" + a; 会调用 toString 方法,我想这就是 @AMR 的意思。 - bizzehdee
68
必须实现 ToString 而又不能调用 ToString 方法,你认为他们是如何做到的? - Eric Lippert
13
黑魔法... 或者是 @EricLippert 做的... - DotNetRussell
8
@AMR,你为什么要问这个问题?这是一个面试/作业问题还是你只是好奇呢? - Kevin
显示剩余9条评论
10个回答

74

像这样:

public string IntToString(int a)
{    
    var chars = new[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
    var str = string.Empty;
    if (a == 0)
    {
        str = chars[0];
    }
    else if (a == int.MinValue)
    {
        str = "-2147483648";
    }
    else
    {
        bool isNegative = (a < 0);
        if (isNegative)
        {
            a = -a;
        }

        while (a > 0)
        {
            str = chars[a % 10] + str;
            a /= 10;
        }

        if (isNegative)
        {
            str = "-" + str;
        }
    }

    return str;
}

更新:这里有另一个版本,更短,性能应该更好,因为它不使用字符串拼接,而是采用操作固定长度数组。它支持高达16进制的基数,但很容易将其扩展到更高的基数。它可能还可以进一步改进:

public string IntToString(int a, int radix)
{
    var chars = "0123456789ABCDEF".ToCharArray();
    var str = new char[32]; // maximum number of chars in any base
    var i = str.Length;
    bool isNegative = (a < 0);
    if (a <= 0) // handles 0 and int.MinValue special cases
    {
        str[--i] = chars[-(a % radix)];
        a = -(a / radix);
    }

    while (a != 0)
    {
        str[--i] = chars[a % radix];
        a /= radix;
    }

    if (isNegative)
    {
        str[--i] = '-';
    }

    return new string(str, i, str.Length - i);
}

8
我正准备写一些真的很下流的东西,但这个比较好。 - Serberuss
7
回答不错。但是有个小bug:在int.MinValue处会崩溃。 - Daniel
1
@Daniel 它在 checked 上下文中崩溃。在 unchecked 上下文中,它只打印“-”。 - Servy
3
字符串拼接不会自动调用 ToString() 方法吗? - Kevin DiTraglia
1
这个答案还是不行,请查看这里的答案一,了解为什么不行:https://dev59.com/N1DTa4cB1Zd3GeqPKp1i - DotNetRussell
显示剩余16条评论

22
这是我一直使用的解决方案:
    public static string numberBaseChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static string IntToStringWithBase(int n, int b) {
        return IntToStringWithBase(n, b, 1);
    }

    public static string IntToStringWithBase(int n, int b, int minDigits) {
        if (minDigits < 1) minDigits = 1;
        if (n == 0) return new string('0', minDigits);
        string s = "";
        if ((b < 2) || (b > numberBaseChars.Length)) return s;
        bool neg = false;
        if ((b == 10) && (n < 0)) { neg = true; n = -n; }
        uint N = (uint)n;
        uint B = (uint)b;
        while ((N > 0) | (minDigits-- > 0)) {
            s = numberBaseChars[(int)(N % B)] + s;
            N /= B;
        }
        if (neg) s = "-" + s;
        return s;
    }

这看起来相当复杂,但具有以下功能:

  • 支持基数2到36
  • 处理负值
  • 可选总位数

9
等一下,你真的有这个技能的实际应用吗? - John Buchanan
11
@JohnBuchanan:没问题!一个例子是:我们有具有唯一值的ID芯片。我们将这些值作为字符串使用基数36进行处理,始终为8个字符长度:0016I2WM。据我所知,这是使用任何.ToString() 都无法实现的。是否有更好的方法? - joe

10

我并不确定字符串连接运算符+会调用ToString方法,但是如果这是真的,你可以通过以下方式避免这两个问题:

if (a == 0) return "0";   

/* Negative maxint doesn't have a corresponding positive value, so handle it
 * as a special case. Thanks to @Daniel for pointing this out.
 */
if (a == 0x80000000) return "-2147483648";

List<char> l = new List<char>();
bool negative = false;

if (a < 0) 
{
    negative = true;
    a *= -1;
}

while (a > 0)
{
    l.Add('0' + (char)(a % 10));
    a /= 10;
}

if (negative) l.Add('-');

l.Reverse();

return new String(l.ToArray());

3
+ 运算符会调用其中一个操作数为 object 类型的重载的 ToString 方法。当两个操作数都为字符串类型时,它不会这样做。 - Servy
@Servy 好的,这就是我想的。谢谢你澄清。 - lc.

5
整数从最低位到最高位进行处理。使用模10(%10)计算单个数字,然后将其加到字符值'0'上。这会导致其中一个字符“0”,“1”......,“9”。
因为数字必须按照它们被处理的相反顺序(最高位数字到最低位数字)呈现,所以将数字推送到堆栈中。这样做比重复将数字前置到字符串中更有效率,但由于数字数量相当低,因此您需要执行基准测试才能确定。
处理非正数需要进行一些额外的处理。
public string IntToString(int a) {
  if (a == 0)
    return "0";
  if (a == int.MinValue)
    return "-2147483648";
  var isNegative = false;
  if (a < 0) {
    a = -a;
    isNegative = true;
  }
  var stack = new Stack<char>();
  while (a != 0) {
    var c = a%10 + '0';
    stack.Push((char) c);
    a /= 10;
  }
  if (isNegative)
    stack.Push('-');
  return new string(stack.ToArray());
}

我的第一个版本使用了StringBuilder从字符数组创建字符串,但是从StringBuilder获取字符串需要调用名为ToString的方法。显然,这个方法不会进行任何int到string的转换,而这正是我所关心的问题。
但是为了证明你可以在不调用ToString的情况下创建字符串,我已经切换到使用string构造函数,我认为与使用StringBuilder相比,这种方法也更有效率。
如果禁止使用任何形式的ToString,则不能使用字符串连接,如string.Concat文档中所述:

该方法通过调用arg0和arg1的无参数ToString方法来连接arg0和arg1;它不添加任何分隔符。

因此,执行s += '1'将调用'1'.ToString()。但对我来说,这并不重要。重要的是如何将int转换为string。

在使用 StringBuilder 时使用 ToString 是否违反规则?另一种选择是仅使用 string += string。 - Gray
我想应该是这样,因为StringBuilder是框架的一部分。不过我不是原帖作者 :) - lc.
1
我现在已经停止使用 ToString 来满足评论者的要求,并提供了一个不使用 ToString 的答案,因为字符串连接在幕后执行。 - Martin Liversage

4
你可以像这样将任何数字转换为字符
byte = (char)(byte)(digit+48)

神奇的数字48是字符0的ASCII值,它们在ASCII表中是连续的,因此您可以将数字相加以获得对应的ASCII表中的值。 并且您可以使用取余运算符%迭代地获取整数中的数字。 借鉴pswg的一般结构,您会得到

public string IntToString(int a) {
  var str = string.Empty;
    bool isNegative = false;
    if (a < 0) {
        isNegative = true;
        a = -a;
    }

    do {
        str = (char)(byte)((a % 10) + 48) + str;
        a /= 10;
    } while(a > 0);

    return isNegative ? '-' + str : str
}

4

为了缩短版本并使用 Math.DivRem

string IntToString(int a)
{
    if (a == int.MinValue)
        return "-2147483648";
    if (a < 0)
        return "-" + IntToString(-a);
    if (a == 0)
        return "0";
    var s = "";
    do
    {
        int r;
        a = Math.DivRem(a, 10, out r);
        s = new string((char)(r + (int)'0'), 1) + s;
    }
    while (a > 0);
    return s;
}

使用new string(..., 1)构造函数只是为了满足OP的要求,即不对任何内容调用ToString函数。

3

以下是我对此的看法,使用迭代和递归进行运行时间分析。

public static class IntegerToString
{
    static char[] d = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();

    public static string Iteration(int num, int radix = 10)
    {
        if (num == 0) return "0";
        if (num < 0) return "-" + Iteration(Math.Abs(num));
        var r = new List<char>();
        while (num > 0)
        {
            r.Insert(0, d[num % radix]);
            num /= radix;
        }
        return new string(r.ToArray());
    }

    public static string Recursion(int num, int radix = 10)
    {
        if (num == 0) return "0";
        if (num < 0) return "-" + Recursion(Math.Abs(num));
        return (num > radix - 1 ? Recursion(num / radix) : "") + d[num % radix];
    }
}

要点

  • 支持2到36进制转换(注意:你必须确保输入的进制是正确的,因为没有异常处理)
  • 递归方法只有3行代码!(类似代码高尔夫)

分析

下面是两种方法与我的电脑上标准 ToString() 方法的运行时间对比的分析。

50 runs of 100000 items per set

Running Time:
Iteration: 00:00:02.3459591 (00:00:00.0469191 avg)
Recursion: 00:00:02.1359731 (00:00:00.0427194 avg)
Standard : 00:00:00.4271253 (00:00:00.0085425 avg)

Ratios:
     | Iter | Rec  | Std
-----+------+------+-----
Iter | 1.00 | 0.91 | 0.18
Rec  | 1.10 | 1.00 | 0.20
Std  | 5.49 | 5.00 | 1.00

结果表明,迭代和递归方法的运行速度比标准的ToString()方法慢了5.49倍和5.00倍。

以下是我用于分析的代码:

class Program
{
    static void Main(string[] args)
    {
        var r = new Random();
        var sw = new System.Diagnostics.Stopwatch();

        var loop = new List<long>();
        var recr = new List<long>();
        var std = new List<long>();
        var setSize = 100000;
        var runs = 50;

        Console.WriteLine("{0} runs of {1} items per set", runs, setSize);

        for (int j = 0; j < runs; j++)
        {
            // create number set
            var numbers = Enumerable.Range(1, setSize)
                                    .Select(s => r.Next(int.MinValue,
                                                        int.MaxValue))
                                    .ToArray();

            // loop
            sw.Start();
            for (int i = 0; i < setSize; i++)
                IntegerToString.Iteration(numbers[i]);
            sw.Stop();
            loop.Add(sw.ElapsedTicks);

            // recursion
            sw.Reset();
            sw.Start();
            for (int i = 0; i < setSize; i++)
                IntegerToString.Recursion(numbers[i]);
            sw.Stop();
            recr.Add(sw.ElapsedTicks);

            // standard
            sw.Reset();
            sw.Start();
            for (int i = 0; i < setSize; i++)
                numbers[i].ToString();
            sw.Stop();
            std.Add(sw.ElapsedTicks);
        }

        Console.WriteLine();
        Console.WriteLine("Running Time:");
        Console.WriteLine("Iteration: {0} ({1} avg)", 
                          TimeSpan.FromTicks(loop.Sum()),
                          TimeSpan.FromTicks((int)loop.Average()));
        Console.WriteLine("Recursion: {0} ({1} avg)", 
                          TimeSpan.FromTicks(recr.Sum()),
                          TimeSpan.FromTicks((int)recr.Average()));
        Console.WriteLine("Standard : {0} ({1} avg)", 
                          TimeSpan.FromTicks(std.Sum()),
                          TimeSpan.FromTicks((int)std.Average()));

        double lSum = loop.Sum();
        double rSum = recr.Sum();
        double sSum = std.Sum();

        Console.WriteLine();
        Console.WriteLine("Ratios: \n" +
                          "     | Iter | Rec  | Std \n" +
                          "-----+------+------+-----");
        foreach (var div in new[] { new {n = "Iter", t = lSum}, 
                                    new {n = "Rec ", t = rSum},
                                    new {n = "Std ", t = sSum}})
            Console.WriteLine("{0} | {1:0.00} | {2:0.00} | {3:0.00}", 
                              div.n, lSum / div.t, rSum / div.t, sSum / div.t);

        Console.ReadLine();
    }

1
    public static string integerToString(int integerPassedIn)
    {
        if (integerPassedIn == 0) return "0";
        var negative = integerPassedIn < 0;
        var res = new List<char>();
        while(integerPassedIn != 0)
        {
           res.Add((char)(48 + Math.Abs(integerPassedIn % 10)));
           integerPassedIn /= 10;
        }
        res.Reverse();
        if (negative) res.Insert(0, '-');
        return new string(res.ToArray());
    }

0

递归:

    public static string integerToString(int integerPassedIn)
    {
        ICollection<char> res = new List<char>();
        IntToStringRecusion(integerPassedIn, res);
        if (integerPassedIn < 0) res.Add('-');
        return new string(res.Reverse().ToArray()).PadLeft(1,'0');
    }

    static void IntToStringRecusion(int integerPassedIn, ICollection<char> array)
    {
        if (integerPassedIn == 0) return;
        array.Add((char)(48 + Math.Abs(integerPassedIn % 10)));
        IntToStringRecusion(integerPassedIn / 10, array);
    }

-1

易如反掌:

string s = 5 + ""
//s = "5"

当您使用上述方法时,将调用ToString方法。 - DotNetRussell
@我这里没有看到"ToString()"被调用。 - person the human
当使用类似这样的连接操作时,C#会调用ToString方法。 - DotNetRussell

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