zz面向對象編程,我的思想(下部)

2.3深入探討函數:

2.3.1構造函數、默認構造函數、 缺省構造函數

       對於上面的實例,它已經能完成絕大部分工作了,但它還是不完善的,還有許許多多的細節等到我們去完善!也許有的同學已經注意到了,當我創建完“jingwei”這個對象時,這個對象的所有的屬性都是空的,也就是說:這個對象的姓名是未定的、年齡是未定的、性別是未定的、薪水是未定的、午餐也是未定的。而我們想把這些屬性都添加上去,就還要用對象調用相應的方法,去一個個修改!天啊,這簡直是太麻煩了!有沒有什麼好方法能夠在我們創建對象的同時就完成了對屬性賦值的操作呢?哦不,應該說是對屬性的初始化呢?當然沒問題了,這就需要所謂的構造函數!

       構造函數是類中最特殊的函數,它與析構函數的功能正好相反!

              從特徵上來說:1.它是編程語言中唯一沒有返回值類型的函數。

                                     2.它的名稱與類的名稱必須要完全相同。

                                     3.它必須被聲明爲公共(public)的類型

                                     4,可以對構造函數進行重載。

                                     5.它在創建對象是自動被調用。

              從功能上來說:1.它是對類中的屬性進行初始化。

其實,對於上面的程序來說我們沒有自己定義構造函數。但是,在這種情況下,系統會自動爲我們定義一個“默認構造函數”。他會把數值變量自動賦值爲0,把布爾行變量賦值爲false等等(但在C++中,默認構造函數不初始化其成員)。如果程序員定義了構造函數,那麼系統就不會再爲你的程序添加一個缺默認造函數了。(在這裏,我們提倡的是自己定義構造函數,而不是用系統的默認構造函數

       還是看個實例吧!這樣比較清楚一些!

       //employee.java

public class employee{

       private String name;       //員工姓名

       private int age;           //員工年齡

       private char sex;          //員工性別

       private float emolument;   //員工薪水

private boolean lunch;     //員工午餐

                              //……等等

       public employee(){        //這個就是“默認”構造函數

              name = “jw”;         //設置員工姓名

              age = 20;             //設置員工年齡

              sex = “M”;           //設置員工性別

              emolument = 100;     //設置員工薪水

              lunch = false;         //設置員工午餐

}

public void heater(){        //這個方法是用來加工員工的午餐

       lunch = true;

}

//……等等

};

這樣,在我們創建“jingwei”這個對象的同時,它的所有的屬性也被初始化了!顯然,這大大的提高了工作效率,但是,它還是不符合要求。想想看,如果我們現在創建這個類型的第二個對象的時候會發生什麼事情?告訴你,除了對象的“名”(這個名稱不在是對象屬性中的名稱,而是對象本身的名稱)不一樣外,其所有的“屬性值”都一樣!比如:現在我們創建第二個對象flashmagic,然而我會發現這個對象的所有的屬性和jingwei這個對象的所有的屬性完全相同。而我們只能在用對象的方法去改變着寫屬性了!很顯然,這種方法不大好!我們需要一種方法在創建對象的時候爲對象的屬性賦予“我們想要的值”。

相信你也看到了,默認構造函數就顯得無能爲力了。我們需要的是帶參數的構造函數,在創建對象時,我們把參數傳給構造函數,這樣就能完成了上述的功能!口說無憑,還是來看個實例吧:

//employee.java

public class employee{

       private String name;       //員工姓名

       private int age;           //員工年齡

       private char sex;          //員工性別

       private float emolument;   //員工薪水

private boolean lunch;     //員工午餐

                              //……等等

       public employee([color=Brown]String n,int a,char s,float e,boolean l){ //看這個構造函數

              name = n;         //設置員工姓名

              age = a;          //設置員工年齡

              sex = s;          //設置員工性別

              emolument = e;    //設置員工薪水

              lunch =l;          //設置員工午餐

}
){ //看這個構造函數

              name = n;         //設置員工姓名

              age = a;          //設置員工年齡

              sex = s;          //設置員工性別

              emolument = e;    //設置員工薪水

              lunch =l;          //設置員工午餐

}[/color]

public void heater(){        //這個方法是用來加工員工的午餐

       lunch = true;

}

//……等等

};

這樣一來,在創建對象的同時我們就可以給他賦予我們想要的值,很顯然,這可就方便多了。哦,對了!還沒有告訴你怎麼創建呢!哈哈,往前翻幾頁你會看到這句話:

jingwei = new employee();這是創建一個對象,而我們把它改成

