今天這節課我們來(lái)學(xué)習類(lèi)的構造方法和析構方法,同學(xué)們現在回憶一下在類(lèi)一 系列中,學(xué)習到創(chuàng )建一個(gè)類(lèi)ClassName的實(shí)例,也就是對象ObjName時(shí)基本語(yǔ)法為:ClassName ObjName=new ClassName();我說(shuō)過(guò),new后面的ClasName()實(shí)際上指的是這個(gè)類(lèi)的構造方法,而且我也說(shuō)過(guò),這個(gè)()中可以有參數,這個(gè)有參數,就是指構造方法可以有參數,那么什么是類(lèi)的構造方法呢?
構造方法
構造方法又叫構造函數,也有人叫做構造器,其實(shí)就是對類(lèi)進(jìn)行初始化。構造方法是一種特殊的方法,在類(lèi)實(shí)例創(chuàng )建之前執行,用來(lái)初始化對象,完成對象創(chuàng )建前所需的相關(guān)設定,構造方法允許將類(lèi)實(shí)例初始為有效狀態(tài)的特殊方法,這就是構造方法的定義,用通俗的話(huà)說(shuō),就是開(kāi)車(chē)前的暖車(chē)工作,用洗衣機之前的接上電源的工作,參數可以有多個(gè)可以這樣理解,洗衣機的插頭有兩頭的、有三項的,在創(chuàng )建洗衣機對象的時(shí)候,要分清插頭的種類(lèi),才能創(chuàng )建成功對象。
為什么說(shuō)構造方法是特殊的方法呢?因為構造方法本身沒(méi)有返回值,并且通常是public訪(fǎng)問(wèn)類(lèi)型,方法的名稱(chēng)必須與類(lèi)名相同,當我們沒(méi)有明確的在類(lèi)中定義構造方法的時(shí)候,例如我們以前所定義的類(lèi),都是沒(méi)有定義構造方法的,這時(shí)系統會(huì )使用默認的構造方法,如創(chuàng )建ClassName類(lèi)的默認構造方法,public ClassName(){}。默認的構造方法并沒(méi)有進(jìn)行任何類(lèi)初始化行為,你可以自己定義構造方法,當然如果你有自定義構造方法,那么默認的構造方法就會(huì )失效了。也就是說(shuō),當我們在ClassName類(lèi)中沒(méi)有定義構造方法時(shí),C#語(yǔ)言會(huì )生成一個(gè)空的構造方法ClassName(),當然這個(gè)空的方法是什么也沒(méi)做,只是為了讓我們定義的類(lèi)能夠在創(chuàng )建對象時(shí)順利的實(shí)例化而已。
構造方法可以有兩個(gè),因為參數的不同區別開(kāi),這就構成了方法的重載 ,方法重載的最大的好處在與可以利用相同的名稱(chēng)來(lái)組織應用程序的方法成員,當一個(gè)復雜的類(lèi)被創(chuàng )建的時(shí)候,對功能相同的方法最好采用相同的命名方式,用不同的參數來(lái)區別,比如,計算面積時(shí),我們就可以把四邊形面積的計算方法的名字起同一個(gè),用參數來(lái)區別如正方形定義一個(gè)參數(一個(gè)邊長(cháng)),長(cháng)方形定義三個(gè)參數(一個(gè)長(cháng),一個(gè)寬,另一個(gè)長(cháng)方形的標志如0),梯形定義三個(gè)參數(一個(gè)底,一個(gè)高,另一個(gè)梯形的標志如1),通過(guò)第三個(gè)參數標志區別長(cháng)方形和梯形不同的面積公式。方法的重載要注意的是,方法名相同,但是參數類(lèi)型或個(gè)數必須要有所不同,方法重載的優(yōu)點(diǎn)就是可以在不改變原方法的基礎上,新增功能。
下面我們來(lái)看實(shí)例,通過(guò)實(shí)例我們來(lái)體會(huì )構造方法是如何在創(chuàng )建對象時(shí)初始化的。
namespace hello { // 創(chuàng )建一個(gè)人類(lèi)Ren class Ren { /* 觀(guān)察字段成員如果不賦初值時(shí),在創(chuàng )建對象的時(shí)候, * 編譯器會(huì )按照類(lèi)型默認的給變量一個(gè)初值,如年齡、姓名 * 我們可以在構造方法中,對未賦值的字段進(jìn)行改變,如年齡 * 如果字段已經(jīng)賦初值了,那么創(chuàng )建對象的時(shí)候, * 就會(huì )使用初值,如果需要改變,就在構造方法中重新賦值即可,如性別 */ // 定義一個(gè)年齡變量,不賦初值 int age; // 定義一個(gè)姓名變量,不賦初值 string name; // 定義一個(gè)性別變量,賦初值為男 string Sex = " 男 " ; // 定義一個(gè)方法,讀取類(lèi)中字段成員,值的變化。 void Say() { Console.WriteLine( " 我的年齡:{0},我的名字:{1},我的性別:{2} " , age, name,Sex ); } /* 1.以下方法都為Ren這個(gè)類(lèi)的構造方法,他們因為參數不同,形成了方法的重載, * 2.this關(guān)鍵字:this關(guān)鍵字用來(lái)引用對象本身,已經(jīng)是說(shuō),this.name指的是對象中的name字段, * 而=name的這個(gè)name指的是方法中離它定義最近的name,這里值的是構造方法中傳入的name參數 * this關(guān)鍵字在例子結束后,還會(huì )具體的講解。 */ // 在無(wú)參的構造方法中,給性別重新賦值,年齡和姓名按照創(chuàng )建對象時(shí)默認的賦初值 public Ren() { // 改變性別的值 Sex = " 女 " ; Console.WriteLine( " 我剛出生!還沒(méi)有名字,年齡也從0歲開(kāi)始! " ); // 此時(shí)讀取對象中的字段值的變化,應該性別改變,年齡和姓名都為默認值 Say(); } // 創(chuàng )建一個(gè)帶姓名參數的構造方法,將創(chuàng )建對象時(shí)傳入的name參數賦給對象中的字段name值,這樣say方法中的姓名也有了改變 // 同時(shí)在這個(gè)構造方法中,給age字段自定義賦值。 public Ren( string name ) { this . name = name ; Console.WriteLine( " 呦!我使用雙截棍,我有名字了!,但是年齡呢?,我自己定,我就27歲了。 " ); age = 27 ; Say(); } // 創(chuàng )建一個(gè)帶姓名參數、姓名的構造方法,將創(chuàng )建對象時(shí)傳入的name參數賦給對象中的字段name值, // 將創(chuàng )建對象時(shí)傳入的age參數的值賦給對象中的字段age,這樣say方法中的姓名、年鄰都有了改變 public Ren( int age, string name) { this .age = age; this .name = name; Console.WriteLine( " 哦!你給我起名字,告訴我多大了吧!你說(shuō)的算! " ); Say(); } // 定義靜態(tài)的構造函數,我把她放在在最后,但是會(huì )第一個(gè)被調用。 static Ren() { Console.WriteLine( " 我是靜態(tài)的構造函數,不能有參數及訪(fǎng)問(wèn)修飾符,并且創(chuàng )建對象時(shí),我只執行一次,最先調用 " ); Console.WriteLine(); } } class Program { static void Main( string [] args) { // 創(chuàng )建無(wú)參的對象boby,并且創(chuàng )建時(shí)會(huì )先調用靜態(tài)的構造方法,再調用無(wú)參的構造方法 Ren boby = new Ren(); Console.WriteLine(); // 創(chuàng )建有姓名參數的對象songer歌手 Ren songer = new Ren( " 周杰倫 " ); Console.WriteLine(); // 創(chuàng )建有年齡、姓名參數的對象man Ren man = new Ren( 33 , " 貝克漢姆 " ); Console.WriteLine(); } } }
結果如下 我是靜態(tài)的構造函數,不能有參數及訪(fǎng)問(wèn)修飾符,并且創(chuàng )建對象時(shí),我只執行一次,最先調用
我剛出生!還沒(méi)有名字,年齡也從0歲開(kāi)始! 我的年齡:0,我的名字:,我的性別:女
呦!我使用雙截棍,我有名字了!,但是年齡呢?,我自己定,我就27歲了。 我的年齡:27,我的名字:周杰倫,我的性別:男
哦!你給我起名字,告訴我多大了吧!你說(shuō)的算! 我的年齡:33,我的名字:貝克漢姆,我的性別:男
在上面的例子中同學(xué)們會(huì )觀(guān)察到我定義了一個(gè)static Ren(){}的構造方法被稱(chēng)為靜態(tài)構造方法。構造方法的重載中還包括一種,在方法的名字前的修飾符只有static,稱(chēng)為靜態(tài)構造方法 ,使用靜態(tài)構造方法要注意以下幾點(diǎn):
1.靜態(tài)構造方法只有一個(gè);
2.并且沒(méi)有參數;
3在所有的構造方法中最先被執行;
4.靜態(tài)的構造方法不會(huì )被繼承,因為它的修飾符是私有的并且只能是私有的;
5.靜態(tài)構造方法在所有靜態(tài)成員本初始化后執行,也就是說(shuō)如果在類(lèi)中定義了靜態(tài)成員,就會(huì )自動(dòng)生成一個(gè)靜態(tài)構造方法,否則如果要使用靜態(tài)構造方法就得自定義。
6.靜態(tài)構造方法在所有靜態(tài)成員被引用之前執行。
7.靜態(tài)構造方法在所有實(shí)例成員被分配之前執行。
本例中還出現了this關(guān)鍵字,下面我們來(lái)說(shuō)說(shuō)this關(guān)鍵字 ,this關(guān)鍵字最大的用途就是用來(lái)區分類(lèi)級別的變量和局部變量,當一個(gè)位于方法內部的局部變量與類(lèi)級別的變量名稱(chēng)相同的時(shí)候,因為有效區域不同,因為并不不會(huì )發(fā)生沖突,但是如此一來(lái),在方法內部所訪(fǎng)問(wèn)的變量,一定是其中所定義的局部變量,而非類(lèi)級別的同名變量,在這種情況下,我們就使用this關(guān)鍵字,比如第一個(gè)實(shí)例中,用this代表對象本身,this.name指的是類(lèi)級別中定義的name字段.使用this雖然可以讓我們分辨出不同級別的變量,但是最好還是起不同的名字來(lái)區分變量。this關(guān)鍵字還有一種用法,就是在同類(lèi)的構造方法中,指代無(wú)參的構造函數,使用:this()來(lái)實(shí)現繼承。下面我們來(lái)看靜態(tài)構造方法和this關(guān)鍵字的構造方法繼承用法實(shí)例:
1 class StaticTest 2 { 3 // 如果i沒(méi)有初值,編譯器不會(huì )自動(dòng)創(chuàng )建靜態(tài)構造方法。 4 public static int i = 1 ; 5 6 // 創(chuàng )建自定義的靜態(tài)構造器,觀(guān)察它的執行順序。 7 static StaticTest() 8 { 9 Console.WriteLine( " 我應該在1的前面被調用,我只被調用一次。 " ); 10 } 11 12 // 定義私有的實(shí)例變量j和s,不賦初值 13 int j; 14 string s; 15 16 // 定義無(wú)參的構造器,同時(shí)給j和s賦值 17 public StaticTest() 18 { 19 Console.WriteLine( " 我是無(wú)參構造器!,我給j賦值后,只要繼承我,使用:this(),你們的j和s都是這個(gè)值 " ); 20 j = 10 ; 21 s = " 原來(lái)的值 " ; 22 } 23 24 // 通過(guò):this()方式,繼承了j和s的值 25 26 public StaticTest( string ss): this () 27 { 28 // 此時(shí)j=10,如果沒(méi)有繼承,j=0. 29 Console.WriteLine( " j= " + j); 30 31 // 通過(guò)傳參進(jìn)入的值,改變了繼承了無(wú)參構造器中s的值, 32 // 說(shuō)明即使繼承了s的值,也可以在本構造方法中改變s的值 33 s = ss; 34 Console.WriteLine(s); 35 36 } 37 } 38 class Program 39 { 40 static void Main( string [] args) 41 { 42 // 先調用靜態(tài)成員i的值,結果會(huì )是先執行靜態(tài)構造方法,再顯示i=1; 43 Console.WriteLine( " 用我前,先會(huì )查看是否有靜態(tài)構造方法的定義,如果有先執行它,再得出i= " + StaticTest.i); 44 Console.WriteLine( " ------------- " ); 45 46 // 此時(shí)不會(huì )再出現static構造器的內容,因為前面已經(jīng)執行了一次。 47 StaticTest A = new StaticTest(); 48 Console.WriteLine( " ------------- " ); 49 50 // 觀(guān)察j和s值的變化: 51 StaticTest B = new StaticTest( " s的值改變了 " ); 52 53 } 54 }
結果如下 我應該在1的前面被調用,我只被調用一次。 用我前,先會(huì )查看是否有靜態(tài)構造方法的定義,如果有先執行它,再得出i=1 ------------- 我是無(wú)參構造器!,我給j賦值后,只要繼承我,使用:this(),你們的j和s都是這個(gè)值 ------------- 我是無(wú)參構造器!,我給j賦值后,只要繼承我,使用:this(),你們的j和s都是這個(gè)值 j=10 s的值改變了
通過(guò)上面的實(shí)例和注釋?zhuān)瑢φ者\行結果,同學(xué)們要熟練的運用和理解。
因為我們現在還沒(méi)有學(xué)習到繼承,其實(shí)在一個(gè)類(lèi)創(chuàng )建成對象的時(shí)候,創(chuàng )建時(shí),編譯器會(huì )先看這個(gè)類(lèi)是否有父類(lèi),如果有父類(lèi),再繼續找是否有父類(lèi)的父類(lèi),我習慣叫爺爺類(lèi),如果有爺爺類(lèi),再看有沒(méi)有太爺爺類(lèi),如果沒(méi)有太爺爺類(lèi),就會(huì )先執行爺爺類(lèi)的構造方法,再執行爸爸類(lèi)的構造方法,最后才執行自己的構造方法,當然所有的構造方法的繼承都是指實(shí)例構造函數,靜態(tài)的構造函數是無(wú)法被繼承的。下面的實(shí)例就是一個(gè)構造方法的繼承順序,現在看不懂沒(méi)有關(guān)系,只是想讓大家了解創(chuàng )建對象的工作過(guò)程,這里提前講一個(gè)知識點(diǎn)就是繼承的語(yǔ)法如A是父類(lèi),B是子類(lèi),那么在定義B類(lèi)的時(shí)候,語(yǔ)法是這樣寫(xiě)的Class B:A,用“:”來(lái)表明繼承關(guān)系,在java中是用“extends ”關(guān)鍵字,現在看看代碼,理解一下構造方法的繼承 結果:
class Class1 可以看出,類(lèi)在繼承時(shí),構造函數在實(shí)例化的過(guò)程中,是會(huì )被再次調用的。 { static void Main() { Console.WriteLine( " ********** " ); A a = new A(); Console.WriteLine( " ********** " ); B b = new B(); Console.WriteLine( " ***** AC : A***** " ); AC ac = new AC(); Console.WriteLine( " *****BC : B***** " ); BC bc = new BC(); Console.WriteLine( " *****CC : AC***** " ); CC cc = new CC(); Console.WriteLine( " *****CCC : BC***** " ); CCC ccc = new CCC(); } } class A { public A() { Console.WriteLine( " Call method A() " ); } } class B { public B() { Console.WriteLine( " Call method B() " ); } } class AC : A { public AC() { Console.WriteLine( " Call method AC() " ); } } class BC : B { public BC() { Console.WriteLine( " Call method BC() " ); } } class CC : AC { public CC() { Console.WriteLine( " Call method CC() " ); } } class CCC : BC { public CCC() { Console.WriteLine( " Call method CCC() " ); }
結果如下: ********** Call method A() ********** Call method B() ***** AC : A***** Call method A() Call method AC() *****BC : B***** Call method B() Call method BC() *****CC : AC***** Call method A() Call method AC() Call method CC() *****CCC : BC***** Call method B() Call method BC() Call method CCC() 請按任意鍵繼續. . .
雖然我沒(méi)有寫(xiě)注釋?zhuān)窍嘈磐瑢W(xué)們都應該能理解這個(gè)過(guò)程,構造器的繼承我也會(huì )在類(lèi)七中再繼續深化講解。構造方法的出現使得我們開(kāi)發(fā)人員可設置默認值、限制實(shí)例化以及編寫(xiě)更加靈活且便于閱讀的代碼。接下來(lái)我們來(lái)學(xué)習本節課最后一個(gè)知識點(diǎn),析構方法。
析構方法
析構方法也叫銷(xiāo)毀方法,也有人稱(chēng)為析構器,類(lèi)借助構造方法進(jìn)行對象的初始化,借助析構方法進(jìn)行對象的終止操作,析構方法也是使用類(lèi)名相同的名稱(chēng)命名,用“~”修飾,當對象被系統終止并且回收時(shí),析構方法執行,我們可以在析構方法中寫(xiě)方法,它會(huì )最后被執行,析構方法的語(yǔ)法書(shū)寫(xiě)如:~ClassName(){},析構函數是不能有參的,并且一個(gè)類(lèi)中只能有一個(gè)析構方法,類(lèi)中有幾個(gè)構造方法,析構方法就有被執行幾次。我們把上面的實(shí)例中加入析構方法,再執行一次代碼。
namespace hello { // 創(chuàng )建一個(gè)人類(lèi)Ren class Ren { // 雖然我把析構方法寫(xiě)在最上面的,但是它是最后執行的方法。 ~ Ren() { Console.WriteLine( " 滅亡了 " ); } /* 觀(guān)察字段成員如果不賦初值時(shí),在創(chuàng )建對象的時(shí)候, * 編譯器會(huì )按照類(lèi)型默認的給變量一個(gè)初值,如年齡、姓名 * 我們可以在構造方法中,對未賦值的字段進(jìn)行改變,如年齡 * 如果字段已經(jīng)賦初值了,那么創(chuàng )建對象的時(shí)候, * 就會(huì )使用初值,如果需要改變,就在構造方法中重新賦值即可,如性別 */ // 定義一個(gè)年齡變量,不賦初值 int age; // 定義一個(gè)姓名變量,不賦初值 string name; // 定義一個(gè)性別變量,賦初值為男 string Sex = " 男 " ; // 定義一個(gè)方法,讀取類(lèi)中字段成員,值的變化。 void Say() { Console.WriteLine( " 我的年齡:{0},我的名字:{1},我的性別:{2} " , age, name,Sex ); } /* 1.以下方法都為Ren這個(gè)類(lèi)的構造方法,他們因為參數不同,形成了方法的重載, * 2.this關(guān)鍵字:this關(guān)鍵字用來(lái)引用對象本身,已經(jīng)是說(shuō),this.name指的是對象中的name字段, * 而=name的這個(gè)name指的是方法中離它定義最近的name,這里值的是構造方法中傳入的name參數 * this關(guān)鍵字在例子結束后,還會(huì )具體的講解。 */ // 在無(wú)參的構造方法中,給性別重新賦值,年齡和姓名按照創(chuàng )建對象時(shí)默認的賦初值 public Ren() { // 改變性別的值 Sex = " 女 " ; Console.WriteLine( " 我剛出生!還沒(méi)有名字,年齡也從0歲開(kāi)始! " ); // 此時(shí)讀取對象中的字段值的變化,應該性別改變,年齡和姓名都為默認值 Say(); } // 創(chuàng )建一個(gè)帶姓名參數的構造方法,將創(chuàng )建對象時(shí)傳入的name參數賦給對象中的字段name值,這樣say方法中的姓名也有了改變 // 同時(shí)在這個(gè)構造方法中,給age字段自定義賦值。 public Ren( string name ) { this . name = name ; Console.WriteLine( " 呦!我使用雙截棍,我有名字了!,但是年齡呢?,我自己定,我就27歲了。 " ); age = 27 ; Say(); } // 創(chuàng )建一個(gè)帶姓名參數、姓名的構造方法,將創(chuàng )建對象時(shí)傳入的name參數賦給對象中的字段name值, // 將創(chuàng )建對象時(shí)傳入的age參數的值賦給對象中的字段age,這樣say方法中的姓名、年鄰都有了改變 public Ren( int age, string name) { this .age = age; this .name = name; Console.WriteLine( " 哦!你給我起名字,告訴我多大了吧!你說(shuō)的算! " ); Say(); } // 定義靜態(tài)的構造函數,我把她放在在最后,但是會(huì )第一個(gè)被調用。 static Ren() { Console.WriteLine( " 我是靜態(tài)的構造函數,不能有參數及訪(fǎng)問(wèn)修飾符,并且創(chuàng )建對象時(shí),我只執行一次,最先調用 " ); Console.WriteLine(); } } class Program { static void Main( string [] args) { // 創(chuàng )建無(wú)參的對象boby,并且創(chuàng )建時(shí)會(huì )先調用靜態(tài)的構造方法,再調用無(wú)參的構造方法 Ren boby = new Ren(); Console.WriteLine(); // 創(chuàng )建有姓名參數的對象songer歌手 Ren songer = new Ren( " 周杰倫 " ); Console.WriteLine(); // 創(chuàng )建有年齡、姓名參數的對象man Ren man = new Ren( 33 , " 貝克漢姆 " ); Console.WriteLine(); } } }
結果如下:
我是靜態(tài)的構造函數,不能有參數及訪(fǎng)問(wèn)修飾符,并且創(chuàng )建對象時(shí),我只執行一次,最先調用
我剛出生!還沒(méi)有名字,年齡也從0歲開(kāi)始! 我的年齡:0,我的名字:,我的性別:女
呦!我使用雙截棍,我有名字了!,但是年齡呢?,我自己定,我就27歲了。 我的年齡:27,我的名字:周杰倫,我的性別:男
哦!你給我起名字,告訴我多大了吧!你說(shuō)的算! 我的年齡:33,我的名字:貝克漢姆,我的性別:男
滅亡了 滅亡了 滅亡了 請按任意鍵繼續. . .
因為析構方法比較好理解,看這個(gè)實(shí)例同學(xué)們就能明白他的用法,析構函數的繼承順序是按照從派生程度最大到最小的順序調用的,也就是與構造方法繼承順序相反,先結束子類(lèi)的析構方法,再結束爸爸類(lèi)的,再結束爺爺類(lèi)的,這點(diǎn)也比較好理解,可以把構造方法和析構方法看出一對開(kāi)始和結束的標記,內部的開(kāi)始標記最先結束,最外部的開(kāi)始標記,最后結束。
本節課我們就學(xué)到這里,下節課我們來(lái)學(xué)習類(lèi)的六大分類(lèi)。