學會總結,最有效的學習方式

C#中Class和Struct的區別

  • class(類)是面向對象編程的基本概念,是一種自定義數據結構類型,通常包含字段、屬性、方法、屬性、構造函數、索引器、操作符等。在.NET中,所有的類都最終繼承自System.Object類,因此是一種引用類型,也就是說,new一個類的實例時,在堆棧(stack)上存放該實例在託管堆(managed heap)中的地址,而實例的值保存在託管堆(managed heap)中。
  • struct(結構)是一種值類型,用於將一組相關的變量組織爲一個單一的變量實體 。所有的結構都繼承自System.ValueType類,因此是一種值類型,也就是說,struct實例在創建時分配在線程的堆棧(stack)上,它本身存儲了值。所以在使用struct時,我們可以將其當作int、char這樣的基本類型類對待。
  • Class是引用類型,可以賦值爲null,struct是值類型,不能賦值爲null。
  • 對class的傳遞實際上是引用傳遞,既將類實例的引用傳遞給對方。struct傳遞的是值。
  • class可以有無參構造器,不需要初始化全部字段,而struct只能使用有參構造器,必須初始化該結構體全部字段。
  • Class字段可以初始化,而struct不能初始化。
  • class實例化時必須使用new關鍵字(靜態類除外),Struct不需要。
  • Class支持繼承和多態,Struct不支持,但是Struct支持實現接口,struct內部函數不能聲明爲abstract和virtual,但是可以使用override關鍵字覆蓋基類System.ValueType中的方法。
  • class默認是public。struct默認是private。
  • Struct總是隱式密封的,因此在定義時不能使用sealed和abstract關鍵字。
  • Class有面向對象的拓展優勢,struct有性能優勢。

Static在C#中的作用

  • 用於修飾變量,每次重新使用該變量所在方法、類時,被static修飾的變量的值爲程序運行中最後一次爲該變量賦值時的值。
  • 用於修飾方法,表示此方法屬於該類,而不屬於該類的所有實例。
  • 用於Class前,說明此類無法創建實例。不能在靜態類中聲明非靜態成員。

Static在C++中的作用

  • 隱藏。所有未加static前綴的全局變量和函數都具有全局可見性。對於函數來講,Static的作用僅限於隱藏。
  • 保持變量內容的持久。全局變量和靜態變量存儲在靜態數據區,會在程序剛開始運行時就完成初始化,也是唯一一次初始化。如果在函數內部定義static變量,生存週期爲整個源程序,但是其作用域僅限於該函數內部,在函數外部不能使用該變量。
  • 默認初始化爲0。因爲靜態變量存儲在靜態數據區,靜態數據區所有字節默認爲0x00。
  • C++中的類成員聲明static

     在類中聲明static變量或者函數時,初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員,這樣就出現以下作用:

    (1)類的靜態成員函數是屬於整個類而非類的對象,所以它沒有this指針,這就導致 了它僅能訪問類的靜態數據和靜態成員函數。      

    (2)不能將靜態成員函數定義爲虛函數。      

    (3)由於靜態成員聲明於類中,操作於其外,所以對其取地址操作,就多少有些特殊 ,變量地址是指向其數據類型的指針 ,函數地址類型是一個“nonmember函數指針”。

    (4)由於靜態成員函數沒有this指針,所以就差不多等同於nonmember函數,結果就 產生了一個意想不到的好處:成爲一個callback函數,使得我們得以將C++和C-based X W indow系統結合,同時也成功的應用於線程函數身上。 (這條沒遇見過)  

    (5)static並沒有增加程序的時空開銷,相反她還縮短了子類對父類靜態成員的訪問 時間,節省了子類的內存空間。      

    (6)靜態數據成員在<定義或說明>時前面加關鍵字static。      

    (7)靜態數據成員是靜態存儲的,所以必須對它進行初始化。 (程序員手動初始化,否則編譯時一般不會報錯,但是在Link時會報錯誤) 

    (8)靜態成員初始化與一般數據成員初始化不同:

    初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆;
    初始化時不加該成員的訪問權限控制符private,public等;        
    初始化時使用作用域運算符來標明它所屬類;
               所以我們得出靜態數據成員初始化的格式:
    <數據類型><類名>::<靜態數據成員名>=<值>

    (9)爲了防止父類的影響,可以在子類定義一個與父類相同的靜態變量,以屏蔽父類的影響。這裏有一點需要注意:我們說靜態成員爲父類和子類共享,但我們有重複定義了靜態成員,這會不會引起錯誤呢?不會,我們的編譯器採用了一種絕妙的手法:name-mangling 用以生成唯一的標誌。

泛型約束

  • 首先,我們需要知道反省究竟是什麼?

泛型是C#語言和公共語言運行庫(CLR)中的一個新功能,它將類型參數的概念引入.NET Framework。類型參數使得設計某些類和方法成爲可能,例如,通過使用泛型類型參數T,可以大大簡化類型之間的強制轉換或裝箱操作的過程(下一篇將說明如何解決裝箱、拆箱問題)。說白了,泛型就是通過參數化類型來實現在同一份代碼上操作多種數據類型,利用“參數化類型”將類型抽象化,從而實現靈活的複用。

