广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C# String字符串案例详解
  • 605
分享到

C# String字符串案例详解

2024-04-02 19:04:59 605人浏览 薄情痞子
摘要

string是一种很特殊的数据类型,它既是基元类型又是引用类型,在编译以及运行时,.net都对它做了一些优化工作,正式这些优化工作有时会迷惑编程人员,使string看起来难以琢磨。这

string是一种很特殊的数据类型,它既是基元类型又是引用类型,在编译以及运行时,.net都对它做了一些优化工作,正式这些优化工作有时会迷惑编程人员,使string看起来难以琢磨。这篇文章共四节,来讲讲关于string的陌生一面。 

一.恒定的字符串

要想比较全面的了解stirng类型,首先要清楚.Net中的值类型与引用类型。

C#中,以下数据类型为值类型: bool、byte、char、enum、sbyte以及数字类型(包括可空类型)
以下数据类型为引用类型: class、interface、delegate、object、stirng
看到了吗,我们要讨论的stirng赫然其中。被声明为string型变量存放于堆中,是一个彻头彻尾的引用类型。那么许多同学就会对如下代码产生有疑问了,难道string类型也会“牵一发而动全身”吗?让我们先来看看以下三行代码有何玄机:


string a = "str_1";
string b = a;
a = "str_2";

不要说无聊,这一点时必须讲清楚的!在以上代码中,第3行的“=”有一个隐藏的秘密:它的作用我们可以理解为新建,而不是对变量“a”的修改。以下是IL代码,可以说明这一点:


.maxstack  1
.locals init ([0] string a,[1] string b)
IL_0000:  nop
IL_0001:  ldstr      "str_1"
IL_0006:  stloc.0
IL_0007:  ldloc.0
IL_0008:  stloc.1
IL_0009:  ldstr      "str_2"
IL_000e:  stloc.0  //以上2行对应 C#代码 a = "str_2";
IL_0015:  ret

  可以看出IL代码的第1、6行,由ldstr指令创建字符串"str_1",并将其关联到了变量“a”中;7、8行直接将堆栈顶部的值弹出并关联到变量“b”中;9、10由ldstr创建字符串"str_2",关联在变量“a”中(并没有像我们想象的那样去修改变量a的旧值,而是产生了新的字符串);

  在C#中,如果用new关键字实例化一个类,对应是由IL指令newobj来完成的;而创建一个字符串,则由ldstr指令完成,看到ldstr指令,我们即可认为,IL希望创建一个新的字符串 。(注意:是IL希望创建一个字符串,而最终是否创建,还要在运行时由字符串的驻留机制决定,这一点下面的章节会有介绍。)

  所以,第三行C#代码(a = "str_2";)的样子看起来是在修改变量a的旧值"str_1",但实际上是创建了一个新的字符串"str_2",然后将变量a的指针指向了"str_2"的内存地址,而"str_1"依然在内存中没有受到任何影响,所以变量b的值没有任何改变---这就是string的恒定性,同学们,一定要牢记这一点,在.Net中,string类型的对象一旦创建即不可修改!包括ToUpper、SubString、Trim等操作都会在内存中产生新的字符串。

本节重点回顾:由于stirng类型的恒定性,让同学友们经常误解,string虽属引用类型但经常表现出值的特性,这是由于不了解string的恒定性造成的,根本不是“值的特性”。例如:


string a = "str_1";
a = "str_2";

这样会在内存中创建"str_1"和"str_2"两个字符串,但只有"str_2"在被使用,"str_1"不会被修改或消失,这样就浪费了内存资源,这也是为什么在做大量字符串操作时,推荐使用StringBuilder的原因。

二..Net中字符串的驻留(重要)

  在第一节中,我们讲了字符串的恒定性,该特性又为我们引出了字符串的另一个重要特性:字符串驻留。
从某些方面讲,正是字符串的恒定性,才造就了字符串的驻留机制,也为字符串的线程同步工作大开方便之门(同一个字符串对象可以在不同的应用程序域中被访问,所以驻留的字符串是进程级的,垃圾回收不能释放这些字符串对象,只有进程结束这些对象才被释放)。
我们用以下2行代码来说明字符串的驻留现象:


string a = "str_1";
string b = "str_1";

  请各位同学友思考一下,这2行代码会在内存中产生了几个string对象?你可能会认为产生2个:由于声明了2个变量,程序第1行会在内存中产生"str_1"供变量a所引用;第2行会产生新的字符串"str_1"供变量b所引用,然而真的是这样吗?我们用ReferenceEquals这个方法来看一下变量a与b的内存引用地址:


string a = "str_1";
string b = "str_1";
Response.Write(ReferenceEquals(a,b));   //比较a与b是否来自同一内存引用
//输出:True

  哈,各位同学看到了吗,我们用ReferenceEquals方法比较a与b,虽然我们声明了2个变量,但它们竟然来自同一内存地址!这说明string b = "str_1";根本没有在内存中产生新的字符串。

  这是因为,在.Net中处理字符串时,有一个很重要的机制,叫做字符串驻留机制。由于string是编程中用到的频率较高的一种类型,CLR对相同的字符串,只分配一次内存。CLR内部维护着一块特殊的数据结构,我们叫它字符串池,可以把它理解成是一个HashTable,这个HashTable维护着程序中用到的一部分字符串,HashTable的Key是字符串的值,而Value则是字符串的内存地址。一般情况下,程序中如果创建一个string类型的变量,CLR会首先在HashTable遍历具有相同Hash Code的字符串,如果找到,则直接把该字符串的地址返回给相应的变量,如果没有才会在内存中新建一个字符串对象。

  所以,这2行代码只在内存中产生了1个string对象,变量b与a共享了内存中的"str_1"。

好了,结合第一节所讲到的字符串恒定性与第二节所讲到的驻留机制,来理解一下下面3行代码吧:


string a = "str_1"; //声明变量a,将变量a的指针指向内存中新产生的"str_1"的地址
a = "str_2";  //CLR先会在字符串池中遍历,查看"str_2"是否已存在,如果没有,则新建"str_2",并修改变量a的指针,指向"str_2"内存地址,"str_1"保持不变。(字符串恒定)
string c = "str_2"; //CLR先会在字符串池中遍历"str_2"是否已存在,如果存在,则直接将变量c的指针指向"str_2"的地址。(字符串驻留)

那么如果是动态创建字符串呢?字符串还会不会有驻留现象呢?

我们分3种情况讲解动态创建字符串时,驻留机制的表现:

(1).字符串常量连接


string a = "str_1" + "str_2";
string b = "str_1str_2";
Response.Write(ReferenceEquals(a,b));   //比较a与b是否来自同一内存引用
//输出 :True

IL代码:


.maxstack  1
.locals init ([0] string a,[1] string b)
IL_0000:  nop
IL_0001:  ldstr      “str_1str_2”
IL_0006:  stloc.0
IL_0007:  ldstr      “str_1str_2”
IL_000c:  stloc.1
IL_000d:  ret

  其中第1、6行对应c#代码string a = “str_1” + “str_2”;第7、8对应c# string b = “str_1str_2”;可以看出,字符串常量连接时,程序在被编译为IL代码前,编译器已经计算出了字符串常量连接的结果,ldstr指令直接处理编译器计算后的字符串值,所以这种情况字符串驻留机制有效!

(2).字符串变量连接


string a = "str_1";
string b = a + "str_2";
string c = "str_1str_2";
Response.Write(ReferenceEquals(b,c));
//输出:False

IL代码:


.maxstack  2
.locals init ([0] string a, [1] string b, [2] string c)
IL_0000:  nop
IL_0001:  ldstr      “str_1”
IL_0006:  stloc.0
IL_0007:  ldloc.0
IL_0008:  ldstr      “str_2”
IL_000d:  call       string [mscorlib]System.String::Concat(string,string)
IL_0012:  stloc.1
IL_0013:  ldstr      “str_1str_2”
IL_0018:  stloc.2
IL_0019:  ret

  其中第1、6行对应string a = “str_1”;第7、8、9行对应string b = a + “str_2”;,IL用的是Concat方法连接字符串,第13、18行对应string c = “str_1str_2”;可以看出,字符串变量连接时,IL使用Concat方法,在运行时生成最终的连接结果,所以这种情况字符串驻留机制无效!

(3).显式实例化


string a = "a";
string b = new string('a',1);
Response.Write(ReferenceEquals(a, b));
//输出 False

IL代码:


