C# 結構體與類的區別

陽塵子發表於2017-05-03

經常聽到有朋友在討論C#中的結構與類有什麼區別.正好這幾日閒來無事,自己總結一下,希望大家指點.

1. 首先是語法定義上的區別啦,這個就不用多說了.定義類使用關鍵字class 定義結構使用關鍵字struct.在語法上其實類和結構有著很多相似的地方.

   定義類的語法    

 1 class Person
 2 {
 3    private string name;
 4    private int age;
 5    
 6    public void SayHi()
 7    {
 8        Console.WriteLine("Hello,My Name is "+this.name+",My Age is "+this.age);
 9    }
10 }

    定義結構的語法. 

 1     struct Rectangle
 2     {
 3         private int width;
 4         private int height;
 5          
 6         public int GetArea()
 7         {
 8             return this.width * height;
 9         }
10     }

    從語法上來看.它們的語法都大同小異,類裡面的成員幾乎都可以定義在結構體中,但是解構函式除外.這是為什麼呢?後面解答.

2. 雖然我們說它們的語法極其相似,但是它們在語法還是有幾點區別的.

   a.在結構體中可以宣告欄位,但是宣告欄位的時候是不能給初始值的.所以當我們試圖這樣寫程式碼的時候,C#編譯器在將原始碼編譯成程式集的是會提示語法錯誤.

       

   我們知道如果我們在類中宣告1個欄位的同時給這個欄位賦初始值,這樣是可以滴,就像下面這樣. 

class Person
{
   private string name ="jack";
}

    但是如果像下面這樣確實不行滴.宣告完1個欄位,再為這個欄位賦值,就像下面這樣. 