jingwei = new employee("jingwei",20,'M',100,false);這樣一來,所有的工作都完成了,呵呵!(在創建對象的同時賦予了我們想要的“初值”)

2.3.2重載構造函數:

       我還是先把概念給你吧,讓你有個認識,隨後我們在進行論述。

       在JAVA中:

1.       函數重載是一個類中聲明瞭多個同名的方法,但有不同的參數個數和參數類型。

2.       函數重構是指在子類中聲明與父類同名的方法,從而覆蓋了父類的方法。重構解決了子類與父類的差異問題。(在討論到繼承時我會詳細說明)

       在C++中:

1.  數重載的概念一樣。

2.  重構的概念可就不一樣了,C++中功能更爲龐大的虛函數。更詳細內容這裏就不過多介紹了!

其實關於重載的概念你並不陌生,在編程中相信你也接觸過。呵呵!讓我們來舉個操作符重載的例子你就會明白了,(JAVA中不支持這個功能)我們定義三個整數變量:

int i1=2, i2=3,i3=0;

i3 = i1 + i2; 

此時i3=5;加號實現了兩個數相加的運算功能。然而我們現在要定義三個字符串變量:

       String str1=”jing”, str2=”wei”,str3=””;

       str3 = str1 + str2;

此時str3 = “jingwei”;加號實現了兩個字符串相加的運算功能。同樣是加號,既可以把兩個整型的變量加在一起,也可以把兩個字符串類型的變量加在一起。同一個操作符實現了不同的功能------這就是所謂的操作符重載(嘿嘿,我說你一定見過吧:)!不就好像是漢語中的一詞多意一樣!我需要說明一下的是,C++中的操作符重載可沒有這麼簡單。比如,我們可以對兩個自定義類型的對象進行相加的運算,進行賦值的運算。這樣書寫簡潔明瞭,而且非常實用。當然,關於操作符重載的話題太多了,有興趣再看看書吧!

我們把操作符的話題在轉到函數上來,我們一直強調的是“對象調方法”------對象其實調的是方法的“名稱”。而我們現在要對方法進想重載,也就是定義多個相同名稱的函數,這樣計算機在調用的時候不會混淆嘛?我想應該不會的,呵呵,因爲僅僅是函數名稱相同,而我們在調用函數時會把參數傳遞給他的。既是沒有參數也是一種參數傳遞參數的信息(信息爲無參數)!然而由於參數類型、參數數量、返回值類型不同我們就可以對相同名稱的函數進行區分了!目的只有一個,用簡便的方法實現更多的功能。還是舉個例子吧,重載構造函數!

public class employee{

       public employee([color=Brown]String n,int a,char s,float e,boolean l){ //看這個構造函數

              name = n;         //設置員工姓名

              age = a;          //設置員工年齡

              sex = s;          //設置員工性別

              emolument = e;    //設置員工薪水

              lunch =l;          //設置員工午餐

}
[/color]
 

public employee(){   [color=Orange]//請注意這個函數沒有參數

       name = “jw”;

       age = 20;

       sex = ’W’;

       emolument = 99;

       lunch = true

}[/color]

//……等等

};

 
看,在一個類中有兩個名稱相同的函數,可我們在使用的時候系統如何知道我們調用的是那個版本的函數呢?呵呵,我剛剛說過了,可以通過函數的參數類型、參數數量、返回值類型來確定。現在我們接着試驗,我們創建兩個對象其中的一個調用帶參數的構造函數,第二個對象調用缺省值的構造函數。我們來看看結果:

jingwei = new employee("jingwei",20,'M',100,false);/*創建這個對象的時候調用的是帶參數的構造函數*/

flashmagic = new employee();//創建這個對象是調用的是卻省值的構造函數

而得到的結果呢?讓我們一起來看一看!

Jingwei這個對象中:                    flashmagic這個對象中:

name        jingwei                     name           jw

age          20                         age            20

sex          M                         sex             W

emolument   100                        emolument       99

lunch        false                       lunch           true

看,雖然是兩個名稱完全相同的函數,但完成了不同的工作內容。呵呵!關於函數重載我們就料到這裏吧,我相信你已經有個大印象了,而更詳細的內容仍需要你的努力!

重載普通的函數與重載構造函數大同小異,不過他多了一個this指針!this一般是對當前對象的引用。這麼說吧,如果涉及到兩個以上的對象時就會使用this指針。每個成員函數都有一個this指針,它是一個隱藏的參數,this指針只向調用它的對象!我說過方法只有一份,而對象都有自己的屬性,當對象調用方法來先是屬性的時候,他怎麼來判斷調用的時不是自己的屬性呢?這就需要this指針來大顯神威了。

