一.C++中的重寫瑞凑、重載会喝、重定義
1.重載(overload)
概念
同一個(gè)類中的函數(shù)具有相同的名稱陡叠,但是參數(shù)的列表不相同的情形,這樣的同名不同參數(shù)的函數(shù)之間肢执,互成為重載函數(shù)枉阵。
基本條件
- 同一個(gè)類中(相同的作用域中)
- 函數(shù)名稱必須相同
- 函數(shù)參數(shù)必須不相同,可以是參數(shù)類型或者參數(shù)個(gè)數(shù)不同
- 函數(shù)的返回值可以不同
2.重寫(override)
概念
也稱為覆蓋预茄,子類重新定義的父類中有相同的名稱或者參數(shù)的虛函數(shù)兴溜,主要在繼承關(guān)系中出現(xiàn)。
基本條件
- 子類重寫父類中的virtual函數(shù)
- 重寫函數(shù)和被重寫函數(shù)的函數(shù)名和參數(shù)必須一致(具體實(shí)現(xiàn)一般不同)
- 重寫函數(shù)和被重寫函數(shù)的返回值相同,要么都返回指針昵慌,要么都返回引用
- 重寫函數(shù)和被重寫函數(shù)都是virtual函數(shù)(其中重載函數(shù)可以帶virtual假夺,也可以不帶)
注意
- 靜態(tài)方法不能被重寫,也就是static和virtual不能同時(shí)使用
- C++ 11中新增了final關(guān)鍵字斋攀,final修飾的虛函數(shù)不能被被重寫
3.重定義(redefining)
注意
也叫隱藏已卷,子類重定義父類中的非虛函數(shù),屏蔽了父類的同名函數(shù)(相當(dāng)于創(chuàng)建了一個(gè)新的函數(shù)淳蔼,跟父類無(wú)關(guān))
基本條件
- 子類和父類函數(shù)的名稱相同侧蘸,參數(shù)也相同,父類中的函數(shù)不是virtual鹉梨,父類的函數(shù)將被隱藏
- 子類和父類的函數(shù)名稱相同讳癌,但參數(shù)不同,此時(shí)不管父類函數(shù)是不是virtual函數(shù)存皂,都將被隱藏晌坤。
4.深入理解C++中的重寫(override)與重定義(redefining)
上面我們對(duì)C++中的重寫(override)與重定義(redefining)做了概念上的區(qū)分,只要記住上面的概念特征就嗯呢該區(qū)分這兩種操作旦袋,下面我們來(lái)從原理上說(shuō)明下這兩種操作有什么區(qū)別:
重寫(override)
上面我們已經(jīng)說(shuō)了骤菠,C++中重寫的時(shí)候重寫的是父類中的虛函數(shù)(virtual),因此我們需要從虛函數(shù)的工作原理說(shuō)起。
??由于對(duì)于沒(méi)有使用virtual的函數(shù)疤孕,程序?qū)⒏鶕?jù)引用類型或指針類型選擇方法商乎;如果使用了virtual,程序?qū)⒏鶕?jù)引用或指針指向的對(duì)象類型來(lái)選擇方法祭阀,因此對(duì)于虛函數(shù)鹉戚,到底使用哪個(gè)函數(shù)(父類或者子類中的函數(shù))是不能再編譯器確定的,因?yàn)榫幾g器不知道指針或者引用指向的是哪種對(duì)象专控,故編譯器需要在程序運(yùn)行時(shí)選擇正確的虛方法的代碼,這被稱為動(dòng)態(tài)聯(lián)編抹凳。為了是動(dòng)態(tài)聯(lián)編能夠在運(yùn)行時(shí)正確的進(jìn)行決策,必須采用一些跟蹤基類的指針或者引用指向的對(duì)象類型踩官。
通常却桶,編譯器處理虛函數(shù)的方法是:給每個(gè)對(duì)象添加一個(gè)隱藏成員。隱藏成員中保存了一個(gè)指向虛函數(shù)表的指針蔗牡。虛函數(shù)表是一個(gè)保存函數(shù)地址的數(shù)組颖系,每一個(gè)地址都對(duì)應(yīng)該類中的一個(gè)虛函數(shù)”缭剑基類對(duì)象包含一個(gè)指針嘁扼,該指針指向基類中所有虛函數(shù)的地址表。派生類對(duì)象將包含一個(gè)指向自己(獨(dú)立)虛函數(shù)表的指針黔攒。
??如果派生類重寫(override)了虛函數(shù)趁啸,則派生類的虛函數(shù)表將保存新函數(shù)的地址强缘。
因此,當(dāng)用指針調(diào)用一個(gè)函數(shù)時(shí)不傅,回去指針?biāo)笇?duì)象的虛函數(shù)表中去查找被重寫后的虛函數(shù)旅掂。
重定義(redefining)
參照隱藏規(guī)則,派生類的成員函數(shù)隱藏了基類的同名函數(shù)访娶。所謂隱藏就是指派生類類型的對(duì)象商虐、引用、指針訪問(wèn)基類和派生類都有的同名函數(shù)的時(shí)候崖疤,訪問(wèn)的是派生類的函數(shù)秘车,隱藏了基類同名函數(shù)。派生類既然自動(dòng)繼承了基類的成員劫哼,那么基類成員就可以被派生類直接訪問(wèn)叮趴,那么為什么訪問(wèn)的是派生類的成員函數(shù)呢?所以隱藏 規(guī)則實(shí)際上就是默認(rèn)的c++名字解析過(guò)程权烧。
??在繼承機(jī)制下眯亦,派生類的類域被嵌套在基類的類域中,派生類的名字解析過(guò)程如下:
- 1)首先在派生類中查找改名字
- 2)如果第一步未查找到豪嚎,及派生類的類域?qū)Ω拿譄o(wú)法進(jìn)行解析搔驼,則編譯器在外圍基類類域查找改名字的定義
所以準(zhǔn)確來(lái)說(shuō)谈火,當(dāng)派生類和基類有同一名字的成員時(shí)侈询,派生類成員是隱藏了對(duì)基類成員的直接訪問(wèn)。那么如果要訪問(wèn)基類同名成員呢糯耍?加上類作用域限定例如:Base::g(float)就可以了扔字。
二.Java 方法重載和重寫
1.重載(overload)
Java中的重載和C++中的基本一致,這里再?gòu)?qiáng)調(diào)一下:
- 重載的方法必須具有不同的參數(shù)列表
- 重載的方法可以有不同的返回值類型
- 重載的方法可以有不同的訪問(wèn)修飾符
2.重寫(override)
若子類中聲明的方法與父類中的某一方法具有相同的方法名温技、返回值類型和參數(shù)列表革为,則子類中的方法將覆蓋父類中的方法。 如需要調(diào)用父類中原有的方法舵鳞,可以使用super關(guān)鍵字震檩,該關(guān)鍵字引用了當(dāng)前類的父類。
方法重寫的規(guī)則如下:
- 重寫方法的參數(shù)列表必須與被重寫方法的參數(shù)列表完全相同
- 重寫方法的返回值類型必須與被重寫方法的返回值類型完全相同
- 重寫方法的訪問(wèn)權(quán)限不能比被重寫方法的訪問(wèn)權(quán)限更嚴(yán)格蜓堕。比如:如果父類的一個(gè)方法被聲明為public抛虏,那么在子類中重寫該方法就不能聲明為protected
- 聲明為final的方法不能被重寫
- 聲明為static的方法不能被重寫,但是能夠在子類中再次聲明套才,父類中的static方法會(huì)被隱藏
- 子類和父類在同一個(gè)包中迂猴,那么子類可以重寫父類所有方法,除了聲明為private和final的方法
- 子類和父類不在同一個(gè)包中背伴,那么子類只能夠重寫父類的聲明為public和protected的非final方法
- 構(gòu)造方法不能被重寫
3.Java沒(méi)有“重定義”這個(gè)概念
三.重寫的意義
重寫伴隨著繼承沸毁,他的的意義主要在于實(shí)現(xiàn)多態(tài)峰髓,用父類的引用來(lái)操作子類對(duì)象,但在實(shí)際的運(yùn)行中對(duì)象將運(yùn)行自己重寫的方法息尺。
??實(shí)際應(yīng)用中携兵,用得最多的一種運(yùn)行時(shí)多態(tài),就是用只知道父類型(可能是類搂誉,更多的可能是接口)的定義眉孩,這樣只能調(diào)用父類型的方法,繼承該父類并重寫該方法的子類勒葱,就會(huì)自動(dòng)執(zhí)行重寫了的方法(這在大型軟件里很常用浪汪,父類型和子類型可能是不同的人編寫的,以利于協(xié)作編程)凛虽。
public class OverrideTest {
public static void main (String[] args) {
Animal h = new Horse();
h.eat();
Opertion o = new Horse();
o.run(30);
}
}
class Animal {
public void eat(){
System.out.println ("Animal is eating.");
}
}
class Horse extends Animal implements Opertion{
public void eat(){
System.out.println ("Horse is eating.");
}
public void buck(){
}
@Override
public void run(int time) {
System.out.println ("Horse is run: "+ time +"s");
}
}
public interface Opertion {
void run(int time);
}
輸出為:
Horse is eating.
Horse is run: 30s
在Java中死遭, Animal h = new Horse();
中引用h指向子類Horse對(duì)象,因此其調(diào)用的也是子類Horse的eat方法凯旋。但是呀潭,如果調(diào)用子類特有的方法,如上例的h.buck(); 會(huì)出現(xiàn)編譯錯(cuò)誤至非,除非強(qiáng)制轉(zhuǎn)換為子類對(duì)象((Horse) h).buck();
钠署。
??同時(shí)需要注意的是向上轉(zhuǎn)形是自動(dòng)的,即Animal h = new Horse();
父類的引用指向子類對(duì)象是可以的荒椭,但是子類的引用指向父類對(duì)象就是錯(cuò)的谐鼎,必須顯示的進(jìn)行強(qiáng)制轉(zhuǎn)換。
C++中由于動(dòng)態(tài)聯(lián)編趣惠,指針或者引用指向的虛函數(shù)可能會(huì)與上述行為略有不同狸棍。
四.Java和C++的一些比較
1.Java和C++中的重寫
可以看到,Java中重寫只要寫一個(gè)和父類函數(shù)同名同參數(shù)的同返回值類型的函數(shù)即可(這在C++中就是“重定義”)味悄,不需要抽象函數(shù)(即C艸中的虛函數(shù))草戈,因此Java中的普通函數(shù)就能起到C艸中虛函數(shù)的作用。
2.Java中為什么沒(méi)有“重定義”侍瑟?
上面我們說(shuō)了唐片,只要是一個(gè)和父類函數(shù)同名同參數(shù)的同返回值類型的函數(shù),不管加沒(méi)加“@override”注解涨颜,他就是重寫的函數(shù)费韭。此時(shí)這個(gè)重載的函數(shù)就相當(dāng)于C艸中“重定義”的函數(shù)。只不過(guò)Java中需要用super關(guān)鍵字來(lái)調(diào)用父類中的函數(shù)(需要參數(shù)相同)咐低,而C++中需要用作用域解析運(yùn)算符“::”來(lái)調(diào)用父類中的方法揽思。
3. Java抽象函數(shù)與C++純虛函數(shù)
Java中沒(méi)有虛函數(shù)的概念,因?yàn)镃++的虛函數(shù)是用來(lái)讓子類重寫的见擦,然而Java中普通方法就可以被子類重寫钉汗,因此沒(méi)有必要用虛函數(shù)羹令。
??Java中的抽象函數(shù)相當(dāng)于C艸中的純虛函數(shù),兩者換湯不換藥:
- C++中純虛函數(shù)形式為:virtual void print() = 0;
- Java中純虛函數(shù)形式為:abstract void print();
4.Java抽象類與C++抽象類
C++類中只要含有一個(gè)純虛函數(shù)损痰,該類就是抽象類
??Java抽象類是用abstract修飾聲明的類福侈,當(dāng)然類中至少要含有一個(gè)抽象方法,不然聲明的抽象類沒(méi)有意義
5.Java接口和C++虛基類
接口的存在是為了形成一種規(guī)約卢未,他更多是對(duì)“行為”的一種抽象(抽象類是對(duì)“屬性”的一種約束)肪凛。在三種的例子中我們也定義了一個(gè)接口
??接口中的方法會(huì)被隱式地指定為public abstract,接口中的變量會(huì)被隱式地指定為public static final變量辽社,并且接口中所有的方法不能有具體的實(shí)現(xiàn)伟墙,也就是說(shuō),接口中的方法必須都是抽象方法滴铅。從這里可以隱約看出接口和抽象類的區(qū)別戳葵,接口是一種極度抽象的類型,它比抽象類更加“抽象”汉匙,并且一般情況下不在接口中定義變量拱烁。
- C++中接口其實(shí)就是全虛基類。
- Java中接口是用interface修飾的類噩翠。
6. 小結(jié)
- C++虛函數(shù) == Java普通函數(shù)
- C++純虛函數(shù) == Java抽象函數(shù)
- C++虛基類 == Java抽象類