在C#中string
关键字的映射实际上是指向.NET基类System.String
。System.String
是一个功能非常强大且用途非常广泛的基类,但它不是.NET库中唯一与字符串相关的类。
System.String
类
System.String
是专门用于储存字符串,允许对字符串进行许多操作的类。由于这种数据类型非常重要,C#提供了它自己的关键字和相关语法,以便于用这个类来轻松处理字符串。
- 使用运算符重载来连接字符串:
string message = "Hello";
message += ",There";//returns "Hello,There"
- 允许使用索引:
string message="Hello";
char char4=message[4];//returns 'o',zero-indexed
Compare
,比较字符串的内容,考虑文化背景(区域),判断某些字符是否相等
var test=String.Compare("John", "Yang");
Console.WriteLine(test);
CompareOrdinal
,与Compare
一样,但不考虑文化背景
var test=String.CompareOrdinal("John", "Yang");
Console.WriteLine(test);
Concat
,把多个字符串实例合并为一个实例
var test=String.Concat("a","b","c");
Console.WriteLine(test);
CopyTo
:用于将指定数量的字符从给定的字符串索引复制到字符数组中的指定位置
public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count);
其中,sourceIndex是从字符串开始复制的位置索引,destination是复制到的数组,destinationIndex是复制到数组的索引,count是从字符串复制多少字符过去。
var charArray = new char[5];
charArray[0] = 'j';
var message = "Hello,World";
message.CopyTo(1,charArray,1,4);//从message字符串的索引位置1开始选取4个字符,复制到charArray数组,数组的索引位置起始点是索引1
Console.WriteLine(charArray);
Format
:格式化包含各种值的字符串IndexOf
:定位字符串中第一次出现某个给定字符串或字符的位置
string str = "ABCDDEABC";
Console.WriteLine(str.IndexOf("B"));
IndexOfAny
:定位字符串中第一次出现某个或一组字符的位置
string str = "ABCDDEABC";
Console.WriteLine(str.IndexOfAny(new char[2]{ 'D','C'}));
Insert
:把已给字符串实例插入到另一个字符串实例的指定索引处
string str = "ABCDDEABC";
string str1 = str.Insert(1, "JohnYang");
Console.WriteLine(str1);
LastIndexOf
:与IndexOf
一样,但定位最后一次出现的位置
string str = "ABCDDEABC";
Console.WriteLine(str.LastIndexOf('C'));
-
LastIndexOfAny
:与IndexOfAny
一样,但定位最后一次出现的位置 -
PadLeft
:在字符串的左侧,通过添加指定的重复字符填充字符串
string str = "ABCDDEABC";
Console.WriteLine(str.PadLeft(15,'*'));
PadRight
string str = "ABCDDEABC";
Console.WriteLine(str.PadRight(15,'*'));
Replace
:用另一个字符或字符串替换字符串给定的字符或字符串
string str = "ABCDDEABC";
var str1=str.Replace("A", " JohnYang ");
Console.WriteLine(str1);
Split
:在出现给定字符的地方,把字符串拆分为一个子字符串数组
string str = "ABCDDEABC";
var p=str.Split(new char[] { 'A', 'C' });
Console.WriteLine(p.Length);
foreach (var s in p)
{
Console.WriteLine(s);
}
string str = "ABCDDEABC";
var p=str.Split(new char[] { 'A', 'C' },StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(p.Length);
foreach (var s in p)
{
Console.WriteLine(s);
}
Substring
:在字符串中检索给定位置的子字符串
string str = "ABCDDEABC";
var str1=str.Substring(2);//索引2开始往后
Console.WriteLine(str1);
string str = "ABCDDEABC";
var str1=str.Substring(2,4);//索引2开始往后,一共长度是4
Console.WriteLine(str1);
-
ToLower
:把字符串转换为小写形式 -
ToUpper
:把字符串转换为大写形式 -
Trim
:删除首尾的空白
创建字符串
String
类功能很强大,但它有一个问题重复修改给定的字符串,效率会很低,因为它实际上是一个不可改变的数据类型,一旦字符串对象进行了初始化,就不能改变了。所以反复修改,其实就是反复新创建字符串,然后旧字符串被垃圾收集器清理。
下面看一个例子,对一个字符串进行加密,加密方法也很简单,就是把每个字符变成其下一个字符。
var testChars = new char[] { 'a', 'z', 'A', 'Z' };
foreach(var alpha in testChars)
{
Console.WriteLine($"{alpha}:{(int)alpha}");
}
string greetingText = "Hello from all the guys at Wrox Press. ";
greetingText += "We do hope you enjoy CSharp as much as we enjoyed writing code.";
Console.WriteLine($"greetingText.Length is {greetingText.Length}");
for(int i = 'z'; i >= 'a'; i--)//字符隐式转换为int
{
char old1 = (char)i;
char new1 = (char)(i + 1);
greetingText=greetingText.Replace(old1, new1);
}
Console.WriteLine("after replacing a-z");
Console.WriteLine(greetingText);//只对'a'-'z'的进行“加密”,也就是将某字符替换为其后面的一个字符
for(int i = 'Z'; i >= 'A'; i--)
{
char old1 = (char)i;
char new1 = (char)(i + 1);
greetingText = greetingText.Replace(old1, new1);
}
Console.WriteLine("after replacing A-Z");
Console.WriteLine(greetingText);
greetingText
有102个字符构成,为了完成这么一个简单的“加密”,它replace一次,就要新创建一个字符串,新字符串长度也为102,而需要26次replace,所以需要在堆上有一个总共能储存102*26=2652个字符的字符串对象,然后最终等待被垃圾收集!显然,如果使用字符串频繁进行文字处理,应用程序就会遇到严重的性能问题。
为了解决这类问题,Microsoft提供了System.Text.StringBuilder
类,它不像String
类那样能支持很多方法,StringBuilder
可以进行的处理仅限于替换,追加,删除字符串中的文本,但是它的工作方式非常高效。
下面的代码证明了这一点:
string str = "abc";
string str1 = str;
Console.WriteLine(str == str1);
str+= "de";
Console.WriteLine(str == str1);
StringBuilder str2 = new StringBuilder("Hello");
var str3 = str2;
str2.Append("JohnYang");
Console.WriteLine(str3 == str2);
str2.AppendFormat("{0:C}", 13);
Console.WriteLine(str2 == str3);
Console.WriteLine(str3);
str
在加上de
后,与str1
不再是同一个地址,说明了其加上"de"
的确是新建了一个string,而str2==str3
为true,则说明了StringBuilder
类上的增加字符的确是在原有的地址上进行的操作。
格式字符串
格式字符串只要有格式化的变量,在参数列表中的下表就必须放在花括号中。如:
double d=13.45;
int i=45;
Console.WriteLine("The double is{0},and the int is {1}",d,i);
在花括号中,还可以有与该项的格式相关的其他信息,例如,可以包含:
- 该项的字符串表示要占用的字符数,(这个信息的前面应有一个逗号,逗号前面是参数列表对应的位置值),负值表示左对齐,正值表示右边对齐。如果实际占用的字符数大于给定的占用字符数,内容完整显示,如果小于给定的占用字符数,就用空格补齐。
double d = 13.45;
Console.WriteLine("The double is {0,10}!", d);//右对齐
Console.WriteLine("The double is {0,-10}!", d);//左对齐
- 格式说明符也可以显示出来(它的前面应有冒号,表示如何格式化该项)
(1)C
:货币
double d = 13.45;
Console.WriteLine("The double is {0,0:C3}!", d);//C表示货币,C后面的3表示3位小数
(2)D
:十进制,格式化成固定的位数,位数不能少于未格式化前,只支持整型
int d = 13;
Console.WriteLine("The int is {0,0:D3}!", d);//D后面的3表示至少是3位,不足的以0补齐
Console.WriteLine("The int is {0,0:D1}", d);
(3)E
:科学计数法
int d = 132;
Console.WriteLine("The int is {0,0:E3}!", d);//E后面的3表示小数点后至少3位
(4)F
:小数点后位数固定
int d = 132;
double e = 13.45;
Console.WriteLine("The int is {0,0:F3}!", d);//F后面的3表示小数点后至少3位
Console.WriteLine("the double is {0,0:F3}",e);
(5)G
:常规
(6)N
:用分号隔开的数字,通常是特定地区的数字格式
int d = 13223232;
double e = 142343.45;
Console.WriteLine("The int is {0,0:N3}!", d);//N后面的3表示小数点后至少3位
Console.WriteLine("the double is {0,0:N3}",e);
(7)P
:百分比四舍五入
double e = 0.545666;
Console.WriteLine("the double is {0,0:P}",e);//默认是两位小数点,四舍五入
Console.WriteLine("the double is {0,0:P3}", e);//指定为三位小数点,四舍五入
(8)X
,十六进制格式
(9)零占位符和数字占位符
string.Format("{0:0000.00}", 12394.039) //结果为:12394.04
string.Format("{0:0000.00}", 194.039) //结果为:0194.04
string.Format("{0:###.##}", 12394.039) //结果为:12394.04
string.Format("{0:####.#}", 194.039) //结果为:194
零占位符: 如果格式化的值在格式字符串中出现“0”的位置有一个数字,则此数字被复制到结果字符串中。小数点前最左边的“0”的位置和小数点后最右边的“0”的位置确定总在结果字符串中出现的数字范围。 “00”说明符使得值被舍入到小数点前最近的数字,其中零位总被舍去。
数字占位符: 如果格式化的值在格式字符串中出现“#”的位置有一个数字,则此数字被复制到结果字符串中。否则,结果字符串中的此位置不存储任何值。
请注意,如果“0”不是有效数字,此说明符永不显示“0”字符,即使“0”是字符串中唯一的数字。如果“0”是所显示的数字中的有效数字,则显示“0”字符。 “##”格式字符串使得值被舍入到小数点前最近的数字,其中零总被舍去。
字符串的格式化
Console.WriteLine("The double is {0,10:E} and the int is {1}",d,i);
Console.WriteLine
只是把参数完整列表传送给静态方法String.Format
,然后调用带有一个参数的重载方法WriteLine
,再将该静态方法返回的字符串传入该重载方法。
String.Format
现在需要用对应对象的合适字符串表示来替换每个格式说明符,构造最终的字符串。但构建字符串的过程,需要StringBuilder
实例,而不是String
实例。在上面的示例中,StringBuilder
实例是用字符串的第一部分(即“The double is”)创建和初始化的,然后调用StringBuilder.AppendFormat
方法(该方法实际上在最终调用Console.WriteLine方法时调用,在这个例子中,因为本身就是调用了Console.WriteLine,所以实际上也确实是调用了AppendFormat方法,但对于String.Format方法来讲,实际上AppendFormat方法并没有直接被调用),传递第一个格式说明符"{0,10:E}"
和相应的对象double
,把这个对象的字符串表示添加到构造号的字符串对象中,这个过程会继续重复调用StringBuilder.Append
和StringBuilder.AppendFormat
方法,直到得到了全部格式化好的字符串为止。
那么StringBuilder.AppendFormat
方法是如何格式化对象的呢?
它首先检查对象,确定是否实现了IFormattable
接口:只要试着把这个对象强制转换为接口,看看是否强制转换成功即可,或者采用is
关键字测试,如果测试失败,AppendFormat
就会调用对象的ToString
方法,所有对象都从System.Object
类继承了这个方法或重写了该方法。但是,所有预定义的基元数字类型都实现了该接口,特别是上例中的double和int,就不会调用继承自System.object类的基本的ToString方法。
IFormattable
接口只定义了一个方法,该方法也命名为ToString
,它带有两个参数,而System.Object版本的ToString()方法不带参数。
interface IFormattable
{
string.ToString(string format,IFormatProvider formatProvider) ;
}
该接口规定的ToString
方法的第一个参数是一个字符串,指定要求的格式,它是子字符串的说明部分,该部分放在字符串的{}中的:
后,如果无格式说明符,则为空值。
比如上例中,传递给它的第一个参数就是E
。第二个参数是实现IFormatProvider
接口的对象引用,提供了ToString
在格式化对象时需要考虑的更多信息,但StringBuilder.AppendFormat
方法为这个参数传递一个空值,如果formatProvider为空,则ToString方法就要使用系统设置中指定的文化背景信息。
FormattableVector例子
class Program
{
struct Vector : IFormattable
{
public double x, y, z;
public Vector(double x,double y,double z)
{
this.x = x;
this.y = y;
this.z = z;
}
public string ToString(string format,IFormatProvider provider)
{
if (format == null)
{
return ToString();//如果无格式说明符,则传入的format为null,这时调用其ToString()方法
}
string formatUpper = format.ToUpper();
switch (formatUpper)
{
case "N"://提供一个数字,即矢量的模
return "||" + Norm().ToString() + "||";
case "VE"://以科学计数法显示每个成员的一个请求
return String.Format("({0:E},{1:E},{2:E}", x, y, z);
case "IJK":
StringBuilder sb = new StringBuilder(x.ToString(), 30);
sb.AppendFormat(" i+ ");
sb.AppendFormat(y.ToString());
sb.AppendFormat(" j+ ");
sb.AppendFormat(z.ToString());
sb.AppendFormat(" k");
return sb.ToString();
default:
return ToString();
}
}
public double Norm()
{
return x * x + y * y + z * z;
}
public override string ToString()
{
return "(" + x + ", " + y + ", " + z + ")";
}
}
public static void Main()
{
Vector v1 = new Vector(1, 32, 5);
Vector v2 = new Vector(845.4, 54.3, -7.8);
Console.WriteLine("IJK format:{0,0:IJK}, {1,0:IJK}", v1, v2);
Console.WriteLine("default format:{0},{1}", v1, v2);
Console.WriteLine("VE format:{0,0:VE},{1,0:VE}", v1, v2);
Console.WriteLine("Norms are :{0,0:n},{1,0:N}", v1, v2);
}
}
正则表达式
- 使用Regex类
string s1 = "One,Two,Three Liberty Associates,Inc.";
Regex theRegex = new Regex(" |,");
StringBuilder sBuilder = new StringBuilder();
int id = 1;
foreach(string substring in theRegex.Split(s1))
{
sBuilder.AppendFormat("{0}:{1}
", id++,substring);
}
Console.WriteLine(sBuilder);
- 使用Regex静态函数
public static void Main()
{
string s1 = "One,Two,Three Liberty Associates,Inc.";
StringBuilder sBuilder = new StringBuilder();
int id = 1;
foreach(string substring in Regex.Split(s1," |,"))
{
sBuilder.AppendFormat("{0}:{1}
", id++,substring);
}
Console.WriteLine(sBuilder);
}
使用Regex的Match集合
.NET的RegularExpressions
命名空间还有两个类,可以用来反复搜索字符串,并以集合的形式返回结果,类型是MatchCollection
,包括0个或多个Match
对象,Match
对象有两个重要的属性:长度和值。
public static void Main()
{
string str1 = "This is a test string";
Regex theReg = new Regex(@"(S+)s");
MatchCollection theMatches = theReg.Matches(str1);
foreach(Match theMatch in theMatches)
{
Console.WriteLine("theMatch.Length:{0}", theMatch.Length);
if (theMatch.Length != 0)
{
Console.WriteLine("theMatch:{0}", theMatch.ToString());
}
}
}
使用Regex的Group类
public static void Main()
{
string str1 = "JohnYang: 04:03:27 127.0.0.0 Liberty.com " +
"LiLei: 03:02:45 127.09.89.02 FreeCoffee.com " +
"Mehaky: 09:08:23 137.08.09.00 zhihu.com ";
Regex theReg = new Regex(@"(?<name>([a-z]|[A-Z])+):s" +
@"(?<time>(d|:)+)s"+
@"(?<ip>(d|.)+)s" +
@"(?<site>(S)+)");
MatchCollection theMatches = theReg.Matches(str1);
foreach(Match theMatch in theMatches)
{
if (theMatch.Length != 0)
{
Console.WriteLine("
theMatch:{0}", theMatch.ToString());
Console.WriteLine("name:{0}", theMatch.Groups["name"]);
Console.WriteLine("time:{0}", theMatch.Groups["time"]);
Console.WriteLine("ip:{0}", theMatch.Groups["ip"]);
Console.WriteLine("site:{0}", theMatch.Groups["site"]);
}
}
}
CaptureCollection
public static void Main()
{
string str1 = "JohnYang 04:03:27 127.0.0.0 Liberty.com " +
"LiLei 03:02:45 127.09.89.02 FreeCoffee.com " +
"Mehaky 09:08:23 137.08.09.00 zhihu.com ";
Regex theReg = new Regex(@"(?<site>([a-z]|[A-Z])+)s" +
@"(?<time>(d|:)+)s"+
@"(?<ip>(d|.)+)s" +
@"(?<site>(S)+)");
MatchCollection theMatches = theReg.Matches(str1);
foreach(Match theMatch in theMatches)
{
if (theMatch.Length != 0)
{
Console.WriteLine("
theMatch:{0}", theMatch.ToString());
Console.WriteLine("site:{0}", theMatch.Groups["site"]);
Console.WriteLine("time:{0}", theMatch.Groups["time"]);
Console.WriteLine("ip:{0}", theMatch.Groups["ip"]);
Console.WriteLine("site:{0}", theMatch.Groups["site"]);
}
}
}
可以发现,两个site组,只显示了最后一个site,其实确实将两个都获取了,只是第一个被第二个覆盖了。
public static void Main()
{
string str1 = "JohnYang 04:03:27 127.0.0.0 Liberty.com " +
"LiLei 03:02:45 127.09.89.02 FreeCoffee.com " +
"Mehaky 09:08:23 137.08.09.00 zhihu.com ";
Regex theReg = new Regex(@"(?<site>([a-z]|[A-Z])+)s" +
@"(?<time>(d|:)+)s"+
@"(?<ip>(d|.)+)s" +
@"(?<site>(S)+)");
MatchCollection theMatches = theReg.Matches(str1);
foreach(Match theMatch in theMatches)
{
if (theMatch.Length != 0)
{
Console.WriteLine("
theMatch:{0}", theMatch.ToString());
Console.WriteLine("site:{0}", theMatch.Groups["site"]);
Console.WriteLine("time:{0}", theMatch.Groups["time"]);
Console.WriteLine("ip:{0}", theMatch.Groups["ip"]);
Console.WriteLine("site:{0}", theMatch.Groups["site"]);
foreach(Capture cap in theMatch.Groups["site"].Captures)
{
Console.WriteLine("Cap:{0}", cap.ToString());
}
}
}
}