關於拷貝構造函數、內聯函數、虛函數、模版等歐就不做過多的討論了,因爲JAVA中好像沒有這些了。不過我需要提醒你一下的是,在C++中,類內定義的函數自動轉換爲內聯函數,而這好像與我前面提到的思想有衝突。因爲內聯函數的目的是減少函數調用的開銷!呵呵!我也沒繞出來呢!還請哪爲大蝦指點一二!謝!

2.3.3 初始化與賦值

這裏我卻要提醒你一下的是,初始化與賦值是完全不同的兩個概念。創建一個類的時候會調用這個類的構造函數對對象的屬性進行初始化。而如果以後再把這個對象賦給其他同類型的對象時可就沒那麼簡單了。在JAVA中直接賦值就行了,因爲JAVA中取消了指針,不存在指針的深拷貝與前拷貝問題。而在C++中就需要拷貝構造函數以及操作符重載了。因爲JAVA中不牽扯這些東西,所以偶就不做過多介紹了。詳情請參閱相關書籍吧!

2.3.4析構函數:

JAVA中不再支持指針了,所以你感覺不到它的重要性,因爲系統會自動爲你釋放內存。而在C++中一切都是手動的。在構造函數中new了一個指針,在析夠函數中就要delete這個指針。

2.3.5靜態:

現在我們再來看一看“靜態”是咋一回事兒!

把一個變量或函數聲明爲靜態的需要“static”這個關鍵字。聲明靜態的目的是“爲某個類的所有對象的某個屬性或方法分配單一的存儲空間”。靜態的數據是屬於類的,不屬於任何的對象。靜態的數據在聲明的時候系統就爲他分配了內存空間,而不用等到創建對象時。舉個例子來幫你更好的理解它吧。

還是接着上面的例子。還記得剛剛我說過的員工能用微波爐熱飯的事情吧。現在我們要找一個手套,畢竟想把熱好的飯從微波爐裏拿出來直接下手是不行的。我把手套定義成一個布爾型的變量,它有乾淨和髒兩種狀態。想想看手套是屬於誰的?所有對象?不對!因爲只有方法才能屬於所有的對象。它是屬於類的,它像微波爐那個方法一樣,在內存中只有一份,所有的對象通過方法都能夠修改它。而下一次修改是基於上一次修改的基礎之上的!我的意思是:一個員工把手套弄髒了,下一個員工在使用的時候它還是髒的。而這個員工把手套洗乾淨之後,別人再用的時候它就是乾淨的了!就這麼點事兒,明白了吧

關於靜態函數我想就沒什麼可多說的了。給我的感覺就是,它也是屬於類的,在定義的時候就分配的內存。調用是可以使用類名直接調用。其他的和普通成員函數沒什麼不同的了不過這裏需要說明的一點是:在JAVA中,靜態的成員函數只能修改靜態的屬性,而靜態的屬性可以被所有的成員函數修改。不過在C++中就沒這麼多事兒了!

2.4繼承

繼承很好理解,它的最大好處就是“代碼重用”,大大提高了工作效率。舉個例子你就明白了。世界上先有的黑白電視機,它有自己的工作原理。然而人們在他的基礎之上開發出了彩色電視機。彩色電視機繼承了黑白電視機的所有的特性與方法!因爲它既能顯示彩色圖像也能顯示黑白圖像。然而它與黑白電視機又有許多區別,在工作原理上。彩色電視及多了矩陣色電路,把彩色信號分離出三種顏色(RGB),他就能顯示彩色的圖像了。而黑白電視機沒有這塊電路,即使它收到了彩色信號也顯示不了彩色圖像。彩色電視機是從黑白電視機中派生出來的。所以,黑白電視機是父類,彩色電視既是子類,彩色電視繼承了黑白電視機所有的特性與方法。看看再計算機中它是什麼樣子的吧:

//BWtv.java               父類的定義

public class BWtv{

       private int a;

       public BWtv(){

              a=1;

       }

       public changeBWtv(int i){

              a=i;

}

}

//Ctv.java                子類的定義

class Ctv exntends BWtv{        //注意關鍵字“extends”

       private int b;

       public Ctv(){

              b=2;

       }

       public changetCv(int x){

              b = x;

       }

}

有了上面的定義,我們來看看他們都有什麼數據。

BWtv的數據包括                         Ctv的數據包括

 private int a                               private int a

                                                                        private int b

 public changeBWtv();                       public changeBWtv()

                                                                        public changeCtv();