1 class Person
2 {
3    private string name;
4    name="jack";
5 }

 

    所以我們說,在類下面只能直接定義類的成員,只能定義.  比如定義成員欄位,屬性 方法 建構函式等等.上面那樣的程式碼name="jack"這樣的程式碼我們稱之為“執行程式碼”,意思就是說這些程式碼只有在被執行的時候才會有效果.而你試想一下,那麼這些程式碼什麼時候被執行呢? 建立類的物件的時候? 那還用得著建構函式嗎? 經常看到一些初學者在類的下面直接寫這樣程式碼.

 

    但是又有人會說了.誒, 那麼為什麼在宣告類的欄位的時候可以賦值呢?賦值表示式也是1個執行程式碼啊?為什麼這樣就不報錯呢?給你看看下面的程式碼 你就會知道其中的真相了.

     

     當我們使用C#編譯器將這段程式碼編譯為程式集的時候,看看微軟為我們生成的程式碼吧.

     

     展開建構函式,看看這裡面有什麼蹊蹺吧!

     

     是的,C#編譯器在編譯的時候,如果我們宣告欄位的時候為欄位賦值,那麼為欄位賦值的程式碼C#編譯器在編譯的時候會將賦值的程式碼放到建構函式中去,其實嚴格意義上來說,類的欄位也是不能有初始值的.只不過微軟在背後幫我們做了點事情,我們不知道而已.

     所以,不管在類和結構中,執行程式碼一定要寫在方法中.不能直接寫在結構或者類的下面.因為當執行程式碼寫在方法中了,那麼這些執行程式碼的執行時機才可以確定,就是這個方法被呼叫的時候了.

    從上面的內容,我們可以看出.其實從本質上來說,類和結構的欄位都是不能有初始值的.只不過微軟在語法上允許我們在定義類的欄位的時候為其賦值.但是背後微軟其實是把賦值的執行程式碼放到建構函式中去執行的. 而結構體微軟卻不幫我們這樣做.至於這其中是什麼原因.查了些資料,也看了園子裡其他博友的文章,感覺都不能說服我,但是自己也想不出1個確切的理由微軟為什麼要這樣做.那就先放著吧,希望參透其中原理的童鞋能指點.

 

    b. 關於建構函式.

    首先,關於隱式建構函式.我們知道,在1個類中如果我們沒有為類寫任意的建構函式,那麼C#編譯器在編譯的時候會自動的為這個類生成1個無引數的建構函式.我們將這個建構函式稱之為隱式建構函式 但是一旦我們為這個類寫了任意的1個建構函式的時候,這個隱式的建構函式就不會自動生成了.

    在結構中,就不是這樣了,在結構中隱式的建構函式無論如何都存在.看看程式碼吧.

    在下面的程式碼中 我們為結構體寫了1個帶引數的建構函式.如下.

    

    我們使用new關鍵字來建立結構體物件,我們發現呼叫建構函式的時候,提示是有兩個建構函式的.多了1個無引數的建構函式.

     

   那麼 我們再想,能不能手動的寫1個無引數的建構函式呢?我們懷著無比激動的心情,試一下.

   

  結果是華麗麗的報錯了.所以我們得出結論. 隱式的無引數的建構函式在結構中無論如何都是存在的,所以程式設計師不能手動的為結構新增1個無引數的建構函式.

  關於建構函式當然還不僅僅如此.我們知道在類的建構函式中我們可以寫一些任意的程式碼(前提是符合C#語法啦),在結構體的建構函式中雖然也可以寫任意的程式碼.但是C#語法規定在結構體的建構函式中,必須要為結構體的所有欄位賦值.看看下面的程式碼吧.

   

   啊哦.....報錯了.....

   我們也知道,在結構中還可以定義屬性,所以有童鞋就這樣寫啦.看下面程式碼.

   

   這個錯誤,仍然提示我們在建構函式中沒有為所有的欄位賦值,這是很多童鞋遇到的問題,誒,不是要在建構函式中為所有的欄位賦值麼?我現在賦值了啊。為什麼還是提示沒有賦值呢? 我們在建構函式中為屬性賦值 而屬性又為欄位賦值,為什麼這樣就不行呢? 原因很簡單.因為語法要求我們為所有的欄位賦值,雖然這裡我們看得出來為屬性賦值其實屬性再把值賦值給欄位, 我們說屬性是對欄位的操作,但是一定是這樣的嗎?我們完全可以在屬性的set塊裡面什麼都不寫,如果什麼都不寫,那麼屬性還是在操作欄位嗎? 所以屬性不一定是在操作欄位的,在結構體的建構函式中我們為屬性賦值,不認為是在對欄位賦值,所以我們在建構函式中要直接為欄位賦值.

  c.建立結構體物件的方式.

   建立結構體物件可以不使用new關鍵字.直接宣告1個變數就可以.但是這樣的話,結構體物件中的欄位是沒有初始值的,所以在使用欄位之前必須要為這個欄位賦值.

   

   原因很簡單.因為宣告的時候就不能給初始值,雖然建構函式中為物件的欄位賦值,但是此種方式建立結構體物件,沒有呼叫建構函式,所以必須要程式設計師在使用之前手動賦值。下面這樣就可以了.

   

  另外1種建立結構體物件的方式和類一樣,使用new關鍵字來建立,與不使用new關鍵字建立不同的是,通過使用new關鍵字建立結構體物件後,這個結構體物件的欄位就已經有值了.原因不難理解,new關鍵字呼叫了建構函式,而結構體建構函式要求必須要為所有的欄位賦值.

  

  所以,我們不難猜出.結構體的無引數的建構函式做了什麼事情,在無引數的建構函式中為所有的欄位賦值,值型別的欄位賦值0,給引用型別的欄位賦值null.

  d. 結構體不能從另外1個結構或者類繼承,但是可以實現介面.特殊的是.雖然結構不能從別的類或者結構繼承,但是所有的結構都預設從ValueType類繼承,ValueType類再從Object類繼承.所以結構體物件仍然擁有超類Object的成員.看看下面的微軟生成的程式碼就知道了.

    

3. 它們之間最大的區別 是結構體是值型別 類是引用型別.

   結構體是值型別,當其作為1個區域性變數的時候,變數是儲存在棧空間中的,其物件的欄位直接儲存在這個變數中的.就像下面這樣.

   

   與引用型別的類不一樣,引用型別的變數中儲存的是物件在堆空間中的地址,所以當我們傳遞1個引用型別的變數的時候,其實傳遞的是變數的值(物件的地址) 傳遞完以後 對變數的修改會影響到另外1個變數指向的物件的值.

 

4. 最後 談一下什麼時候使用結構,什麼使用類.

   我們知道,結構儲存在棧中,而棧有1個特點,就是空間較小,但是訪問速度較快,堆空間較大,但是訪問速度相對較慢.所以當我們描述1個輕量級物件的時候,可以將其定義為結構來提高效率.比如點,矩形,顏色,這些物件是輕量級的物件,因為描述他們,只需要少量的欄位。當描述1個重量級物件的時候,我們知道類的物件是儲存在堆空間中的,我們就將重量級物件定義為類. 他們都表示可以包含資料成員和函式成員的資料結構。與類不同的是,結構是值型別並且不需要堆分配。結構型別的變數直接包含結構的資料,而類型別的變數包含對資料的引用(該變數稱為物件)。 struct 型別適合表示如點、矩形和顏色這樣的輕量物件。儘管可能將一個點表示為類,但結構在某些方案中更有效。在一些情況下,結構的成本較低。例如,如果宣告一個含有 1000 個點物件的陣列,則將為引用每個物件分配附加的記憶體。所以結構適合表示1個輕量級物件.

   基於另外1個理由我也會使用結構. 我們在變數傳值的時候,我就是希望傳遞物件的拷貝,而不是物件的引用地址,那麼這個時候也可以使用結構了.

 

  以上只是個人總結,難免會有些地方有瑕疵,歡迎大家指正,謝謝.!

相關文章