一.構造函數與析構函數的原理程序員
做爲比C更先進的語言,C#提供了更好的機制來加強程序的安全性。
C#編譯器具備嚴格的類型安全檢查功能,它幾乎能找出程序中全部的語法問題,這的確幫了程序員的大忙。
可是程序經過了編譯檢查並不表示錯誤已經不存在了,
在「錯誤」的你們庭裏,「語法錯誤」的地位只能算是冰山一角。級別高的錯誤一般隱藏得很深,不容易發現。
根據經驗,很多難以察覺的程序錯誤是因爲變量沒有被正確初始化或清除形成的,
而初始化和清除工做很容易被人遺忘。
微軟利用面向對象的概念在設計C#語言時充分考慮了這個問題並很好地予以解決:
把對象的初始化工做放在構造函數中,把清除工做放在析構函數中。當對象被建立時,構造函數被自動執行。
當對象消亡時,析構函數被自動執行。這樣就不用擔憂忘記對象的初始化和清除工做。
二.構造函數在C#中的運用
構造函數的名字不能隨便起,必須讓編譯器認得出才能夠被自動執行。
它的命名方法既簡單又合理:讓構造函數與類同名。
除了名字外,構造函數的另外一個特別之處是沒有返回值類型,這與返回值類型爲void的函數不一樣。
若是它有返回值類型,那麼編譯器將不知所措。
在你能夠訪問一個類的方法、屬性或任何其它東西以前, 第一條執行的語句是包含有相應類的構造函數。
甚至你本身不寫一個構造函數,也會有一個缺省構造函數提供給你。
class TestClass
{
public TestClass(): base() {} // 由CLR提供
}
下面列舉了幾種類型的構造函數
1)缺省構造函數
class TestClass
{
public TestClass(): base() {}
}
上面已介紹,它由系統(CLR)提供。
2)實例構造函數
實例構造函數是實現對類中實例進行初始化的方法成員。如:
using System;
class Point
{
public double x, y;
public Point()
{
this.x = 0;
this.y = 0;
}
public Point(double x, double y)
{
this.x = x;
this.y = y;
}
…
}
class Test
{
static void Main()
{
Point a = new Point();
Point b = new Point(3, 4); // 用構造函數初始化對象
…
}
}
聲明瞭一個類Point,它提供了兩個構造函數。它們是重載的。
一個是沒有參數的Point構造函數和一個是有兩個double參數的Point構造函數。
若是類中沒有提供這些構造函數,那麼會CLR會自動提供一個缺省構造函數的。
但一旦類中提供了自定義的構造函數,如Point()和Point(double x, double y),
則缺省構造函數將不會被提供,這一點要注意。
3) 靜態構造函數
靜態構造函數是實現對一個類進行初始化的方法成員。它通常用於對靜態數據的初始化。
靜態構造函數不能有參數,不能有修飾符並且不能被調用,當類被加載時,類的靜態構造函數自動被調用。
如:
using System.Data;
class Employee
{
private static DataSet ds;
static Employee()
{
ds = new DataSet(...);
}
...
}
聲明瞭一個有靜態構造函數的類Employee。
注意靜態構造函數只能對靜態數據成員進行初始化,而不能對非靜態數據成員進行初始化。
可是,非靜態構造函數既能夠對靜態數據成員賦值,也能夠對非靜態數據成員進行初始化。
若是類僅包含靜態成員,你能夠建立一個private的構造函數:private TestClass() {…},
可是private意味着從類的外面不可能訪問該構造函數。
因此,它不能被調用,且沒有對象能夠被該類定義實例化。
以上是幾種類型構造函數的簡單運用,
下面將重點介紹一下在類的層次結構中(即繼承結構中)基類和派生類的構造函數的使用方式。
派生類對象的初始化由基類和派生類共同完成:
基類的成員由基類的構造函數初始化,派生類的成員由派生類的構造函數初始化。
當建立派生類的對象時,系統將會調用基類的構造函數和派生類的構造函數,
構 造函數的執行次序是:先執行基類的構造函數,再執行派生類的構造函數。
若是派生類又有對象成員,則,先執行基類的構造函數,再執行成員對象類的構造函數,
最後執行派生類的構造函數。
至於執行基類的什麼構造函數,缺省狀況下是執行基類的無參構造函數,
若是要執行基類的有參構造函數,則必須在派生類構造函數的成員初始化表中指出。
如:
class A
{ private int x;
public A( ) { x = 0; }
public A( int i ) { x = i; }
};
class B : A
{ private int y;
public B( ) { y = 0; }
public B( int i ) { y = i; }
public B( int i, int j ):A(i) { y = j; }
};
B b1 = new B(); //執行基類A的構造函數A(),再執行派生類的構造函數B()
B b2 = new B(1); //執行基類A的構造函數A(),再執行派生類的構造函數B(int)
B b3 = new B(0,1); //執行執行基類A的構造函數A(int) ,再執行派生類的
構造函數B(int,int)
在這裏構造函數的執行次序是必定要分析清楚的。
另外,若是基類A中沒有提供無參構造函數public A( ) { x = 0; },
則在派生類的全部構造函數成員初始化表中必須指出基類A的有參構造函數A(i),
以下所示:
class A
{ private int x;
public A( int i ) { x = i; }
};
class B : A
{ private int y;
public B():A(i) { y = 0; }
public B(int i):A(i) { y = i; }
public B(int i, int j):A(i) { y = j; }
};
三.析構函數和垃圾回收器在C#中的運用
析構函數是實現銷燬一個類的實例的方法成員。
析構函數不能有參數,不能任何修飾符並且不能被調用。
因爲析構函數的目的與構造函數的相反,就加前綴‘~’以示區別。
雖然C#(更確切的說是CLR)提供了一種新的內存管理機制---自動內存管理機制(Automatic memory management),
資源的釋放是能夠經過「垃圾回收器」 自動完成的,通常不須要用戶干預,
但在有些特殊狀況下仍是須要用到析構函數的,如在C#中非託管資源的釋放。
資源的釋放通常是經過"垃圾回收器"自動完成的,但具體來講,仍有些須要注意的地方:
1. 值類型和引用類型的引用實際上是不須要什麼"垃圾回收器"來釋放內存的,
由於當它們出了做用域後會自動釋放所佔內存,由於它們都保存在棧(Stack)中;
2. 只有引用類型的引用所指向的對象實例才保存在堆(Heap)中,而堆由於是一個自由存儲空間,
因此它並無像"棧"那樣有生存期("棧"的元素彈出後就表明生存期結束,也就表明釋放了內存),
而且要注意的是,"垃圾回收器"只對這塊區域起做用;
然而,有些狀況下,當須要釋放非託管資源時,就必須經過寫代碼的方式來解決。
一般是使用析構函數釋放非託管資源,將用戶本身編寫的釋放非託管資源的代碼段放在析構函數中便可。
須要注意的是,若是一個類中沒有使用到非託管資源,那麼必定不要定義析構函數,
這是由於對象執行了析構函數,那麼"垃圾回收器"在釋放託管資源以前要先調用析構函數,
而後第二次才真正釋放託管資源,這樣一來,兩次刪除動做的花銷比一次大多的。
下面使用一段代碼來示析構函數是如何使用的:
public class ResourceHolder
{
…
~ResourceHolder()
{
// 這裏是清理非託管資源的用戶代碼段
}
}
四.小結
構造函數與析構函數雖然是一個類中形式上較簡單的函數,
但它們的使用決非看上去那麼簡單,
所以靈活而正確的使用構造函數與析構函數可以幫你更好的理解CLR的內存管理機制,
以及更好的管理系統中的資源。