你看,子類擁有父類的所有的方法及屬性。注意關鍵字”extends”,它的意思是繼承。在C++中使用的是“:”操作符。意思是一樣的。但是這裏有許多問題,首先是訪問權限的問題,子類的對象擁有父類的所有的屬性和方法這句話。對嘛?肯定是對的!(不過JAVA的書中可不是這麼說的,他說只繼承非private類型的屬性及方法,我覺得它這句話有錯誤!)可是,子類的對象不能直接訪問父類的私有屬性或方法,它只能通過父類的公有成員函數來訪問。而此時,如果你修改了父類的屬性的值。那就真的修改了。我的意思是:父類的私有屬性的值會隨着子類對象調用父類的公有方法進行對相應屬性的修改而發生變化!(這裏面存在一個域的問題,所有的修改都是在子類中進行的,修改的是子類繼承的父類的屬性在子類這個域中,此時父類以拷貝到子類中了。)。程序中定義的父類的屬性不會發生任何變化在父類的域中),)

       其次是構造函數,在創建一個子類對象時首先要調用的是父類的構造函數,然後再調用子類的構造函數,畢竟,子類的構造函數不包括父類的屬性的初始化功能!從這一點來說我的觀點又是正確的子類的對象擁有父類的所有的屬性和方法”)當然了,析夠函數的調用順序正好相反!

       現在讓我們來談談protected這個關鍵字吧,它的意思是:對對象來說,聲明爲protected的變量是私有的,而對子類父類來說,聲明爲protected的變量是公共的。

       現在又出現了這樣的一個問題,如果我們在子類中也定義了一個int 類型的變量a,那我們在創建子類的對象的時候調用的是子類定義的還是父類定義的呢?這就涉及到數據的隱藏的問題了,我可以告訴你肯定是調用的子類的變量a。因爲,子類把父類的這個同名變量給隱藏了。而如果是方法呢?這就涉及到重構的問題了,在上面我提到過“函數重構是指在子類中聲明與父類同名的方法,從而覆蓋了父類的方法。重構解決了子類與父類的差異問題。”這裏必須要聲明一下的是,在JAVA中,子類出現了對父類屬性的隱藏和父類方法的覆蓋後,在子類中,子類對象僅能調用子類本身的屬性和方法。要調用父類的屬性和方法必須要實用super這個關鍵子。而在C++中就不這樣了。因爲它有虛函數

虛擬函數在C++中非常好玩的事。我們可以把需要改寫的函數聲明爲虛函數,用virtual這個關鍵字來聲明。這樣。假如如果我們CwinApp這麼一個基類,它裏面定義了一個成員(虛)函數爲InitInstance()和另一個爲(虛)函數InitApplication()。如果我從CWinApp派生一個子類爲CMyWinApp並修改了InitInstance()這個成員函數。我們並沒有修改InitApplication()這個成員函數。現在我們創建CMyWinApp這個類的函數theApp,我們並創建一個指針*pApp指向這個對象theApp。此時:

pApp->InitInstance()     //指針調用的是子類CMyWinApp的虛方法

pApp->InitApplication()  //指針調用的時父類CwinApp的虛方法

因爲子類並沒有修改父類的方法,所以調用的是父類的虛方法。這就牽扯到虛你表的問題。礙與本篇文章的定位,這裏就不討論了!

關於父類與子類的對象的類型轉換問題是這樣的,子類對象轉換爲父類對象時,不會出現錯誤。因爲子類包含父類的所有的屬性及方法,而父類向子類轉換時就難說了,呵呵。這還會牽扯到虛擬表的問題,也不討論了!

       JAVA中不再支持多重繼承,也就是一個類從兩個以上的類中繼承而來,但它卻多了接口的概念“interface”。這裏就不做過多介紹了!

       關於抽象基類也沒什麼難的!他的一個大概念就是:做爲許多類的父類,不定義對象,只做派生用!

我能做得也只有這些了,如果你能明白以上的六七成,那就是對我最大的回報了,呵呵!就像剛剛開始我說的,我只是給你一個大概的思想,至於內部的實現細節,仍需要你的繼續努力。關於編程語言的內容還有許多許多,實屬小生個人能力有限而不能全盤照顧到。不過作爲一個初學者的你來說,這些東西都是基本的。需要我提醒你一點的是,不要指望在第一、二遍的時候看懂什麼!加油:)


                                                                                                         04.4.29  韓景維

願意和大家保持聯絡,如果大家能夠對本篇文章提出寶貴的意見和建議,我將不勝感激。我的電子郵件地址是:

[email protected]

或在QQ裏給我留言(86228551---亂碼遊魂.h)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章