.maxstack  3
.locals init ([0] string a,[1] string b)
IL_0000:  nop
IL_0001:  ldstr      "a"
IL_0006:  stloc.0
IL_0007:  ldc.i4.s   97
IL_0009:  ldc.i4.1
IL_000a:  newobj     instance void [mscorlib]System.String::.ctor(char, int32)
IL_000f:  stloc.1
IL_0010:  ret

  这种情况比较好理解,IL使用newobj来实例化一个字符串对象,驻留机制无效。从string b = new string('a',1);这行代码我们可以看出,其实string类型实际上是由char[]实现的,一个string的诞生绝不像我们想想的那样简单,要由栈、堆同时配合,才会有一个string的诞生。这一点在第四节会有介绍。

当然,当字符串驻留机制无效时,我们可以很简便的使用string.Intern方法将其手动驻留至字符串池中,例如以下代码:


string a = "a";
string b = new string('a',1);    
Response.Write(ReferenceEquals(a, string.Intern(b)));
//输出:True (程序返回Ture,说明变量"a"与"b"来自同一内存地址。)

三.有趣的比较操作

  在第一节与第二节中,我们分别介绍了字符串的恒定性与与驻留性,如果这位同学友觉得完全掌握了以上内容,那么就在第三节中检验一下自己的学习成果吧!以下10段简单的代码将通过值比较与地址引用比较,来说明前两节讲到的内容,大家也可以通过这些代码来检测一下自己对string的了解程度。

代码一:


string a = "str_1";
string b = "str_1";
Response.Write(a.Equals(b));
Response.Write(ReferenceEquals(a,b));

//输出:True (Equals比较字符串对象的值)
//输出:True (ReferenceEquals比较字符串对象的引用,由于字符串驻留机制,a与b的引用相同)

代码二:


string a = "str_1str_2";
string b = "str_1";
string c = "str_2";
string d = b + c;
Response.Write(a.Equals(d));
Response.Write(ReferenceEquals(a, d));

//输出:True (Equals比较字符串对象的值)
//输出:False(ReferenceEquals比较字符串对象的引用,由于变量d的值为变量连接的结果,字符串驻留机制无效)

代码三:


string a = "str_1str_2";
string b = "str_1" + "str_2";
Response.Write(a.Equals(b));
Response.Write(ReferenceEquals(a, b));

//输出:True (Equals比较字符串对象的值)
//输出:True (ReferenceEquals比较字符串对象的引用,由于变量b的值为常量连接的结果,字符串驻留机制有效。如果变量b的值由“常量+变量”的方式得出,则字符串驻留无效)

代码四:


string a = "str_1";
string b = String.Copy(a);
Response.Write(a.Equals(b));
Response.Write(ReferenceEquals(a, b));

//输出:True (Equals比较字符串对象的值)
//输出:False(ReferenceEquals比较字符串对象的引用,Copy操作产生了新的string对象)

代码五:


string a = "str_1";
string b = String.Copy(a);
b = String.Intern(b);
Response.Write(a.Equals(b));
Response.Write(ReferenceEquals(a, b));

//输出:True (Equals比较字符串对象的值)
//输出:True (ReferenceEquals比较字符串对象的引用,String.Intern实现了字符串驻留)

代码六:


string a = "str_1";
string b = String.Copy(a);
string c = "str_1";
Response.Write((object)a == (object)b);
Response.Write((object)a == (object)c);

//输出:False(“==”在两边为引用类型时,则比较引用的地址,所以a与b为不同引用)
//输出:True (“==”在两边为引用类型时,则比较引用的地址,所以a与c的引用相同)(原文:ReferenceEquals比较字符串对象的引用,a与c由于字符串驻留机制,引用相同)

代码七:


string a = "str_1";
string c = "str_1";
Response.Write(a == c);

//输出:True(刚才我们提到过,“==”在两边为引用类型时,则比较引用的地址;如果是值类型时则需要比较引用和值。string为引用类型,那么上面的代码是比较了变量a与c的地址还是地址和值呢?
       答案是:比较了地址和值!因为在string类型比较的时候,“==”已经被重载为“Equals”了,所以,虽然你在用“==”比较两个引用类型,但实际上是在用“Equals”比较它们的地址和值!(先比较地址,地址不等再比较值))

代码八:


string a = "a";
string b = new string('a', 1);
Response.Write(a.Equals(b));
Response.Write(ReferenceEquals(a, b));

//输出:True (Equals比较值,a与b的值相同)
//输出:False(ReferenceEquals比较字符串对象的引用)

代码九:


string a = "a";
string b = new string('a', 1);
Response.Write(a.Equals(string.Intern(b)));
Response.Write(ReferenceEquals(a, string.Intern(b)));
//输出:True (Equals比较值,无论是否Intern都会相同)
//输出:True (ReferenceEquals比较字符串对象的引用,Intern已经将b驻留至字符串池内)

代码十:


string a = "str";
string b = "str_2".Substring(0,3);
Response.Write(a.Equals(b));
Response.Write(ReferenceEquals(a, b));
//输出:True (Equals比较值,a与c的值相同)
//输出:False(ReferenceEquals比较字符串对象的引用,Substring操作产生了新的字符串对象)

四.艺海拾贝

  这一节将主要给大家介绍一些string的常见问题。

(1)“string = ”与“new stirng()”的区别


string test = "a";
string test = new string('a', 1);

  以上两行代码的效果是一样的,它们的区别在于加载”a”的时间不同:第一行的“a”是一个常量,在编译期就已经被放在一个叫做常量池的地方了,常量池通常装载一些在编译期被确定下来的数据,例如类、接口等等;而第二行是运行时CLR在堆中生成的值为“a”的字符串对象,所以后者没有字符串驻留。

(2). string 与 String的区别

  String的大名叫做System.String,在编译为IL代码时,string和System.String会生成完全相同的代码:(ps:long和System.Int64,float和System.Single等也有此特性)

C#代码:


string str_test = "test";
System.String Str_test = "test";

IL代码:


// 代码大小       14 (0xe)
.maxstack  1
.locals init ([0] string str_test,[1] string Str_test)
IL_0000:  nop
IL_0001:  ldstr      "test"
IL_0006:  stloc.0
IL_0007:  ldstr      "test"
IL_000c:  stloc.1
IL_000d:  ret

  所以,二者的区别并不在于底层,而是在于string是类似于int的基元类型;System. String是框架类库(FCL)的基本类型,二者之间有直接的对应关系。

(3).StringBuilder

  StringBuilder提供了高效创建字符串的方法,由StringBuilder表示的字符串是可变的(非恒定的),在需要多处使用“+”连接字符串变量的时候,推荐使用StringBuilder来完成,最后调用其ToString()方法输出。当调用了StringBuilder的ToString()方法之后,StringBuilder将返回其内部维护的一个字符串字段引用,如再次修改StringBuilder,它将会创建一个新的字符串,这时被修改的是新的字符串,原来已经返回的字符串才不会发生改变。

StringBuilder有两个比较重要的内部字段,大家需要掌握:

  m_MaxCapacity:StringBuilder的最大容量,它规定了最多可以放置到        

  m_StringValue的字符个数,默认值为Int32.MaxValue。m_MaxCapacity一旦被指定就不能再更改。

  m_StringValue:StringBuilder维护的一个字符数组串,实际上可以理解为一个字符串。StringBuilder重写的Tostring()方法返回的就是这个字段。

到此这篇关于C# String字符串案例详解的文章就介绍到这了,更多相关C# string字符串详解内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C# String字符串案例详解

