前言
几乎任何一个项目都离不开对字符串的处理,在C和C++编程中,许多程序的漏洞都是由于字符串缓冲区溢出造成的。为了避免在C#中出现类似的问题,同时也为了使用更方便,C#中专门设置了两个字符串处理类:String类和StringBuilder类。
本文主要给大家介绍了关于c#基础系列之string的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧
扩展阅读:深入理解值类型和引用类型
基本概念
string(严格来说应该是System.String) 类型是我们日常coding中用的最多的类型之一。那什么是String呢?^ ~ ^
String是一个不可变的连续16位的Unicode代码值的集合,它直接派生自System.Object类型。
与之对应的还有一个不常用的安全字符串类型System.Security.SecureString,它会在非托管的内存上分配,以便避开GC的黑手。主要用于安全性特高的场景。[具体可查看msdn这里不展开讨论了。=>msdn查看详情
特性
- 由于String类型直接派生于Object,所以它是引用类型,那就意味着String对象的实例总是存在于堆上。
- String具有不变性,也就是说一旦初始化,它的值将永远不变。
- String类型是封闭的,换言之,你的任何类型不能继承String。
- 定义字符串实例的关键字string只是System.String 类型的一个映射。
注意事项
- 关于字符串中的回车符和换行符一般大家喜欢直接硬编码‘\r\n',但是不建议这么做,一旦程序迁移到其他平台,将出现错误。相反,推荐使用System.Environment类的NewLine属性来生成回车符和换行符,可以跨平台使用的。
- 常量字符串的拼接和非常量字符串在CLR中行为是不一样的。具体请查看性能部分。
- 字符串之前加@符号会改变编译器的行为,如果加了@符号,编译器会把String中的转义字符视为正常字符来显示。也就是我定义的什么内容就是什么内容,主要在使用文件路径或者目录字符串中使用。以下两个String内容的输出将完全一致。
static void Main(string[] args)
{
string a = "c:\\temp\\1";
string b = @"c:\temp\1";
Console.WriteLine(a);
Console.WriteLine(b);
Console.Read();
}
性能
- c#的编译器直接支持String类型,并将定义的常量字符串在编译期直接存放到模块的元数据中。然后会在运行时直接加载。这也说明String类型的常量在运行时是有特殊待遇的。
- 由于字符串的不变性,也就意味着多个线程同时操作该字符串不会有任何线程安全的问题。这在某些共享配置的设计中很有用。
- 如果程序经常会对比重复度比较高的字符串,这会造成性能上的影响,因为对比字符串是要经过几个步骤的。为此CLR引入了一个字符串重用的技术,学名叫做‘字符串留用'。原理就是:CLR会在初始化的时候创建一个内部的哈希表,key是字符串,value就是留用字符串在托管堆上的引用。
String类型提供了两个静态方法来操作这个哈希表:
String.Intern
String.IsInterned
具体请查看msdn(https://msdn.microsoft.com/zh-cn/library/system.string.isinterned(v=vs.110).aspx)
但是c#编译器默认是不开启字符串留用功能的,因为如果程序大量把字符串留用,应用程序总体性能可能会变得更慢。(微软也是挺纠结的,程序员TMD的更纠结)
如果我们的程序中有很多个一模一样值的常量字符串, c#的编译器会在编译期间把这些字符串合并为一个并写入模块的元数据中,然后修改所有引用该字符串的代码。这也是一种字符串重用技术,学名‘字符串池'。这意味着什么呢?这意味着所有值相同的常量字符串其实引用的是同一个内存地址的实例,在相同值非常多的情况下能显著提高性能和节省大量内存。
string s1 = "hello 大菜";
string s2 = "hello 大菜";
unsafe
{
fixed (char* p = s1)
{
Console.WriteLine("字符串地址= 0x{0:x}", (int)p);
}
fixed (char* p = s2)
{
Console.WriteLine("字符串地址= 0x{0:x}", (int)p);
}
}
输出结果:
惒zsr'ZG^>Vg瞒jR2 |