本文只是個人總結見解,勿噴
首先肯定的是string是引用類型
string s_a = "yhc";
string s_b = s_a;
if(s_a.Equals(s_b))
Console.WriteLine("相同?");
else
Console.WriteLine("不相同");
輸出是“相同”,讓s_b=s_a,本質是讓s_b指向了yhc在堆上的存儲位置,此時s_a和s_b都指向了yhc在堆上的存儲位置。
理論上說如果修改s_a的值,例如s_a=”wq”,b也會跟著變化,但是實際上不是的,在c#裡面,如果修改其中一個string對象的值比如s_a,系統會為s_a再重新申請一塊內存用來存放修改後的s_a的值wq,而b依然指向於a曾今的內存位置。
這就是為什麼在如果要頻繁對string操作的時候建議用stringbuilder,因為stringbuilder類似c++中的標准庫string,是可以動態增加內存大小的,而c#中的string是固定大小,你string一個對象就給你分配一個固定大小的內存空間,這個對象一旦創建就無法更改,如果你要往裡面增加內容,我就必須新建一個string對象,把值拷貝進來,然後把之前的string對象刪除,頻繁操作string會帶來效率的降低,畢竟創建對象,分配內存,刪除對象這個過程需要耗時耗資源。
我們可以通過比較內存地址來斷定這一結論(ReferenceEquals靜態方法用於比較對象的引用是否一致,而不是簡單的值),如果s_a和s_b指向的是同一地址,這裡的比較應該返回相同
s_a =
"wq";
if (ReferenceEquals(s_a,s_b))
Console.WriteLine("相同");
else
Console.WriteLine("不相同");
答案很明顯,會輸出“不相同”,這說明系統為s_a重新分配了新地址的內存空間,當然你也許會說為什麼不用==,因為string的= =被重寫後,其實比較的還是string的值,(當然如果你一定要用= =也是可以的,只不過需要裝箱成object類型,讓==不再為string的特例重寫比較,即if(((object)s_a)==( (object)s_b))),但是= =在比較其他對象的時候可不是這樣了,這是string這個引用類型表現出值類型特征的一點,其實還有一個地方也表現了string類型的值類型特征表現,就是在string類型作為參數傳遞的時候,傳遞的是string對象的引用地址,但是在傳遞過去的方法裡面修改string對象的值卻不影響本體,這是因為系統根據傳送過來的地址重新構造分配了一個全新的string對象,比如調用方法 void fun(string a);並把s_a傳遞作為參數過來的時候,a會被系統重新分配一個地址,而不是指向s_a的地址,這是引用類型的一個特例。比如A是一個普通類,比如
A b=new A();
b.name=”yhc”;
fun(b);
//代碼運行到此,如果輸出,此時輸出a的name值:為wq
void fun(A a)
{
//代碼運行到此,如果輸出,此時輸出a的name值:為yhc
a.name=”wq”;//因為參數傳遞的是b的引用,對a的操作就是對b的操作
//代碼運行到此,如果輸出,此時輸出a的name值:為wq
}
但是如果是這樣:
A b=new A();
b.name=”yhc”;
fun(b);
//代碼運行到此,如果輸出,此時輸出a的name值:為yhc
void fun(A a)
{
//代碼運行到此,如果輸出,此時輸出a的name值:為yhc
a=new A();
a.name=”wq”;
//代碼運行到此,如果輸出,此時輸出a的name值:為wq
}
在執行玩fun(b)後的輸出,就不再是wq了,因為在方法fun內部,我們把a指向了一個新建立在堆上的對象A,這個new對象在離開函數方法後會被GC垃圾回收機制回收,什麼時候來回收就不知道了,當然如果是建立在堆棧上的資源會被立刻釋放,但是關於建立在堆上的對象的GC回收也有特例,就是那些非托管對象,比如什麼SqlConnection數據庫連接、文件句柄、網絡連接什麼的,這些對象在方法內部new後,離開方法後不會被GC回收。
上面說的跑遠裡,我們回到之前,當然有人會說用ReferenceEquals來比較不夠深刻和准確性,因為s_a改為wq後,值也發生了變化,用ReferenceEquals比較無法判斷是值不同還是引用不同,於是我們如下修改:按照string修改後系統會自動為其分配新的內存空間的原理我們把s_a修改成yhc,即雖然修改了,但是值還是保持原來的yhc,只為了測試這個修改操作有沒有重新分配內存空間
s_a = "yhc";
if (ReferenceEquals(s_a,s_b))
Console.WriteLine("相同");
else
Console.WriteLine("不相同");
我們都以為輸出會是“不相同”,但是,但是結果讓我們失望,系統輸出“相同”,原因是微軟的CLR使用了優化技術,叫做字符串駐留技術,這個技術的原理大家可能都知道,就是CLR初始化的時候會創建一個內部散列表,表為鍵值形式,鍵為字符串,比如這裡的yhc,值為yhc在堆內存上存儲的地址,初始化的時候散列表肯定為空了,即時編譯器(JIT)編譯時先去散列表找字符串yhc,第一次找沒找到yhc,其就會在堆上開辟空間來存放yhc,然後把存放的地址和yhc這個字符串存儲在散列表中,然後第二次又去找,就是這裡的修改s_a的值為yhc,發現在散列表的鍵中找到了yhc這個值,於是就不再分配內存來存放修改後的yhc,而是讓s_a依然指向之前這個yhc在堆上存放的空間,到此也就是說,你修改後的s_a=“yhc”,雖然修改了,但是其值沒有變,還是yhc,所以被編譯器的優化機制優化了,所以這個測試還是測試不出來我們要的效果。
上面測試缺陷的原理我們知道了,但是我們差點忘了我們到底是要測試什麼,我們要測試的是s_a或者s_b修改後,系統為其重新分配內存空間,我們要比較修改後兩者的空間地址是否一致,於是總結上面教訓如下操作:
s_a=string.Copy(s_b)
if (ReferenceEquals(s_a,s_b))
Console.WriteLine("相同");
else
Console.WriteLine("不相同");
這裡,s_a=string.Copy(s_b),string.Copy方法是創建一個與指定的s_b具有相同值的 System.String 的新實例。注意是新實例,用這個方法則編譯器不會做上面的字符串駐留技術優化,即此時s_a再次被修改了,修改的值是拷貝了s_b的的值(s_b的值為yhc),即此時s_a的值也是yhc,這時候系統輸出“不相同”了,這就驗證了我們string對象被修改後,系統會為他重新分配內存空間。
完整測試代碼如下:
測試很簡單,但是深究很多
string s_a = "yhc";
string s_b = s_a;
s_a = string.Copy(s_b);
if (ReferenceEquals(s_a,s_b))
Console.WriteLine("相同");
else
Console.WriteLine("不相同");
Console.Read();