(四)C#之建構函式與解構函式
建構函式與解構函式
建構函式
首先我們看一下例項建構函式的形式
|建構函式修飾符| 識別符號 (|引數列表|) |: base(|引數列表|)| |:this(|引數列表|)|
{
建構函式語句塊;
}
如果你以前沒學過C++的建構函式或者第一次看見寫的這麼亂七八糟的建構函式,我覺得你應該會罵人。
那麼下面就家幾條註解吧:
- 建構函式的修飾符號:這不就是我們熟悉的 public,private,protected這三個嘛。在C#中 internal 也是可以被當做修飾符的,這裡便不再展開介紹修飾符的具體的訪問規則。
- 識別符號不就是類名嘛,引數列表也不用多說。
- :base:這個表示呼叫直接基類中的例項建構函式(就是呼叫它爸爸的建構函式唄)。
- :this:呼叫該類本身所宣告的其他建構函式(一個類可以有多個建構函式啊,因為類是可以被過載的嘛!所以就在這個建構函式裡面呼叫了一下其他的建構函式唄)。
怎麼樣?通過上面的分析你應該基本瞭解一個例項建構函式是怎樣組成的了吧?如果還不是很清楚的話,我們看一下下面的一個小程式吧:
public class Base {
public Base() {
Console.WriteLine("爸爸的建構函式被呼叫啦~");
}
}
public class A :Base
{
public A() : this("hello"){
Console.WriteLine("我是可以呼叫基類和我的其他建構函式的類");
}
public A(string info) : base()
{
Console.WriteLine("我是被其他建構函式呼叫的,它發過來字串"+info);
}
}
class Program
{
static void Main(string [] args)
{
A a = new A();
Console.ReadLine();
}
}
我們先猜測一下這個小例子會輸出什麼吧:
- 首先呼叫建構函式
A()
- 然後呢,因為
A()
使用了this
關鍵字,所以在還沒有進入函式體的是否就呼叫了this("hello")
也就是A(string info)
- 這時候
A(string info)
建構函式被呼叫,但是由於該建構函式使用了base
關鍵字,所以在沒有進入函式體就去呼叫它爸爸建構函式Base()
咯 - 然後呼叫完
Base()
就依次的輸出結果咯,整個過程其實就是一個壓棧然後取棧頂元素的過程
經過上面的分析,我們大致可以得到輸出結果為
爸爸的建構函式被呼叫啦~
我是被其他建構函式呼叫的,它發過來字串hello
我是可以呼叫基類和我的其他建構函式的類
對於例項建構函式我們補充說明一下幾點:
- 儘量不要用 base 和 this 關鍵字
- 不能同時將this和base作用在同一個建構函式上。
- 對於沒有宣告構造的類,系統會提供一個預設的建構函式。
接下來我們看一下C#對於類中的欄位的初始化!
我們看一下下面的小程式:
public class A
{
public A() {
Console.WriteLine("A.A()");
}
private static int InitX()
{
Console.WriteLine("A.InitX()");
return 1;
}
private static int InitY()
{
Console.WriteLine("A.InitY()");
return 2;
}
private static int InitA()
{
Console.WriteLine("A.InitA()");
return 3;
}
private static int InitB()
{
Console.WriteLine("A.InitB()");
return 4;
}
private int y = InitY();
private int x = InitX();
private static int a = InitA();
private static int b = InitB();
}
class Program
{
static void Main(string[] args)
{
A a = new A();
Console.ReadLine();
}
}
輸出結果如下:
A.InitA()
A.InitB()
A.InitY()
A.InitX()
A.A()
可以看出程式是先初始化類中的靜態欄位,再初始化非靜態欄位。且初始化的順序與其排列的順序呈現正相關。而建構函式是最後初始化的!
根據上面說的原則,我們看一看下面的程式並預測一下輸出結果:
class Base
{
public Base(int x)
{
Console.WriteLine("Base.Base(int)");//4
this.x = x;
}
private static int InitX()
{
Console.WriteLine("Base.InitX()"); //3
return 1;
}
public int x = InitX();
}
class Derived : Base
{
public Derived(int a)
: base(a)
{
Console.WriteLine("Derived.Derived(int)"); //5
this.a = a;
}
public Derived(int a, int b)
: this(a)
{
Console.WriteLine("Derived.Derived(int,int)"); //6
this.b = b;
}
private static int InitA()
{
Console.WriteLine("Derived.InitA()");//1
return 3;
}
private static int InitB()
{
Console.WriteLine("Derived.InitB()");//2
return 4;
}
public int a = InitA();
public int b = InitB();
}
class Program
{
static void Main(string[] args)
{
Derived b = new Derived(1, 2);
Console.ReadLine();
}
}
- 呼叫
Derived b = new Derived(1, 2);
- 初始化
Derived
的靜態方法:
即:呼叫public int a = InitA();
這時候輸出:Derived.InitA()
再呼叫public int b = InitB();
輸出:Derived.InitB()
- 初始化建構函式,因為傳過去的是兩個引數,所以呼叫
public Derived(int a, int b): this(a)
- 因為該建構函式有this關鍵字,所以這時候呼叫
public Derived(int a): base(a)
- 因為這個建構函式有base關鍵子,所以呼叫其父類方法
- 這時候初始化其父類,先初始化其靜態變數再初始化非靜態變數
即:呼叫public int x = InitX();
,這時候輸出:Base.InitX() - 非靜態變量出事完成,呼叫構造方法
public Base(int x)
,輸出:Base.Base(int) - 接著進入
public Derived(int a): base(a)
的方法體,輸出:Derived.Derived(int) - 再接著進入
public Derived(int a, int b): this(a)
的方法體,輸出:Derived.Derived(int,int)
至此,我們分析完畢,我執行一下程式驗證一下結果:
最後我們來看一下靜態建構函式
首先從其形式說起:
[靜態建構函式修飾符] 識別符號(){/*靜態建構函式的函式體*/}
這時候按照慣例補充幾點說明:
- 靜態函式修飾符,說的那麼高大上,其實就是 static 關鍵字啦!
- 靜態建構函式只能對靜態資料成員進行初始化(例項建構函式是都可以哦~)!
- 如果沒有寫靜態建構函式,而類中包含帶有初始值設定的靜態成員,那麼編譯器會自動生成預設的靜態建構函式。
靜態建構函式是屬於類的,不是屬於例項的!所以說只會在類建立任何例項或者引用其靜態資料成員的時候被執行一次(注意是隻會被執行一次哦)!
看一下程式碼更直觀:class A { public static int x; public static int y; static A() { Console.WriteLine("靜態建構函式被呼叫(只會輸出一次哦~)"); x = 10; //1 y = 20; } } class Test { static void Main() { Console.WriteLine(A.x); Console.WriteLine(A.y); } }
這個程式中雖然我們呼叫的兩次它的靜態方法,但是也只會呼叫一次它的靜態建構函式,輸出結果:
靜態建構函式被呼叫(只會輸出一次哦~)
當然我們要是先例項化一下
A
類,把Main()
函式改成一下形式:static void Main() { new A(); Console.WriteLine(A.x); Console.WriteLine(A.y); }
結果一樣只會輸出一次。
說了這麼多,我們分析一下下面的程式的輸出,加深一下印象!
class A
{
protected static int x;
protected int y;
static A()
{
x = 10; //1
}
public A(int a, int b) //3
{
x = a;
y = b;
}
public void print()
{
Console.WriteLine("x={0},y={1}", x, y);
}
}
class B : A
{
protected new static int x;
private int z;
public B(int a, int b, int c)
: base(a, b)
{
z = c; //4
}
static B()
{
x = 20; //2
}
new public void print()
{
Console.WriteLine("x={0},y={1},z={2}", x, y, z);
}
}
class Test
{
static void Main()
{
A a = new B(3, 4, 5);
a.print();
B b = (B)a;
b.print();
}
}
我們來依次對該程式碼進行分析:
- 因為是
A
類的例項,所以先初始化A
的靜態建構函式再初始化B
類的靜態建構函式,這時候:[A
類中x=10
],[B
類中x=20
]。 - 呼叫
B
的public B(int a, int b, int c): base(a, b)
建構函式。(a=3; b=4; c=5
) - 因為該建構函式有
base
關鍵字,所以呼叫父類(A
)的public A(int a, int b)
建構函式。這時候:[A
類中x=a=3; y=b=4;
]。 - 父類建構函式呼叫完畢,進入本身=建構函式的函式體。這時候:[
B
類中z=5;
]。 - 所有構造方法呼叫完畢。
a.print();
的輸出結果為:x=3,y=4
。b.print();
的輸出結果為:x=20,y=4,z=5
。- 至此,程式執行完畢。
C#中的建構函式部分已經講完了,下面看看解構函式吧!
解構函式
首先解構函式的基本形式如下:
~ 識別符號(就是類名啦) { /* 解構函式體 */ }
說明幾點:
- 解構函式不能有程式顯示的呼叫,是由系統在釋放物件之前呼叫。
- 解構函式在C#中充當雞肋的作用,因為C#有垃圾回收器清理資源。
- 解構函式是在垃圾回收器回收物件的空間之前呼叫的,最終會呼叫
System.Object
的Finalize()
方法。
覺得解構函式也啥可說的,C#其實提供了 Dispose()
和Close()
方法來回收記憶體,三者的區別就如下啦:
總結
這一篇部落格你我們學習了C#的建構函式與解構函式:
首先簡單介紹建構函式的概念,
然後介紹了建構函式中的欄位的初始化,
接著有說了下靜態建構函式。並且每個點都有響應的小例子。
最後簡單的說了下解構函式,比較了它與Dispose()
和Close()
方法的區別!