C# 格式化字符串的实现代码
1前言
如果你熟悉MicrosoftFoundationClasses(MFC)的CString,WindowsTemplateLibrary(WTL)的CString或者StandardTemplateLibrary(STL)的字符串类,那么你对String.Format方法肯定很熟悉。在C#中也经常使用这个方法来格式化字符串,比如下面这样:
intx=16; decimaly=3.57m; stringh=String.Format("item{0}sellsat{1:C}",x,y); Console.WriteLine(h);
在我的机器上,可以得到下面的输出:
item16sellsat¥3.57
也许你的机器上的输出和这个不太一样。这是正常的,本文稍后就会解释这个问题。
在我们日常使用中,更多的是使用Console.WriteLine方法来输出一个字符串。其实String.Format和Console.WriteLine有很多共同点。两个方法都有很多重载的格式并且采用无固定参数的对象数组作为最后一个参数。下面的两个语句会产生同样的输出。
Console.WriteLine("Hello{0}{1}{2}{3}{4}{5}{6}{7}{8}",123,45.67,true,'Q',4,5,6,7,'8'); stringu=String.Format("Hello{0}{1}{2}{3}{4}{5}{6}{7}{8}",123,45.67,true,'Q',4,5,6,7,'8'); Console.WriteLine(u);
输出如下:
Hello12345.67TrueQ45678
Hello12345.67TrueQ45678
2字符串格式
String.Format和WriteLine都遵守同样的格式化规则。格式化的格式如下:"{N[,M][:formatString]}",arg1,...argN,在这个格式中:
1)N是从0开始的整数,表示要格式化的参数的个数
2)M是一个可选的整数,表示格式化后的参数所占的宽度,如果M是负数,那么格式化后的值就是左对齐的,如果M是正数,那么格式化后的值是右对齐的
3)formatString是另外一个可选的参数,表示格式代码
argN表示要格式化的表达式,和N是对应的。
如果argN是空值,那么就用一个空字符串来代替。如果没有formatString,那么就用参数N对应的ToString方法来格式化。下面的语句会产生同样的输出:
publicclassTestConsoleApp { publicstaticvoidMain(string[]args) { Console.WriteLine(123); Console.WriteLine("{0}",123); Console.WriteLine("{0:D3}",123); } }
输出是:
123
123
123
也可以通过String.Format得到同样的输出。
strings=string.Format("123"); stringt=string.Format("{0}",123); stringu=string.Format("{0:D3}",123); Console.WriteLine(s); Console.WriteLine(t); Console.WriteLine(u);
因此有如下结论:
(,M)决定了格式化字符串的宽度和对齐方向
(:formatString)决定了如何格式化数据,比如用货币符号,科学计数法或者16进制。就像下面这样:
Console.WriteLine("{0,5}{1,5}",123,456);//右对齐 Console.WriteLine("{0,-5}{1,-5}",123,456);//左对齐
输出是
123 456
123 456
也可以合并这些表达式,先放一个逗号,再放一个冒号。就像这样:
Console.WriteLine("{0,-10:D6}{1,-10:D6}",123,456);
输出是:
000123 000456
我们可以用这种格式化特性来对齐我们的输出。
Console.WriteLine("\n{0,-10}{1,-3}","Name","Salary"); Console.WriteLine("----------------"); Console.WriteLine("{0,-10}{1,6}","Bill",123456); Console.WriteLine("{0,-10}{1,6}","Polly",7890);
输出是:
Name Salary
----------------
Bill 123456
Polly 7890
3格式化标识符
标准的数学格式字符串用于返回通常使用的字符串。它们通常象X0这样的格式。X是格式化标识符,0是精度标识符。格式标识符号共有9种,它们代表了大多数常用的数字格式。就像下表所示:
如果我们使用下面的表达方式,让我们看看会发生什么
publicclassFormatSpecApp { publicstaticvoidMain(string[]args) { inti=123456; Console.WriteLine("{0:C}",i);//¥123,456.00 Console.WriteLine("{0:D}",i);//123456 Console.WriteLine("{0:E}",i);//1.234560E+005 Console.WriteLine("{0:F}",i);//123456.00 Console.WriteLine("{0:G}",i);//123456 Console.WriteLine("{0:N}",i);//123,456.00 Console.WriteLine("{0:P}",i);//12,345,600.00% Console.WriteLine("{0:X}",i);//1E240 } }
精度控制标识控制了有效数字的个数或者十进制数小数的位数。
Console.WriteLine("{0:C5}",i);//¥123,456.00 Console.WriteLine("{0:D5}",i);//123456 Console.WriteLine("{0:E5}",i);//1.23456E+005 Console.WriteLine("{0:F5}",i);//123456.00000 Console.WriteLine("{0:G5}",i);//1.23456E5 Console.WriteLine("{0:N5}",i);//123,456.00000 Console.WriteLine("{0:P5}",i);//12,345,600.00000% Console.WriteLine("{0:X5}",i);//1E240
R(圆整)格式仅仅对浮点数有效。这个值首先会用通用格式来格式化。对于双精度数有15位精度,对于单精度数有7位精度。如果这个值可以被正确地解析回原始的数字,就会用通用格式符来格式化。如果不能解析回去的话,那么就会用17位精度来格式化双精度数,用9位精度来格式化单精度数。尽管我们可以在圆整标识符后面添加有效数字的位数,但是它会被忽略掉。
doubled=1.2345678901234567890; Console.WriteLine("Floating-Point:\t{0:F16}",d);//1.2345678901234600 Console.WriteLine("Roundtrip:\t{0:R16}",d);//1.2345678901234567
如果标准格式化标识符还不能满足你。你可以使用图形化格式字符串来创建定制的字符串输出。图形化格式化使用占位符来表示最小位数,
最大位数,定位符号,负号的外观以及其它数字符号的外观。就像下表所示
E-0
e+0
e-0
"ABC"
看下面的例子:
doublei=123456.42; Console.WriteLine(); Console.WriteLine("{0:000000.00}",i);//123456.42 Console.WriteLine("{0:00.00000000e+0}",i);//12.34564200e+4 Console.WriteLine("{0:0,.}",i);//123 Console.WriteLine("{0:#0.000}",i);//123456.420 Console.WriteLine("{0:#0.000;(#0.000)}",i);//123456.420 Console.WriteLine("{0:#0.000;(#0.000);<zero>}",i);//123456.420 Console.WriteLine("{0:#%}",i);//12345642% i=-123456.42; Console.WriteLine(); Console.WriteLine("{0:000000.00}",i);//-123456.42 Console.WriteLine("{0:00.00000000e+0}",i);//-12.34564200e+4 Console.WriteLine("{0:0,.}",i);//-123 Console.WriteLine("{0:#0.000}",i);//-123456.420 Console.WriteLine("{0:#0.000;(#0.000)}",i);//(123456.420) Console.WriteLine("{0:#0;(#0);<zero>}",i);//(123456) Console.WriteLine("{0:#%}",i);//-12345642% i=0; Console.WriteLine(); Console.WriteLine("{0:0,.}",i);//0 Console.WriteLine("{0:#0}",i);//0 Console.WriteLine("{0:#0;(#0)}",i);//0 Console.WriteLine("{0:#0;(#0);<zero>}",i);//<zero> Console.WriteLine("{0:#%}",i);//%
4数字字符串的解析
所有的基础类型都有ToString方法,它是从object类型中继承过来的。所有的数值类型都有Parse方法,它用字符串为参数,并且返回相等的数值。比如
publicclassNumParsingApp { publicstaticvoidMain(string[]args) { inti=int.Parse("12345"); Console.WriteLine("i={0}",i); intj=Int32.Parse("12345"); Console.WriteLine("j={0}",j); doubled=Double.Parse("1.2345E+6"); Console.WriteLine("d={0:F}",d); strings=i.ToString(); Console.WriteLine("s={0}",s); } }
输出如下
i=12345
j=12345
d=1234500.00
s=12345
在缺省状况下,某些非数字字符是可以存在的。比如开头和结尾的空白。逗号和小数点,加号和减号,因此,下面的Parse语句是一样的
stringt="-1,234,567.890"; //doubleg=double.Parse(t);//和下面的代码干同样的事情 doubleg=double.Parse(t, NumberStyles.AllowLeadingSign| NumberStyles.AllowDecimalPoint| NumberStyles.AllowThousands| NumberStyles.AllowLeadingWhite| NumberStyles.AllowTrailingWhite); Console.WriteLine("g={0:F}",g);
输出都是这样
g=-1234567.89
注意到,如果你要使用NumberStyles,就要添加对System.Globalization的引用,然后就可以使用不同NumberStyles的组合或者其中的任意一种。如果你想兼容货币符号,就需要使用重载的Parse方法,它们采用了NumberFormatInfo对象作为一个参数,然后你可以设置NumberFormatInfo的CurrencySymbol属性来调用Parse方法,比如:
stringu="¥-1,234,567.890"; NumberFormatInfoni=newNumberFormatInfo(); ni.CurrencySymbol="¥"; doubleh=Double.Parse(u,NumberStyles.Any,ni); Console.WriteLine("h={0:F}",h);
上面的代码有如下输出
h=-1234567.89
除了NumberFormatInfo,还可以使用CultureInfo类。CultureInfo代表了某种特定的文化,包括文化的名字,书写的方式,日历的格式。对于某种特定文化的操作是非常普遍的情况,比如格式化日期和排序。文化的命名方式遵从RFC1766标准,使用<语言代码2>-<国家/地区码2>的方式,其中的<语言代码2>是两个小写的字母,它们来自ISO639-1;<国家/地区码2>是两个大写字母,它们来自ISO3166。比如,美国英语是“en-US"。英国英语是"en-GB"。特立尼达和多巴哥英语是"en-TT"。例如,我们可以创建一个美国英语的CultureInfo对象并且基于这种文化将数字转换成字符串。
intk=12345; CultureInfous=newCultureInfo("en-US"); stringv=k.ToString("c",us); Console.WriteLine(v);
输出是:
$12,345.00
要注意到,我们使用了重载的ToString方法,它把第一个格式化字符串当成第一个参数,将一个CultureInfo对象(执行了IFormatProvider对象)作为第二个参数。这儿有第二个例子,对于丹麦人来说:
CultureInfodk=newCultureInfo("da-DK"); stringw=k.ToString("c",dk); Console.WriteLine(w);
输出是:
kr12.345,00
5字符串和日期
一个日期对象有个叫Ticks的属性。它存储了自从公元1年的1月1号上午12点开始的,以100纳秒为间隔的时间。比如,Ticks值等于31241376000000000L表示公元100年,星期五,1月1号,上午12点这一时间。Ticks总是以100纳秒为间隔递增。
DateTime的值以存储在DateTimeFormatInfo实例里面的标准或者自定义的方式来表示。为了修改一个日期显示的方式,DateTimeFormatInfo实例必须要是可写的,以便我们写入自定义的格式并且存入属性中
usingSystem.Globalization; publicclassDatesApp { publicstaticvoidMain(string[]args) { DateTimedt=DateTime.Now; Console.WriteLine(dt); Console.WriteLine("date={0},time={1}\n", dt.Date,dt.TimeOfDay); } }
代码会产生下面的输出
23/06/200117:55:10
date=23/06/200100:00:00,time=17:55:10.3839296
下表列出了标准的格式字符串以及相关的DateTimeFormatInfo属性
DateTimeFormatInfo.InvariantInfo属性得到了默认的只读的DateTimeFormatInfo实例,它与文化无关。你可以创建自定义的模式。要注意到的是InvariantInfo不一定和本地的格式一样。Invariant等于美国格式。另外,如果你向DateTime.Format方法传递的第二个参数是null,DateTimeFormatInfo将会是默认的CurrentInfo。比如
Console.WriteLine(dt.ToString("d",dtfi)); Console.WriteLine(dt.ToString("d",null)); Console.WriteLine();
输出是
06/23/2001
23/06/2001
对比选择InvariantInfo和CurrentInfo的。
DateTimeFormatInfodtfi; Console.Write("[I]nvariantor[C]urrentInfo?:"); if(Console.Read()=='I') dtfi=DateTimeFormatInfo.InvariantInfo; else dtfi=DateTimeFormatInfo.CurrentInfo; DateTimeFormatInfodtfi=DateTimeFormatInfo.InvariantInfo; Console.WriteLine(dt.ToString("D",dtfi)); Console.WriteLine(dt.ToString("f",dtfi)); Console.WriteLine(dt.ToString("F",dtfi)); Console.WriteLine(dt.ToString("g",dtfi)); Console.WriteLine(dt.ToString("G",dtfi)); Console.WriteLine(dt.ToString("m",dtfi)); Console.WriteLine(dt.ToString("r",dtfi)); Console.WriteLine(dt.ToString("s",dtfi)); Console.WriteLine(dt.ToString("t",dtfi)); Console.WriteLine(dt.ToString("T",dtfi)); Console.WriteLine(dt.ToString("u",dtfi)); Console.WriteLine(dt.ToString("U",dtfi)); Console.WriteLine(dt.ToString("d",dtfi)); Console.WriteLine(dt.ToString("y",dtfi)); Console.WriteLine(dt.ToString("dd-MMM-yy",dtfi));
输出是
[I]nvariantor[C]urrentInfo?:I
01/03/2002
03/01/2002
Thursday,03January2002
Thursday,03January200212:55
Thursday,03January200212:55:03
01/03/200212:55
01/03/200212:55:03
January03
Thu,03Jan200212:55:03GMT
2002-01-03T12:55:03
12:55
12:55:03
2002-01-0312:55:03Z
Thursday,03January200212:55:03
01/03/2002
2002January
03-Jan-02
[I]nvariantor[C]urrentInfo?:C
03/01/2002
03/01/2002
03January2002
03January200212:55
03January200212:55:47
03/01/200212:55
03/01/200212:55:47
03January
Thu,03Jan200212:55:47GMT
2002-01-03T12:55:47
12:55
12:55:47
2002-01-0312:55:47Z
03January200212:55:47
03/01/2002
January2002
03-Jan-02
/******************************************************************************************
*【Author】:flyingbread
*【Date】:2007年1月18日
*【Notice】:
*1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
*2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
*3、本声明为文章一部分,转载和引用必须包括在原文中。
******************************************************************************************/