本文链接: https://www.lsjlt.com/news/130050.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C# String字符串案例详解
    string是一种很特殊的数据类型,它既是基元类型又是引用类型,在编译以及运行时,.Net都对它做了一些优化工作,正式这些优化工作有时会迷惑编程人员,使string看起来难以琢磨。这...
    99+
    2022-11-12
  • c字符串,string对象,字符串字面值的区别详解
    一、字符串字面值字符串字面值是一串常量字符,字符串字面值常量用双引号括起来的零个或多个字符表示,为兼容C语言,C++中所有的字符串字面值都由编译器自动在末尾添加一个空字符。字符串没有...
    99+
    2022-11-15
    string 字符串
  • Java的String(字符串详解)
    字符串 1.字符串的常见构造方法 主要有三种,一种是直接使用常量去构造,要么使用new String来构造,或者还可以使用字符数组的形式。 public static void main(String...
    99+
    2023-10-19
    java String 字符串 详解
  • Python 基础之字符串string详解及实例
    Python字符串(string) 详解 及 代码 Python的字符串可以使用单引号('), 双引号("), 三引号('''); 三引号(''')里面, 可以添加单引号和双引号, 也可以通过转义序列()添...
    99+
    2022-06-04
    字符串 详解 实例
  • Cython处理C字符串的示例详解
    目录楔子创建 C 字符串引用计数陷阱strlenstrcpystrcatstrcmpsprintf动态申请字符串内存memsetmemcpymemmovememcmp小结楔子 在介绍...
    99+
    2023-01-06
    Cython处理C字符串 Cython处理字符串 Cython 字符串
  • C语言字符串替换:字符,字符串,字符数组详解
    目录案例描述案例分析必备知识1,字符数组(1)字符数组的定义(2)字符数组的初始化2,字符串概念(1)字符串的概念(2)用字符初始化字符数组(3)获取字符串的长度3,字符串与指针4,...
    99+
    2022-11-12
  • C语言字符串替换:字符,字符串,字符数组详解
    在C语言中,字符串是由字符数组表示的。一个字符串是一个以null字符('\0')结尾的字符数组。字符替换:要替换字符串中的某个字符,...
    99+
    2023-08-15
    C语言
  • Golang基础教程之字符串string实例详解
    目录1、 string的定义2、string不可变3、使用string给另一个string赋值4、string重新赋值补充:字符串拼接总结1、 string的定义 Golang中的s...
    99+
    2022-11-13
  • Java Pattern与Matcher字符串匹配案例详解
    Pattern类定义          public final class Pattern extends Object impl...
    99+
    2022-11-12
  • c语言中字符串与字符串数组详解
    目录字符串字符串输出输入字符串字符串常用方法字符串数组总结字符串 用双引号引起来的就是字符串,字符串由字符组成 字符串使用%s格式化输出 字符串以\0结尾,...
    99+
    2022-11-12
  • C# String字符串的用法
    本篇内容主要讲解“C# String字符串的用法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C# String字符串的用法”吧!string是一种很特殊的数据类型,它既是基元类型又是引用类型,...
    99+
    2023-06-20
  • C/C++编程判断String字符串是否包含某个字符串实现示例
    目录一、C语言风格二、C++风格一、C语言风格 在C语言中,字符串存储为字符数组,以'\0'结束。 在C的接口中,有strstr函数,可以在字符串中查找另一个字符串。 char *...
    99+
    2022-11-12
  • SQLSERVER 拼接含有变量字符串案例详解
    一、拼接字符串(整个字符串不分割)步骤: 首先在字符串的前后加单引号; 字符串中的变量以'''+@para+'''在字符串中表示; 若在执行时存在类型转换错...
    99+
    2022-11-12
  • C++字符串的处理详解
    目录字符数组总结字符数组 双引号引起的a占两个字符,包含“\0”。 字符串处理函数 连接的时候,str2中的1替换str1中的‘\0'; 比较函数按照str...
    99+
    2022-11-12
  • C语言字符串替换空格实例详解
    目录一、题目描述二、思路分析三、整体代码总结一、题目描述 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 示例: 输入:s = "We a...
    99+
    2022-11-13
  • python开发之字符串string操作方法实例详解
    本文实例讲述了python开发之字符串string操作方法。分享给大家供大家参考,具体如下: 在python中,对于字符串string的操作,我们有必要了解一下,这样在我们的以后的开发中会给我们带来很多方便...
    99+
    2022-06-04
    字符串 详解 操作方法
  • C语言详解字符串基础
    目录一、字符串的概念二、字符数组与字符串三、字符串字面量的秘密四、字符串的长度五、小结一、字符串的概念 字符串是有序字符的集合 字符串是程序中的基本元素之一 C 语言中没有字符串的概...
    99+
    2022-11-13
  • C语言字符串数组详解
    C语言字符串数组 字符串是连续的字符序列,最后以空字符'\0'作为终止符。一个字符串的长度指所有字符的数量,但不包括终止符。在 C 语言中,没有字符串类型,自然也就没有运算符以字符串...
    99+
    2022-11-12
  • C++超详细讲解字符串类
    目录一、历史遗留问题二、解决方案三、标准库中的字符串类四、字符串循环右移五、小结一、历史遗留问题 C 语言不支持真正意义上的字符串C 语言用字符数组和一组函数实现字符串操作C 语言不...
    99+
    2022-11-13
  • C语言编程C++旋转字符操作串示例详解
    目录旋转字符串字符串左旋题前认知:暴力移位:三步翻转:判断字符串旋转题前认知字符串追加判断旋转字符串 字符串左旋 实现一个函数,可以左旋字符串中的k个字符。 例如: ABCD左旋一个...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作