除了向你展示如何使用.NET框架中的字符串外,本文还将向你介绍正规表达式。正规表达式是格式代码,不仅允许你验证一个特定字符串匹配一个给定的格式,而且你还可以使用正规表达式来从任何其它可能被认为是自由格式的文本中提取有意义的信息,例如从用户输入中提取第一个名字,或从一个数字输入中提取代码,或从一个URL中提取服务器名。
一、 使用字符串
使用字符串是创建高质量应用程序的一个必要的技巧。即使你在处理数字或图像数据,终端用户也需要上下文反馈。本文将向你介绍.NET字符串,如何格式它们,操作它们和比较它们,及其它有用的操作。
(一) .NET字符串简介
在.NET框架和通用语言运行时刻(CLR)以前,开发者总是花费大量时间处理字符串。一个字符串例程可重用库几乎是每一个C和C++程序员的工具箱中的一部分。编写在不同程序语言之间交换字符串数据的代码也是相当困难的。例如,Pascal把字符串存储为一个内存字符数组,其中,该数组的第一个元素指示字符串的长度;而C把字符串存储为一个具有可变长度的字符内存数组,字符串的末端加上一个ASCII null字符(在C中以"/0"表示)。
在.NET框架中,字符串以常量方式存储。这意味着,当你用C#(或任何其它.NET语言)创建一个字符串时,该字符串以一种固定大小存储在内存以便CLR运行更快些。结果是,当你实现例如连接字符串或修改一个字符串中的单个字符时,CLR实际上是创建你的字符串的多个副本。
C#中的字符串与其它值类型例如整数或浮点数声明方式相同,见下面的例子:
string x = "Hello World"; string y; string z = x; |
(二) 格式化字符串
当使用字符串时最常见的一项任务是格式化字符串。当向用户显示信息时,你经常显示如日期,时间,数字值,十进制值,货币值,甚至象十六进制数字这样的内容。C#字符串都能够显示这些类型的信息,甚至更多。另外一个强有力的特征是,当你使用该标准格式化工具时,该格式化的输出具有地区感知特征。例如,如果你以短格式显示一个英格兰用户的当前日期,那么对于一个美国用户来说,当前日期的短格式将以不同形式显示。
为了创建一个格式化的字符串,你仅需要调用string类的Format方法,并且传递给它一个格式字符串,如下列代码所显示的:
string formatted = string.Format("The value is {0}", value); |
在此,{0}占位符指示一个值应该被插入的位置。除了指定一个值应该被插入的位置外,你还可以指定该值的格式。
其它数据类型还支持经由定制格式修饰符转换成字符串,例如,DateTime数据类型,通过使用如下方式,它能够产生一种定制格式的输出:
DateTime.ToString("format specifiers"); |
表格1列举了用于格式化日期,时间,数字值等数据的一些最常用的格式字符串。
表格1.定制DateTime格式修饰符
修饰符 | 描述 |
d | 显示某月中的这一天。 |
dd | 显示某月中的这一天,其中,小于10的值之前加上一个0。 |
ddd | 显示一个星期中某一天的三字母缩写名。 |
dddd(+) | 显示给定的DateTime值中星期中的一天的完整名。 |
f(+) | 显示秒值的最重要的x位数。在f中格式修饰符位数越多,该数字越重要。这是个总秒数,而不是从上个分钟以来经过的秒数。 |
F(+) | 与f(+)相同,除了不显示末尾的零外。 |
g | 显示一个给定的DateTime中的时代,例如,"A.D"。 |
h | 显示小时,范围为:1~12。 |
hh | 显示小时,范围为:1~12,其中,小于10的值之前加上一个0。 |
H | 显示小时范围为:0~23。 |
HH | 显示小时范围为:0~23,其中,小于10的值之前加上一个0。 |
m | 显示分钟,范围为0~59。 |
mm | 显示分钟,范围为0~59,其中,小于10的值之前加上一个0。 |
M | 显示月份,范围为1~12。 |
MM | 显示月份,范围为1~12,其中,小于10的值之前加上一个0。 |
MMM | 显示月份的三字符缩略名。 |
MMMM | 显示月份的完整名字。 |
s | 显示秒数范围为:0~59。 |
ss(+) | 显示秒数范围为:0~59,其中,小于10的值之前加上一个0。 |
t | 显示给定的时间中AM/PM指示器中的第一个字符。 |
tt(+) | 显示给定的时间中完整的AM/PM指示器。 |
y/yy/yyyy | 显示给定的时间中的年份。 |
z/zz/zzz(+) | 显示给定的时间中的时区偏移量。 |
让我们观察下列代码,它展示使用字符串格式修饰符创建定制格式的日期和时间字符串:
DateTime dt = DateTime.Now; Console.WriteLine(string.Format("Default format: {0}", dt.ToString())); Console.WriteLine(dt.ToString("dddd dd MMMM, yyyy g")); Console.WriteLine(string.Format("Custom Format 1: {0:MM/dd/yy hh:mm:sstt}", dt)); Console.WriteLine(string.Format("Custom Format 2: {0:hh:mm:sstt G/MT zz}", dt)); |
下面是前面代码的输出:
Default format: 9/24/2005 12:59:49 PM Saturday 24 September, 2005 A.D. Custom Format 1: 09/24/05 12:59:49PM Custom Format 2: 12:59:49PM GMT -06 |
你还可以提供针对数字值的定制格式修饰符。表格2描述了适用于数字值的定制格式修饰符。
表格2.数字定制格式修饰符
修饰符 | 描述 |
0 | 零占位符。 |
# | 数字占位符。如果给定的值中在#修饰符指示的位置有一个数字,那么该数字将以格式化输出显示。 |
. | 十进制点。 |
, | 千分位分隔符。 |
% | 百分比修饰符。被格式化的值在包括到格式化的输出前将乘以100。 |
E0/E+0/e/e+0/e-0/E | 科学标志。 |
"XX"或"XX" | 代表格式的字符串。这些被包含在格式化的输出中,而不翻译其相对位置。 |
; | 用于条件格式化负数,零和正值的节分隔符。 |
如果定义多个格式节,那么你可以更精确地控制数字的格式化:
· 两个节-如果你有两个格式化节,则第一节应用于所有正数(包括0)值。第二节应用于负数值,当你想把负数值包括在括号中(就象中许多财务软件包中一样),这是十分方便的。
· 三个节-如果你有三个格式化节,则第一个节应用于所有正数(不包括0)值。第二节应用于负数值,第三节应用于零。
下列代码显示如何使用定制数字格式修饰符。
double dVal = 59.99; double dNeg = -569.99; double zeroVal = 0.0; double pct = 0.23; string formatString = "{0:$#,###0.00;($#,###0.00);nuttin}"; Console.WriteLine(string.Format(formatString, dVal)); Console.WriteLine(string.Format(formatString, dNeg)); Console.WriteLine(string.Format(formatString, zeroVal)); Console.WriteLine(pct.ToString("00%")); |
前面的代码将产生如下所示的输出结果:
$59.99
($569.99)
nuttin
23%
(三) 操作和比较字符串
除了显示包含各种格式化数据的字符串外,其它普通与字符串有关的任务就是字符串操作和比较。要记住的一个重要的事情是,字符串实际上是.NET框架基类库中的一个类。因为它是一个类,所以实际上,你可以调用一个字符串的方法,就象你可以调用任何其它类上的方法一样。
你可以在字符串常数或字符串变量调用这些方法,见下列代码:
int x = string.Length(); int y = "Hello World".Length(); |
表格3简短列举了一些你可以使用于字符串的最常用的方法以便获得该字符串的信息或操作它。
表格3.常用的字符串实例方法
方法 | 描述 |
CompareTo | 把这个字符串实例与其它字符串实例比较。 |
Contains | 返回一布尔值,指示是否当前字符串实例包含给定的子串。 |
CopyTo | 从字符串实例中把一个子串复制到一个字符数组的特定位置。 |
EndsWith | 返回一布尔值,指示是否字符串以一个给定的子串结束。 |
Equals | 指示是否该字符串等于另一个字符串。你还可以使用'=='操作符来代替。 |
IndexOf | 返回一个子串在字符串实例中的索引。 |
IndexOfAny | 返回一个字符串实例中在子串内的任何字符的第一次索引的出现。 |
PadLeft | 使用特定数目的空格或其它Unicode字符来填充字符串,特别适用于字符串右对齐。 |
PadRight | 把一组特定的空格字符或其它Unicode字符添加到字符串的最后,创建一个字符串右对齐效果。 |
Remove | 从字符串中删除给定数目的字符。 |
Replace | 使用特定的代替内容来代替一个给定的字符或字符串在字符串实例中的所有出现。 |
Split | 使用特定的字符作为分割点,把当前字符串分解成一个字符串数组。 |
StartsWith | 返回一个布尔值,指示是否该字符串实例以一个特定的字符串开始。 |
SubString | 给定起始点和长度的情况下,返回字符串的特定部分。 |
ToCharArray | 把字符串转换成一个字符数组。 |
ToLower | 把字符串全部转换成小写字符。 |
ToUpper | 把字符串全部转换成大写字符。 |
Trim | 从一个字符串的开始和结束位置,删除一组给定字符的所有出现。 |
TrimStart | 实现Trim功能,但仅从字符串的开始位置。 |
TrimEnd | 实现Trim功能,但仅从字符串的结束位置。 |
下列代码展示了你可以使用上面的相应函数来实现字符串查询和操作等:
string sourceString = "Mary Had a Little Lamb"; string sourceString2 = " Mary Had a Little Lamb "; Console.WriteLine(sourceString.ToLower()); Console.WriteLine(string.Format("The string '{0}' is {1} chars long", sourceString,sourceString.Length)); Console.WriteLine(string.Format("Fourth word in sentence is : {0}", sourceString.Split(' ')[3])); Console.WriteLine(sourceString2.Trim()); Console.WriteLine("Two strings equal? " + (sourceString == sourceString2.Trim())); |
前面的代码输出如下所示结果:
mary had a little lamb The string 'Mary Had 一个 Little Lamb' is 22 chars long. Fourth word in sentence is : Little Mary Had a Little Lamb Two strings equal? True |
(四) StringBuilder入门
如前面所提及,字符串是常量。这意味着,当你把两个字符串连接成一个新的字符串时,有一段时间CLR在内存中有三个字符串。因此,例如,当你连接实现如下代码所示连接时:
string a = "Hello"; string b = "World"; string c = 一个 + " " + c; |
实际上,在内存共有四个字符串,包括空格。为了缓和这个字符串连接性能问题并且提供给你一个工具使连接更容易些,.NET框架中提供了一个类StringBuilder。
通过使用StringBuilder动态地创建可变长度的字符串,你克服了CLR字符串中常量字符串这一事实;而且,这样以来,该代码变得更具可读性。下列代码展示了StringBuilder的使用:
StringBuilder sb = new StringBuilder(); sb.Append("Greetings!/n"); formatString = "{0:$#,###0.00;($#,###0.00);Zero}"; dVal = 129.99; sb.AppendFormat(formatString, dVal); sb.Append("/nThis is a big concatenated string."); Console.WriteLine(sb.ToString()); |
前面的代码输出如下所示结果:
Greetings! $129.99 This is a big concatenated string. |
注意,前面代码中的"/n"把一个换行符字符插入到字符串中。
二、 使用正规表达式
正规表达式允许快速有效地处理文本。被处理的文本小到一个电子邮件地址,大到一个多行的输入框内容。正规表达式的使用不仅允许你使用一个定义模式来校验文本,而且还允许你从匹配一个给定模式的文本中提取数据。
你可以把一个正规表达式当作是一种特别强有力的通配符。当我们看到象"SAMS*"这样的表达式时,我们都会熟悉通配符,任何以单词SAMS开头的内容都是一个匹配的表达式。正规表达式能为你提供远远超过通配符的强有力的控制功能。
本节先向你简短地介绍一下.NET框架中提供的支持使用正规表达式的类。有关正规表达式的更多信息,你可以参考《正规表达式快速参考手册》或《精通正规表达式》的第二版。这些书将提供给你需要的信息以便创建你自己的正规表达式,而且还提供了常用正规表达式的一组列表。至于正规表达式本身已经超出本文的讨论范围。
(一) 校验输入
正规表达式的一种最常用的场所是用于使用一些预先定义的格式(例如,强制建立的规则用于确保口令中包含特定的使其很难被拆断的字符)校验用户输入。这些规则被典型地定义为正规表达式。正规表达式也常用于校验简单的输入,例如电子邮件地址和电话号码。
.NET框架提供的操作正规表达式的一个关键类是RegEx类。这个类提供一个静态的方法IsMatch,它返回一个布尔值指示是否指定的输入字符串匹配一个给定的正规表达式。
在下列代码中,使用一个普通正规表达式来测试电子邮件地址的有效性:
string emailPattern = @"^([/w-/.]+)@((/[[0-9]{1,3}/.[0-9]{1,3}/.[0-9]{1,3}/.)|[ccc] (([/w-]+/.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(/]?)$"; Console.Write("Enter an e-mail address:"); string emailInput = Console.ReadLine(); bool match = Regex.IsMatch(emailInput, emailPattern); if (match) Console.WriteLine("E-mail address is valid."); else Console.WriteLine("Supplied input is not a valid e-mail address."); |
如果你搞不清楚这个正规表达式,别担心。电子邮件模式的基本思想是,它需要一些数字字母字符,后面跟着一个@符号,然后是一些字符组合,再后面跟着一个".",再往后至少跟着两个字符。你可以以不同的输入试验前面的代码来看一下你得到什么结果。即使你不理解该正规表达式本身,只要知道它们的存在,那么你就可以把它使用于你的应用程序中来校验输入。
(二) 从输入中提取数据
正规表达式的其它常见的用法是,根据表达式分析文本以及使用之来从用户输入中提取数据(称作组匹配)。
正规表达式中包括一个特征叫组。一个组允许你把一个命名标识放到该正规表达式的一个特定节中。当你调用Match()来针对模式比较输入数据时,其结果实际上把匹配分成一些组,允许你提取匹配每一个组的输入的部分。
例如,在前面的例子中,我们创建了一个username,它允许我们提取在一个电子邮件地址中位于@符号前的所有数据。然后,当执行一个匹配时,我们能够使用正规表达式的命名组从输入中提取该username。
下列代码显示怎样从一个用户在控制台输入的URL中提取协议名字和端口号。正规表达式的伟大在于,它们使用自己的语言;因此,它们不必依赖于C、C++、C#、VB.NET或任何其它语言。在下列代码中的正规表达式来自于一个MSDN例子:
string urlPattern = @"^(?<proto>/w+)://[^/]+?(?<port>:/d+)?/"; Console.WriteLine(); Console.Write("Enter a URL for data parsing: "); string url = Console.ReadLine(); Regex urlExpression = new Regex(urlPattern, RegexOptions.Compiled); Match urlMatch = urlExpression.Match(url); Console.WriteLine("The Protocol you entered was " + urlMatch.Groups["proto"].Value); Console.WriteLine("The Port Number you entered was " + urlMatch.Groups["port"].Value); |
当你使用不带有一个端口号的URL运行前面的代码时,你会注意到,你没有得到任何组值。这是因为,该输入根本不匹配正规表达式。当不存在匹配时,你显然无法从给定的组中提取有意义的数据。当你使用匹配该正规表达式的端口号的URL运行前面的代码时,你将得到如下列文本所示的输出结果:
Entera URL for data parsing: http://server.com:2100/home.aspx The Protocol you entered was http The Port Number you entered was :2100 |
三、 总结
在本文中,你已看到现在你有了自己的字符串例程库。借助于C#和.NET框架,字符串成为该基类库的一个本机组成部分,并且提供给你大量的工具方法用于实现字符串的比较,操作,格式化等操作。你还看到,StringBuilder类向你提供了一组易于使用的工具方法以便动态地构建字符串而不会带来本地字符串连接的性能损失。
最后,本文向你简短介绍了正规表达式的威力以及Regex类是如何把这些功能整合到一起的。通过阅读本文和试验相应的示例代码后,你应该熟悉一些字符串和正规表达式操作以使你的应用程序更为有力。