雖然項目中一直在使用類、結構體等類型,仔細琢磨,還真無法系統的說出個所以然。記錄一下類、結構體、類和結構體區別
一、類
對於類,大家都特別熟悉。簡單的介紹一下類的結構,然后記錄一下Class需要注意的地方。考慮到靜態類和非靜態類的區別。下面介紹的都是主要以非靜態類為前提介紹。
1、類的成員
類的數據和函數都屬於類的成員
(1)類的成員可以分為兩類:首先是類本身所聲明的。然后是從基類中繼承來的。如果在類聲明中沒有指定基類,則該類將繼承System.Object類的所有成員。
(2)C#中的類成員可以是任意類型,包括數組和集合。
(3)C#類成員修飾符。
private聲明私有成員,只有該類中的成員可以訪問,如果在聲明中沒有設置訪問修飾符,則默認是private。
public 聲明公有成員,訪問不受限制,允許從外部訪問。
protected聲明受保護成員,包含類和包含類派生的類可以訪問 ,對外界是隱藏的。
internal聲明內部成員,只能當前程序集可以訪問。
protected internal聲明受保護的內部成員,只能訪問當前程序集和包含類派生的類。

public class Teacher { private int _age; private string _name; public Teacher(int age, string name) { this.Age = age; this.Name = name ?? throw new ArgumentNullException(nameof(name)); } public int Age { get { return this._age; } set { this._age = value;} } public string Name { get { return this._name; } set { this._name = value; } } public string GetName() { return this.Name; } }
2、類的構造函數
概念:構造函數是類的一種特殊方法,每次創建類的實例都會調用它。在創建一個類的實例時,構造函數就像一個方法一樣被調用,但不返回值。
分類:實例構造函數,靜態構造函數,私有構造函數。
(1)實例構造函數的特征
a、構造函數的名字與類名相同。
b、使用 new 表達式創建類的對象或者結構(例如int)時,會調用其構造函數。並且通常初始化新對象的數據成員。
c、除非類是靜態的,否則會為沒有構造函數的類,自動生成一個默認構造函數,並使用默認值來初始化對象字段。
d、構造函數可以有參數,可以以重載的形式存在多個構造函數。

class Program { static void Main(string[] args) { Car car = new Car(); car.Print(); Car car1 = new Car(200000,"奧迪"); car1.Print(); Console.ReadKey(); } } public class Car { private int _price; private string _carName; public Car() { this.Price = 600000; this.CarName = "奔馳"; } public Car(int price,string carName) { this.Price = price; this.CarName = carName; } public int Price { get { return this._price; } set { this._price = value; } } public string CarName { get { return this._carName; } set { this._carName = value; } } public void Print() { Console.WriteLine("{0}價格是{1}元!",this.CarName,this.Price); } }
(2)靜態構造函數的特征
a、靜態構造函數不使用訪問修飾符或不能有參數。
b、不能直接調用靜態構造函數。
c、如果靜態構造函數引發異常,運行時將不會再次調用該函數,並且類型在程序運行所在的應用程序域的生存期內將保持未初始化。
d、用戶無法控制在程序中執行靜態構造函數的時間。

