字符与字符串类型的实践
分类索引:C# 语言和运行时剖析--前言
字符类型
概念:
- 定义:在.NET Framwork中,字符(Char)类型总是表示成16位Unicode的编码值。在存储中间中总是占用两个字节(Byte)。
- 说明:在实践中,直接操作字符类型的场景不是很多,大多数时候字符类型会作为字符串中的一个Item来使用。但在需要控制多国语言或者涉及到需要使用不同的编码方式序列化字节时,还是需要掌握Unicode和GB(简体中文)编码方式中的一些基本知识的。特别是在网络传输中使用得较多。
- 字符类型可以在需要的情况下与其他类型进行转化,常见有三种转化方式,如下:
Char c; Int32 n; // 强制转型,可以使用unchecked/checked关键字修饰 c = (Char)65; Console.WriteLine(c); // Displays "A" n = (Int32)c; Console.WriteLine(n); // Displays "65" c = unchecked((Char)(65536 + 65)); Console.WriteLine(c); // Displays "A" // 使用 Convert 辅助类型,在方法中会强制检查是否数据溢出 c = Convert.ToChar(65); Console.WriteLine(c); // Displays "A" n = Convert.ToInt32(c); Console.WriteLine(n); // Displays "65" // 捕获溢出异常的演示 try { c = Convert.ToChar(70000); // Too big for 16-bits Console.WriteLine(c); // Doesn't execute } catch (OverflowException) { Console.WriteLine("Can't convert 70000 to a Char."); } // 使用 IConvertible 接口的转型,会发生装箱,不建议使用 c = ((IConvertible)65).ToChar(null); Console.WriteLine(c); // Displays "A" n = ((IConvertible)c).ToInt32(null); Console.WriteLine(n); // Displays "65"
- 字符类型中定义的其他常用方法。
// // 摘要: // 指示指定的 Unicode 字符是否属于控制字符类别。 public static bool IsControl(char c); // // 摘要: // 指示指定的 Unicode 字符是否属于十进制数字类别。 public static bool IsDigit(char c); // 返回结果: // 如果 c 是字母,则为 true;否则为 false。 public static bool IsLetter(char c); // 返回结果: // 如果 c 是字母或十进制数字,则为 true;否则为 false。 public static bool IsLetterOrDigit(char c); // 返回结果: // 如果 c 是小写字母,则为 true;否则,为 false。 public static bool IsLower(char c); // 返回结果: // 如果 c 是数字,则为 true,否则为 false。 public static bool IsNumber(char c); // 返回结果: // 如果 c 是标点符号,则为 true;否则,为 false。 public static bool IsPunctuation(char c); // 返回结果: // 如果 c 是分隔符,则为 true;否则,为 false。 public static bool IsSeparator(char c); // 返回结果: // 如果 c 是符号字符,则为 true;否则为 false。 public static bool IsSymbol(char c); // 返回结果: // 如果 c 是大写字母,则为 true;否则,为 false。 public static bool IsUpper(char c); // 返回结果: // 如果 c 是空白,则为 true;否则,为 false。 public static bool IsWhiteSpace(char c); // // 摘要: // 将指定字符串的值转换为它的等效 Unicode 字符。 public static char Parse(string s); // // 摘要: // 将 Unicode 字符的值转换为它的小写等效项。 public static char ToLower(char c); // // 摘要: // 将此实例的值转换为其等效的字符串表示形式。 public override string ToString(); // // 摘要: // 将指定的 Unicode 字符转换为它的等效字符串表示形式。 public static string ToString(char c); // // 摘要: // 将 Unicode 字符的值转换为它的大写等效项。 public static char ToUpper(char c); // // 摘要: // 将指定字符串的值转换为它的等效 Unicode 字符。一个指示转换是否成功的返回代码。 // 返回结果: // 如果 s 参数成功转换,则为 true;否则为 false。 public static bool TryParse(string s, out char result);
字符串类型
一. 概念:
- 定义:一个String类型代表一个不可变的顺序字符集合。
- 说明:
- 字符串是实践中使用最多的类型之一。
- 字符串直接派生自System.Object,属于引用类型,但跟普通的引用类型有所区别,经常表现出值类型的特征。例如在本系列中<参数的运用>一节中<参数的传递>段落中有所讲述。
- 字符串类型与区域设置密切相关,在多国语言环境下的编程时,要密切注意使用不同的区域设置,可能会造成String类型中操作的不同结果。
- 混淆点:
- 字符串在.NET中是属于不可变的类型,通常我们认为改变了字符串的值是因为对象指向了一个不同的堆内存地址。
- 为减少字符串对堆内存的占用,在CLR中有一个字符串留用机制,内存中会保留一个字符串数据的哈希表,每次进行字符串操作,生成新的字符串时,都会在该哈希表中检查此字符串是否已经存在,如果存在,则返回对已有字符串的引用,而不用创建额外的内存空间。
二. 构造字符串:
- 字符串构造方式:不能使用new()实例构造器构造字符串,一般只能使用直接赋值的方法构造字符串。
- 高效率构造方式:使用System.Text.StringBuilder类型构造字符串。
- 优势:使用链式操作或者对同一个字符串对象的多步操作时不会生成若干个string对象的拷贝,而都是在同一个StringBuilder的实例中进行。
- 常用成员列举:
成员名称 | 成员类型 | 说明 |
Capacity | 可读/写属性 | 获取或设置字符数据的长度(容量)。 |
Length | 可读/写属性 | 获取或设置“字符串”中的字符数 |
ToString | 方法 | 将StringBuilder中容纳的字符数组转化为一个字符串 |
Chars | 索引器属性 | 通过[]索引访问对应的字符 |
Clear | 方法 | 清除StringBuilder对象的内容 |
Append | 方法 | 在字符数组末尾最佳单独一个对象,如果Capacity容量不够,会自动扩充 |
Insert | 方法 | 在字符数组中指定的位置插入一个对象 |
AppendFormat | 方法 | 在字符数组末尾插入指定的带格式的若干个对象 |
AppendLine | 方法 | 在字符数组的末尾追加一个行终止符或者一个带行终止符的字符串 |
Replace | 方法 | 将字符替换为指定的字符,或将字符串替换为指定的字符串 |
Remove | 方法 | 从字符数组中删除指定范围的字符 |
CopyTo | 方法 | 将StringBuilder对象中的字符内容的一个子集复制到一个Char数组中 |
三.比较字符串:
- 一般比较方式:使用String类型定义的静态方法:Compare, StartWith, EndWith等。
- 特殊比较方式:使用System.Globalization.CompareInfo类型或者定制该类型的衍生类型来进行字符串的比较。此类型对象可以通过区域性类型System.Globalization.CultureInfo访问。
- 实践中注意:在使用多国语言混合处理时,应该注意使用不同的区域性System.Globalization.CultureInfo所造成的结果可能大大不同,这个时候应该尽量使用上述的第二种方法指定区域性来比较字符串。示例如下:
internal static class ComparingStringForEquality { public static void Go() { String s1 = "Strasse"; String s2 = "Straße"; Boolean eq; // CompareOrdinal returns nonzero. eq = String.Compare(s1, s2, StringComparison.Ordinal) == 0; Console.WriteLine("Ordinal comparison: '{0}' {2} '{1}'", s1, s2, eq ? "==" : "!="); // Compare Strings appropriately for people // who speak German (de) in Germany (DE) CultureInfo ci = new CultureInfo("de-DE"); // Compare returns zero. eq = String.Compare(s1, s2, true, ci) == 0; Console.WriteLine("Cultural comparison: '{0}' {2} '{1}'", s1, s2, eq ? "==" : "!="); } } internal static class ComparingStringsForSorting { public static void Go() { String output = String.Empty; String[] symbol = new String[] { "<", "=", ">" }; Int32 x; CultureInfo ci; // The code below demonstrates how strings compare // differently for different cultures. String s1 = "coté"; String s2 = "côte"; // Sorting strings for French in France. ci = new CultureInfo("fr-FR"); x = Math.Sign(ci.CompareInfo.Compare(s1, s2)); output += String.Format("{0} Compare: {1} {3} {2}", ci.Name, s1, s2, symbol[x + 1]); output += Environment.NewLine; // Sorting strings for Japanese in Japan. ci = new CultureInfo("ja-JP"); x = Math.Sign(ci.CompareInfo.Compare(s1, s2)); output += String.Format("{0} Compare: {1} {3} {2}", ci.Name, s1, s2, symbol[x + 1]); output += Environment.NewLine; // Sorting strings for the thread's culture ci = Thread.CurrentThread.CurrentCulture; x = Math.Sign(ci.CompareInfo.Compare(s1, s2)); output += String.Format("{0} Compare: {1} {3} {2}", ci.Name, s1, s2, symbol[x + 1]); output += Environment.NewLine + Environment.NewLine; // The code below demonstrates how to use CompareInfo.Compare's // advanced options with 2 Japanese strings. One string represents // the word "shinkansen" (the name for the Japanese high-speed // train) in hiragana (one subtype of Japanese writing), and the // other represents the same word in katakana (another subtype of // Japanese writing). s1 = "しんかんせん"; // ("\u3057\u3093\u304B\u3093\u305b\u3093") s2 = "シンカンセン"; // ("\u30b7\u30f3\u30ab\u30f3\u30bb\u30f3") // Here is the result of a default comparison ci = new CultureInfo("ja-JP"); x = Math.Sign(String.Compare(s1, s2, true, ci)); output += String.Format("Simple {0} Compare: {1} {3} {2}", ci.Name, s1, s2, symbol[x + 1]); output += Environment.NewLine; // Here is the result of a comparison that ignores // kana type (a type of Japanese writing) CompareInfo compareInfo = CompareInfo.GetCompareInfo("ja-JP"); x = Math.Sign(compareInfo.Compare(s1, s2, CompareOptions.IgnoreKanaType)); output += String.Format("Advanced {0} Compare: {1} {3} {2}", ci.Name, s1, s2, symbol[x + 1]); MessageBox.Show(output, "Comparing Strings For Sorting"); } }
四.检查字符串中的字符和文本元素:
- 方法:使用String类型中定义的Contains, IndexOf, LastIndexOf, IndexOfAny, ToCharArray等方法。
- 实践中注意: 与比较字符串差不多,在处理多国语言时需要注意System.Globalization.CultureInfo的设定,此处也有一个辅助类型System.Globalization.StringInfo提供,使用这个类型可以保证正确的处理多国语言背景下的文本元素。
对象与字符串的相互转化
一.获取对象的字符串表示:ToString
- 来源与重载:ToString方法的源定义来源于System.Object,所以在C#中的任何类型都会自动继承该方法。但在FCL中常用的一些类型都对该方法做了重载,因为继承的ToString方法将会返回对象所属类型的全名,这在实践中基本没有意义。
- 指定具体的格式化器:
- 在ToString方法中,需要指定实现IFormatProvider接口格式化器。
- 常见的格式化器包括:CultureInfo,NumberFormat, DateTimeFormat等。
- 如果不指定具体的格式化器,ToString方法将会读取当前线程关联的CurrentCulture作为默认的格式化器。
示例如下:
internal static class Formatting { public static void Go() { Decimal price = 123.54M; String s = price.ToString("C", new CultureInfo("vi-VN")); MessageBox.Show(s); s = price.ToString("C", CultureInfo.InvariantCulture); MessageBox.Show(s); } }
- 将多个对象格式化为一个字符串
使用String.Format方法可以同时格式化多个对象为字符串。
示例如下(使用定制格式化器):
internal static class CustomFormatter { public static void Go() { StringBuilder sb = new StringBuilder(); sb.AppendFormat(new BoldInt32s(), "{0} {1} {2:M}", "Jeff", 123, DateTime.Now); Console.WriteLine(sb); } private sealed class BoldInt32s : IFormatProvider, ICustomFormatter { public Object GetFormat(Type formatType) { if (formatType == typeof(ICustomFormatter)) return this; return Thread.CurrentThread.CurrentCulture.GetFormat(formatType); } public String Format(String format, Object arg, IFormatProvider formatProvider) { String s; IFormattable formattable = arg as IFormattable; if (formattable == null) s = arg.ToString(); else s = formattable.ToString(format, formatProvider); if (arg.GetType() == typeof(Int32)) return "<B>" + s + "</B>"; return s; } } }
- 提供定制格式化器:自定义实现接口IFormatProvider的格式化器。
二.解析字符串来获取对象:Parse
- 定义: FCL中的一些常用类型,例如所有的数据类型都提供的Parse方法,解析字符串获取对象。
- 实践注意:在实践中尽量使用TryParse方法,因为类型转换的原因,使用Parse解析字符串时可能会出现异常,使用TryParse方法可以避免这个异常发生。