您的位置:首頁>正文

深入理解C中的String

關於C#中的類型

在C#中類型分為數值型別和參考類型, 參考類型和數值型別都繼承自System.Object類,幾乎所有的參考類型都直接從System.Object繼承, 而數值型別具體一點則繼承System.Object的子類, 即繼承System.ValueType。 而String類型卻有點特別, 雖然它屬於參考類型, 但是他的一些特性卻有點類似數值型別。

關於C# String1、不變性

我們先來看看一個例子:

static void Main(string[] args) { string str1 = "string"; string str2 = str1; Console.WriteLine(object.ReferenceEquals(str1, str2)); str2 += "change"; Console.WriteLine(object.ReferenceEquals(str1, str2)); Console.ReadKey; }

輸出結果是True、False。 為什麼呢?我們來看看IL。

.entrypoint // 代碼大小 48 (0x30) .maxstack 2 .locals init ([0] string str1, [1] string str2) IL_0000: nop IL_0001: ldstr "string" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: stloc.1 IL_0009: ldloc.0 IL_000a: ldloc.1 IL_000b: ceq IL_000d: call void [mscorlib]System.Console::WriteLine(bool) IL_0012: nop IL_0013: ldloc.1 IL_0014: ldstr "change" IL_0019: call string [mscorlib]System.String::Concat(string,string) IL_001e: stloc.1 IL_001f: ldloc.0 IL_0020: ldloc.1 IL_0021: ceq IL_0023: call void [mscorlib]System.Console::WriteLine(bool) IL_0028: nop IL_0029: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey IL_002e: pop IL_002f: ret

+=在內部調用了Concat函數, 將str2和"change"連接起來直接生成了一個新的字串, 和原來的字串是不同的物件。 Trim、Remove函數都是會直接生成一個新的物件, 字串一經定義, 就不能改變。

其實字串具有原子性(也就是不變性),

任何改變字串的值的行為都不會成功, 只會創建一個新的字串物件。 在實際程式設計中, 我們會大量的使用字串, 這樣就會導致不停地創建新的字串物件和分配記憶體, 可能導致垃圾回收器GC不停地進行垃圾回收, 大大降低性能, 並且伴隨著記憶體溢出的危險。 所以.Net對字串進行了的特殊的處理, 這就是字串駐留池。

在字串駐留池, 保存著字串字面值和指向的引用。 每次有新的字串創建, 都會在駐留池中查找是否存在字面值相同的字串, 如果存在就將其指向已經存在的字串的引用, 不存在就直接新建一個字串, 然後指向一個新的位址。

2、作為函數參數的處理

在函數的參數傳遞中,

數值型別直接拷貝變數保存的值, 傳遞的是一個值得副本, 而參考類型傳遞的是位址的一個副本, 所以在函數中改變傳址參數中屬性的值會直接改變函數外真實類型物件的值。

static void Main(string[] args) { People people = new People { Name = "Jack" }; Console.WriteLine(people.Name); Change(people); Console.WriteLine(people.Name); Console.ReadKey; } static void Change(People p) { p.Name = "Eason"; } class People { public string Name { get; set; } }

程式先輸出Jack, 後輸出Eason, 可以說明參考類型傳遞的是引用位址, 函數改變的參數物件和外部傳遞進來的物件是一個物件。

那麼我們來看看String作為參數的情況:

static void Main(string[] args) { string str = "string"; Console.WriteLine(str); Change(str); Console.WriteLine(str); Console.ReadKey; } static void Change(string str) { str = "change"; Console.WriteLine(str); }

結果輸出string、change、string。 調用Change函數後str的值還是"string", 由於字串類型的不變性, 在Change函數中對str進行賦值會重新創建一個新的字串物件, 然後為這個新的物件附上引用。 所以雖然字串類型是參考類型, 但是在參數傳遞時它其實相當於數值型別。

3、相等比較處理

先看一個例子:

string str1 = "string"; string str2 = "string"; string str3 = "stringstring"; string str4 = "string" + "string"; string str5 = str1 + "string"; Console.WriteLine(ReferenceEquals(str1, str2)); Console.WriteLine(str1 == str2); Console.WriteLine(ReferenceEquals(str3, str4)); Console.WriteLine(str3 == str4); Console.WriteLine(ReferenceEquals(str3, str5)); Console.WriteLine(str3 == str5); Console.ReadKey;

不出意外結果都應該為True, True, True, True, True, True, 但是結果卻是True,

True, True, True, False, True, str3和str5不是一個對象, 他們不是指向同一個位址, 為什麼呢?經過查看IL代碼發現, str5在IL代碼中調用了Concat函數將str1和"string"進行了拼接, 那這個Concat函數到底做了什麼。

public static string Concat(string str0, string str1) { if (IsNullOrEmpty(str0)) { if (IsNullOrEmpty(str1)) { return Empty; } return str1; } if (IsNullOrEmpty(str1)) { return str0; } int length = str0.Length; string dest = FastAllocateString(length + str1.Length); FillStringChecked(dest, 0, str0); FillStringChecked(dest, length, str1); return dest; }

FastAllocateString函數負責分配長度為str0.Length+str1.Length的空字串dest, FillStringChecked分別將str0和str1複製到dest中, 最後生成由str0和str1連接成的字串, 這樣不會再去字串駐留池中查找是否存在和dest相同的字串, 而是直接生成一個新的物件。 所以字串變數和字串常量進行拼接後會直接生成一個新的物件, 繞過駐留池檢查。

而字串常量拼接不會產生新的字串, 除非駐留池中沒有與之拼接後字面值相等的字串。 我們來看看IL代碼:

IL_0001: ldstr "string" IL_0006: stloc.0 IL_0007: ldstr "string" IL_000c: stloc.1 IL_000d: ldstr "stringstring" IL_0012: stloc.2 IL_0013: ldstr "stringstring" IL_0018: stloc.3 IL_0019: ldloc.0 IL_001a: ldstr "string" IL_001f: call string [mscorlib]System.String::Concat(string,string) IL_0024: stloc.s str5 IL_0026: ldloc.0 IL_0027: ldloc.1

str3和str4的字面值是相等的, 都是"stringstring", str3先於str4被初始化, 當str4被初始化的時候, 由於其字面值和str3相等, 所以CLR會將str3指向的位址賦給str4,

所以str3和str4引用是相等的。

至於"=="操作符的得到的結果都是True是因為"=="操作符會調用String.Equal方法, IL代碼如下:

IL_0032: call bool [mscorlib]System.String::op_Equality(string,string)

op_Equality最終會調用String.Equal函數, Equal函數的比較步驟是先比較兩個物件的引用是否相等, 不相等的話再對值進行比較, 比較值時是按位比較的。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示