使用泛型給代碼帶來的5點好處:1、可以做大限度的重用代碼、保護類型的安全以及提高性能。

                  2、可以創建集合類。

                  3、可以創建自己的泛型接口、泛型方法、泛型類、泛型事件和泛型委託。

                  4、可以對泛型類進行約束,以訪問特定數據類型的方法。

                  5、關於泛型數據類型中使用的類型的信息,可在運行時通過反射獲取。

六種類型的約束:

T:結構                       類型參數必須是值類型。可以指定除Nullable以外的任何值類型。

T:類                           類型參數必須是引用類型,包括任何類、接口、委託或數據類型。

T:new()                  類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new()約束必須最後制定。

T:<基類名>                 類型參數必須是指定的基類或派生自指定的基類。

T:<接口名稱>              類型參數必須是指定的接口或實現指定的接口。可以指定多個接口約束。約束接口也可以是泛型的。

T:U                              爲 T 提供的類型參數必須是爲 U 提供的參數或派生自爲 U 提供的參數。這稱爲裸類型約束。

例子:

1.接口約束。

例如,可以聲明一個泛型類 MyGenericClass,這樣,類型參數 T 就可以實現 IComparable<T> 接口:

public class MyGenericClass<T> where T:IComparable { } 

  2.基類約束。

  指出某個類型必須將指定的類作爲基類(或者就是該類本身),才能用作該泛型類型的類型參數。這樣的約束一經使用,就必須出現在該類型參數的所有其他約束之前。

class MyClassy<T, U>
where T : class
where U : struct
{

}

  3.構造函數約束。

  以使用 new 運算符創建類型參數的實例;但類型參數爲此必須受構造函數約束 new() 的約束。new() 約束可以讓編譯器知道:提供的任何類型參數都必須具有可訪問的無參數(或默認)構造函數。new() 約束出現在 where 子句的最後。

public class MyGenericClass <T> where T: IComparable, new()
{
         T item = new T();
}

  4.對於多個類型參數,每個類型參數都使用一個 where 子句。

 

interface MyI { }
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
    public void Add(TKey key, TVal val)
    {

    }
}

 

  5.還可以將約束附加到泛型方法的類型參數。

public bool MyMethod<T>(T t) where T : IMyInterface { }  

  6. 裸類型約束

  用作約束的泛型類型參數稱爲裸類型約束。當具有自己的類型參數的成員函數需要將該參數約束爲包含類型的類型參數時,裸類型約束很有用。

class List<T>
{
    void Add<U>(List<U> items) where U : T {}
}

  爲什麼要有約束呢?

  當一個泛型參數沒有任何約束時,它可以進行的操作和運算是非常有限的,因爲不能對實參做任何類型上的保證,這時候就需要用到泛型的約束。泛型的主要約束和次要約束都是指泛型的實參必須滿足一定的規範。C#編譯器在編譯的過程中可以根據約束來檢查所有泛型類型的實參並確保其滿足約束條件。

  一個泛型參數可以至多擁有一個主要約束,主要約束可以是一個引用類型、class或者struct。如果指定一個引用類型,則實參必須是該類型或者該類型派生類型。class規定實參必須是一個引用類型。struct規定了參數必須是一個之類新。以下代碼是泛型參數主要約束的示例。

 

using System;

namespace Test
{
    class GenericPrimaryConstraint
    {
        static void Main()
        {
            Console.Read();
        }
    }
    //主要約束限定T繼承自Exception類型
    public class ClassT1<T> where T : Exception
    {
        private T myException;
        public ClassT1(T t)
        {
            myException = t;
        }
        public override string ToString()
        {
            //主要約束保證了myException擁有Source成員
            return myException.Source;
        }
    }
    //主要約束限定T是引用類型
    public class ClassT2<T> where T : class
    {
        private T myT;
        public void Clear()
        { 
            //T是引用類型,可以置null
            myT = null;
        }
    }
    //主要約束限定T是值類型
    public class ClassT3<T> where T : struct
    {
        private T myT;
        public override string ToString()
        {
            //T是值類型,不會發生NullReferenceException異常
            return myT.ToString();
        }
    }
}

  以上代碼,泛型參數具備了主要約束後,就能夠在類型中對其進行一定的操作,否則任何算法就只能基於一個System.Object類型的成員。

  可以說,主要約束是實參類型的限定,而相對的次要約束,則是指實參實現的接口的限定。對於一個泛型類型,可以有0至無限的次要約束,次要約束規定了參數必須實現所有次要約束中規定的接口。次要約束的語法和主要約束基本一致,區別僅在於提供的不是一個引用類型而是一個或多個接口。

  ps:同時擁有主要約束和次要約束的泛型參數,表示實參必須同時滿足主要約束和次要約束。

 

這篇文章是自己在面試過程中發現自己的不足,從其他大神哪裏Copy過來,以備自己學習並希望能給有和我一樣的小白一點幫助。如果對原作者有冒犯,希望告知。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章