class Program { private static int _price=1000; public Program() { _price = 10; } static Program() { _price += 1; } static void Main(string[] args) { Console.WriteLine("房子價格{0}",_price);//程序啟動,靜態類就會占用內存,會先執行靜態構造函數 _price=1001 Program program = new Program(); Console.WriteLine("房子價格{0}",_price);//實例化類,會執行實例構造函數 _price=10 Console.ReadKey(); } }
在調用某類的靜態函數時真正的執行順序:
1、靜態變量 > 靜態構造函數 > 靜態函數
2、靜態變量 > 靜態構造函數 > 構造函數

public class A { public static readonly int x; static A() { //第二步,調用B.y,此處B.y = 0,因為int類型在初始化階段,會給賦默認值,默認值為0。最后x = 0 + 1(返回給第一步) x = B.y + 1; } } public class B { //第一步,調用A.x,然后執行類A的靜態構造函數,等待返回(第二步返回的A.x = 1,所有y = 1 + 1) public static int y = A.x + 1; public static void Main(string[] args) { //第三步,A.x = 1,y = 2。 Console.WriteLine("x:{0},y:{1}。", A.x, y); Console.ReadLine(); } }
(3)私有構造函數
私有構造函數是一種特殊的實例構造函數。 它通常用於只包含靜態成員的類中。 如果類具有一個或多個私有構造函數而沒有公共構造函數,則其他類(除嵌套類外)無法創建該類的實例。聲明空構造函數可阻止自動生成默認構造函數。 請注意,如果不對構造函數使用訪問修飾符,則在默認情況下它仍為私有構造函數。 但是,通常會顯式地使用 private 修飾符來清楚地表明該類不能被實例化。 其中單例模式就用到了私有構造函數的特性來保證類不會被實例化。

class Program { private Program() { // House house = new House();//注釋打開會報錯,錯誤信息:不可訪問,因為它受保護級別限制。因為私有構造函數無法在類的外面實例化。 } public class House { private int price; private House() { price = 1000; } static void Main(string[] args) { Program program = new Program();//嵌套類允許實例化 House house = new House(); Console.WriteLine("price{0}",house.price);//輸出1000 Console.ReadKey(); } } }
3、類的析構函數
(1)概念:析構函數(destructor) 與構造函數相反,當對象脫離其作用域時(例如對象所在的函數已調用完畢),系統自動執行析構函數。析構函數往往用來做“清理善后” 的工作(例如在建立對象時用new開辟了一片內存空間,應在退出前在析構函數中用delete釋放)。
(2)特征
一個類只能有一個析構函數。
不能在結構中定義析構函數。 只能對類使用析構函數。
無法繼承或重載析構函數。
無法調用析構函數。 它們是被自動調用的。
析構函數既沒有修飾符,也沒有參數。
(3)析構函數的作用
程序員無法控制何時調用析構函數,因為這是由垃圾回收器決定的。 垃圾回收器檢查是否存在應用程序不再使用的對象。 如果垃圾回收器認為某個對象符合析構,則調用析構函數(如果有)並回收用來存儲此對象的內存。 程序退出時也會調用析構函數。
通常,與運行時不進行垃圾回收的開發語言相比,C# 無需太多的內存管理。 這是因為 .NET Framework 垃圾回收器會隱式地管理對象的內存分配和釋放。 但是,當應用程序封裝窗口、文件和網絡連接這類非托管資源時,應當使用析構函數釋放這些資源。 當對象符合析構時,垃圾回收器將運行對象的 Finalize 方法
(4)實例

class Program { static void Main(string[] args) { RedApple redApple = new RedApple(); } } class Fruits //基類Fruits { ~Fruits()//析構函數 { Console.WriteLine("~Fruits()的析構函數"); } public Fruits() { Console.WriteLine("Fruits()的構造函數"); } } class Apple:Fruits { ~Apple() { Console.WriteLine("~Apple()的析構函數"); } public Apple() { Console.WriteLine("Apple()的構造函數"); } } class RedApple:Apple { ~RedApple() { Console.WriteLine("~RedApple()的析構函數"); } public RedApple() { Console.WriteLine("RedApple()的構造函數"); } }
運行結果
總結:
1>程序運行時,這三個類的析構函數將自動被調用,調用順序是按照從派生程度最大的(~C())到派生程度最小的(~A())次序調用的,和構造函數的調用順序正好相反。則可以得出,當用戶創建類的對象是調用構造函數,而當該對象已經調用完畢時,使用析構函數
2>析構函數(destructor) 與構造函數相反,當對象脫離其作用域時(例如對象所在的函數已調用完畢),系統自動執行析構函數。
3>析構函數往往用來做“清理善后” 的工作(例如在建立對象時用new開辟了一片內存空間,應在退出前在析構函數中用delete釋放).
4>析構函數名也應與類名相同,只是在函數名前面加一個波浪符~,例如~A( ),以區別於構造函數。它不能帶任何參數,也沒有返回值(包括void類型)。只能有一個析構函數,不能重載。
5>如果用戶沒有編寫析構函數,編譯系統會自動生成一個缺省的析構函數,它也不進行任何操作。所以許多簡單的類中沒有用顯式的析構函數。
4、類的繼承
(1)類的注意事項
1>注意靜態類和非靜態類的特征以及使用方式的不同。可以參見C#基礎知識之靜態和非靜態
2>派生類只能從一個類中繼承,從多個基類中派生一個類這往往會帶來許多問題,從而抵消了這種靈活性帶來的優勢。
3>C#中,派生類從它的直接基類中繼承成員:方法、域、屬性、事件、索引指示器。除了構造函數和析構函數,派生類隱式地繼承了直接基類的所有成員。
(2)類的重寫 override和virtual
1>重寫父類的方法要用到override關鍵字(具有override關鍵字修飾的方法是對父類中同名方法的新實現)。
2>要重寫父類的方法,前提是父類中該要被重寫的方法必須聲明為virtual或者是abstract類型。給父類中要被重寫的方法添加virtual關鍵字表示可以在子類中重寫它的實現。
3>virtual關鍵字用於將方法定義為支持多態,有virtual關鍵字修飾的方法稱為“虛擬方法”。
4>virtual:不是必須被子類重寫的方法,父類必須給出實現,子類可以重寫(使用override,new,或無特殊標志的普通方法),也可以不重寫該方法。
5>new:重寫父類方法時,父類可以使用virtual,override,new之一聲明,也可以是沒有關鍵字的普通方法,運行時會根據引用類型選擇調用父類還是子類方法,重寫父類方法時,使用new關鍵字與使用沒有關鍵字的普通方法的等效的,但是后者會給出編譯警告。

class Program { static void Main(string[] args) { Dog dog = new Dog("狗"); dog.ShowName(); Console.ReadKey(); } } class Animal { private string _name; public string Name { get { return _name; } set { _name = value; } } public virtual void ShowName() { Console.WriteLine("動物列表:"); } } class Dog : Animal { public Dog(string name) { base.Name = name; } public override void ShowName() { base.ShowName(); Console.WriteLine("動物的名稱:{0}",base.Name); } }

class Program { static void Main(string[] args) { Dog dog = new Dog("狗"); dog.ShowName(); Console.ReadKey(); } } abstract class Animal { private string _name; public string Name { get { return _name; } set { _name = value; } } public abstract void ShowName(); } class Dog : Animal { public Dog(string name) { base.Name = name; } public override void ShowName() { Console.WriteLine("動物的名稱:{0}",base.Name); } }
(3)類的重載
方法名相同,但是參數不同,參數的個數不同或者類型不同,滿足一個就可以(和返回值無關,和參數的類型和個數有關)。注意:返回值可以相同也可以不同。當參數個數相同而參數類型不同的時候,可以考慮使用泛型(C#基礎知識之泛型),提高代碼的復用性。

class Dog { public void Show() { } public void Show(string name) { } public void Show(string name,int weight) { } public void Show(int weight,int age) { } }
(4)抽象類
修飾類名為抽象類,修飾方法為抽象方法。如果一個類為抽象類,則這個類只能是其他某個類的基類。抽象方法在抽象類中沒有函數體。抽象類中的抽象方法是沒有方法體的,繼承其的子類必須實現抽象類的抽象方法。
1>抽象類的特征
u 抽象類不能實例化。
u 抽象類的派生類必須實現所有抽象方法。
u 抽象類的抽象方法是沒有方法體的,繼承抽象類的子類必須得實現所有抽象方法。
2>抽象方法的特征
u 抽象方法是隱式的虛方法。
u 只允許在抽象類中聲明抽象方法。
u 抽象方法在抽象類中沒有方法體。
u 在抽象方法聲明中,不能使用static和virtual修飾符。
(5)密封類
密封類不能被繼承,所以sealed和abstract不能共用。
1>密封類

class Program { static void Main(string[] args) { B b = new B(); b.x = 10; b.y = 20; Console.WriteLine("b.x {0}, b.y {1}",b.x,b.y); Console.ReadKey(); } } public sealed class B { public int x; public int y; }
2>密封方法
對類中的方法可以使用sealed修飾符,我們稱該方法為密封方法。不是類的每個成員方法都可以作為密封方法,密封方法必須對基類的虛方法進行重載,提供具體的實現方法。所以,在方法的聲明中,sealed修飾符總是和override修飾符同時使用,sealed修飾符位於override修飾符前面。

class Program { static void Main(string[] args) { A a1 = new A();//實例化A類的對象,調用A類中的方法 a1.Fun1(); a1.Fun2(); A a2 = new B();//實例化B類的對象,調用B類中的方法,注意前邊 new作為修飾符的用法(很重要!) a2.Fun1(); a2.Fun2(); A a3 = new C();//實例化C類的對象,調用C類中的方法,因為FUN1()在B類中使用密封修飾符,所以C類中無法進行重寫,所以調用B.Fun1() a3.Fun1(); a3.Fun2(); B b1 = new B();//實例化B類的對象,調用B類中的方法 b1.Fun1(); b1.Fun2(); B b2 = new C();//實例化C類的對象,因為FUN1()在B類中使用密封修飾符,所以C類中無法進行重寫,所以調用B.Fun1() b2.Fun1(); b2.Fun2(); C c1 = new C();//因為FUN1()在B類中使用密封修飾符,所以C類中無法進行重寫,所以調用B.Fun1() c1.Fun1(); c1.Fun2(); Console.ReadKey(); } } public class A { public virtual void Fun1() { Console.WriteLine("base.Fun1();"); } public virtual void Fun2() { Console.WriteLine("base.Fun2();"); } } public class B:A { public sealed override void Fun1() { Console.WriteLine("B.Fun1();"); } public override void Fun2() { Console.WriteLine("B.Fun2();"); } } public class C : B { public override void Fun2() { Console.WriteLine("C.Fun2()"); } }
(6)New new的關鍵詞有三個功能
u 作為運算符: 用於創建對象和調用構造函數。 Class obj = new Class();
u 作為修飾符 : 在用作修飾符時,new 關鍵字可以顯式隱藏從基類繼承的成員。具體地說,new聲明的方法,當使用子類的類型來調用的時候,它會運行子類的函數,而如果類型是基類的話,被隱藏的基類函數會被調用。而子類中函數使用override的時候,則當使用子類的類型來調用的是,它會運行子類的函數,類型是基類的時候,仍會調用子類函數。

class Program { static void Main(string[] args) { A a1 = new A();//如果類型是基類的話,被隱藏的基類函數會被調用 a1.ShowInfo(); System.Console.WriteLine("----------"); A a2 = new B(); a2.ShowInfo();//如果類型是基類的話,被隱藏的基類函數會被調用 System.Console.WriteLine("----------"); B a3 = new B(); a3.ShowInfo();//如果類型是子類的話,它會運行子類的函數 System.Console.WriteLine("----------"); A a4 = new C();//子類中函數使用override的時候,類型是基類的時候,仍會調用子類函數。 a4.ShowInfo(); System.Console.WriteLine("----------"); C a5 = new C(); a5.ShowInfo();//子類中函數使用override的時候,則當使用子類的類型來調用的是,它會運行子類的函數 System.Console.WriteLine("----------"); Console.ReadKey(); } class A { public virtual void ShowInfo() { System.Console.WriteLine("Four wheels and an engine."); } } class B : A { public new void ShowInfo() { System.Console.WriteLine("A roof that opens up."); } } class C : A { public override void ShowInfo() { System.Console.WriteLine("Carries seven people."); } } }
u 作為約束: 用於在泛型聲明中約束可能用作類型參數的參數的類型。new約束指定泛型類聲明中的任何類型參數都必須具有公共的無參數構造函數

class Program { static void Main(string[] args) { AFactory<A> aFactory = new AFactory<A>(); //此處編譯器會檢查Employee是否具有公有的無參構造函數。 //若沒有則會有The Employee must have a public parameterless constructor 錯誤。 Console.WriteLine("{0}'ID is {1}.", aFactory.GetName().Name, aFactory.GetName().ID); Console.ReadKey(); } } public class A { private string name; private int id; public A() { name = "dachong"; id = 100; } public A(string s, int i) { name = s; id = i; } public string Name { get { return name; } set { name = value; } } public int ID { get { return id; } set { id = value; } } } class AFactory<T> where T : new() { public T GetName() { return new T(); } }
二、結構體
1、概念:C# 中,結構體是值類型數據結構。它使得一個單一變量可以存儲各種數據類型的相關數據。struct 關鍵字用於創建結構體。結構體是用來代表一個記錄。
2、特點:C#中結構體的特點
(1)結構可帶有方法、字段、索引、屬性、運算符方法和事件。
(2)結構可定義構造函數,但不能定義析構函數。但是,您不能為結構定義默認的構造函數。默認的構造函數是自動定義的,且不能被改變。
(3)與類不同,結構不能繼承其他的結構或類。
(4)結構可實現一個或多個接口。
(5)結構成員不能指定為 abstract、virtual 或 protected。
(6)當您使用 New 操作符創建一個結構對象時,會調用適當的構造函數來創建結構。與類不同,結構可以不使用 New 操作符即可被實例化。如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被賦值,對象才被使用。

struct Books { private string title; private string author; private string subject; private int book_id; public void getValues(string t, string a, string s, int id) { title = t; author = a; subject = s; book_id =id; } public void display() { Console.WriteLine("Title : {0}", title); Console.WriteLine("Author : {0}", author); Console.WriteLine("Subject : {0}", subject); Console.WriteLine("Book_id :{0}", book_id); } }; class Program { Books Book1 = new Books(); Book1.getValues("C Programming", "Nuha Ali", "C Programming Tutorial",6495407); Book1.display(); Console.ReadKey(); }
三、類和結構體區別
1、結構是值類型,它在棧中分配空間;而類是引用類型,它在堆中分配空間,棧中保存的只是引用。
2、C# 中的簡單類型,如int、double、bool等都是結構類型。如果需要的話,甚至可以使用結構類型結合運算符運算重載,再為 C# 語言創建出一種新的值類型來。由於結構是值類型,並且直接存儲數據,因此在一個對象的主要成員為數據且數據量不大的情況下,使用結構會帶來更好的性能。因為結構是值類型,因此在為結構分配內存,或者當結構超出了作用域被刪除時,性能會非常好,因為他們將內聯或者保存在堆棧中。當把一個結構類型的變量賦值給另一個結構時,對性能的影響取決於結構的大小,如果結構的數據成員非常多而且復雜,就會造成損失。
3、在結構體中可以聲明字段,但是聲明字段的時候是不能給初始值的。
4、類中如果我們沒有為類寫任意的構造函數,那么C#編譯器在編譯的時候會自動的為這個類生成1個無參數的構造函數.我們將這個構造函數稱之為隱式構造函數 但是一旦我們為這個類寫了任意的1個構造函數的時候,這個隱式的構造函數就不會自動生成了。隱式的無參數的構造函數在結構中無論如何都是存在的,所以程序員不能手動的為結構添加1個無參數的構造函數,否則會報錯。
5、C#語法規定在結構體的構造函數中,必須要為結構體的所有字段賦值。注意在結構體的構造函數中我們為屬性賦值,不認為是在對字段賦值,所以我們在構造函數中要直接為字段賦值.
四、類和結構體的使用場所
1、當堆棧的空間很有限,且有大量的邏輯對象時,創建類要比創建結構好一些;
2、對於點、矩形和顏色這樣的輕量對象,假如要聲明一個含有許多個顏色對象的數組,則CLR需要為每個對象分配內存,在這種情況下,使用結構的成本較低;
3、在表現抽象和多級別的對象層次時,類是最好的選擇,因為結構不支持繼承。