C#的值型別和引用型別解析
值型別:byte,sbyte,short,int,long,float,double,decimal,char,uint,ushort,ulong,bool ,列舉型別,使用者定義的結構體struct
引用型別:class、delegate、dynamic、interface、object(Object)、string(String)、內插字串(這個樣子的:$"Name = {name}, hours = {hours:hh}")。
特殊情況:
(1)陣列。陣列是引用型別,陣列的物件在棧上。其每一個元素均分配在託管堆上。如果是值型別陣列(int,float....)那麼會在堆上對陣列自動進行初始化。如果是引用型別陣列,那麼不會初始化任何元素。此時陣列為null。
(2)巢狀型別:如果宣告一個類,該類中包含值型別的欄位,同時包含值型別的區域性變數,那麼它同樣是引用型別。但是值型別欄位和值型別區域性變數位置是不同的。
例如:
class A
{
int a; //值型別欄位
public A()
{
int b; //值型別區域性變數
}
}
欄位跟隨例項儲存,所以值型別欄位a儲存在託管堆上。但是這裡的b是區域性變數,它是值型別,它在棧上。
三、注意事項
1.值型別測試
using System;
namespace ValueAndReference
{
class Program
{
static void Main(string[] args)
{
int VT1, VT2; //值型別變數VT1,VT2
VT1 = 5;
VT2 = VT1;
VT1 = 6;
Console.WriteLine("VT1 is " + VT1 +" VT2 is " + VT2); //傳遞的只是值
}
}
}
這個沒啥可注意的,很正常。
2.引用型別測試(class)
using System;
namespace ValueAndReference
{
//引用型別定義
class ReferenceType
{
public int field; //定義欄位
public ReferenceType(int v) //建構函式,初始化field
{
field = v;
}
}
class Program
{
static void Main(string[] args)
{
ReferenceType RT1 = new ReferenceType(5); //定義並例項化引用型別物件RT1,初始欄位值為5
ReferenceType RT2 = RT1;
Console.WriteLine("RT1 is " + RT1.field +",RT2 is " + RT2.field); //輸出RT1、RT2欄位值,均為5
RT2 = new ReferenceType(6); //例項化RT2,並初始欄位值為6
Console.WriteLine("RT1 is " + RT1.field +",RT2 is " + RT2.field); //輸出RT1欄位值為5、RT2欄位值為6
ReferenceType RT3 = new ReferenceType(5); //定義並例項化引用型別物件RT3,初始欄位值為5
ReferenceType RT4 = RT3;
Console.WriteLine("RT3 is "+RT3.field+",RT4 is "+RT4.field); //輸出RT3、RT4欄位值,均為5
RT4.field = 6; //更改RT3的欄位值為6
Console.WriteLine("RT3 is " + RT3.field +",RT4 is " + RT4.field); //輸出RT3、RT4欄位值,均為6
}
}
}
這個比較需要注意,RT1的field欄位初始為5,然後RT1賦值給RT2,由於RT2是引用變數,那麼修改RT2就是修改RT1,可是為什麼RT1在後邊沒變化呢?這是因為這種情況下RT1和RT2指向的不是一個堆的地址,具體可以看下邊的圖。RT3和RT4是正常的情況,修改了RT4的值,RT3同時跟著變化。關於這兩種情況其實很簡單,而且這個很好的反應了引用型別的性質。為什麼第一種情況不行呢?是因為第一種情況改變了引用物件本身,重新進行了例項化,那麼就是重新聲明瞭一個新的例項。而第二種情況改變的是物件的屬性,兩個物件還是對應的同一個例項。看下面兩幅圖:
第一種情況:
第一次例項化RT2時拷貝的RT1,此時RT1、RT2是一個物件。接下來的例項化是對RT2本身的改變,並且在這裡是會重新分配RT2地址的。所以這個RT2是一個全新的例項,與RT1沒有任何聯絡。所以它再怎麼改欄位的值也不會在影響RT1的欄位。
第二種情況:
這裡修改的只是欄位,所以修改後並沒有產生新的例項,那麼RT1、RT2對應的還是一個例項。修改任何一個的欄位值,另外一個會同時變化。
以上這些情況同樣適用於方法中的引數,如果在方法中重新例項了物件,那麼原始的物件在方法結束時不會做出任何改變,如果僅是改變物件例項的欄位,那麼沒有任何問題,可以修改。如果想要在方法中對物件的例項本身做修改,使用ref傳遞引數,這個可以做到。
3.引用型別測試(string)
using System;
using System.Runtime.InteropServices;
namespace ValueAndReference
{
class Program
{
static void Main(string[] args)
{
string STR1 ="aaaaa";
string STR2 = STR1;
Console.WriteLine("STR1 is " + STR1+",STR2 is "+STR2); //輸出STT1、STR2
STR2 = "bbbbb";
Console.WriteLine("STR1 is " + STR1 +",STR2 is " + STR2); //輸出STT1、STR2
}
}
}
最後在說下這個string(String),在這裡STR1並沒有隨著STR2的變化而變化,這麼看起來它確實像值型別。但實際上它確實是引用型別。為什麼出現這種情況那,這是因為string做了運算子過載這樣看起來是string更像是字串。可以把它這個賦值理解為new,這樣就和上邊的class一樣了,所以這種情況是正常的。