
一.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ù),跟父類無關(guān))
基本條件
- 子類和父類函數(shù)的名稱相同,參數(shù)也相同,父類中的函數(shù)不是virtual,父類的函數(shù)將被隱藏
- 子類和父類的函數(shù)名稱相同,但參數(shù)不同,此時(shí)不管父類函數(shù)是不是virtual函數(shù),都將被隱藏。
4.深入理解C++中的重寫(override)與重定義(redefining)
上面我們對C++中的重寫(override)與重定義(redefining)做了概念上的區(qū)分,只要記住上面的概念特征就嗯呢該區(qū)分這兩種操作,下面我們來從原理上說明下這兩種操作有什么區(qū)別:
重寫(override)
上面我們已經(jīng)說了,C++中重寫的時(shí)候重寫的是父類中的虛函數(shù)(virtual),因此我們需要從虛函數(shù)的工作原理說起。
??由于對于沒有使用virtual的函數(shù),程序?qū)⒏鶕?jù)引用類型或指針類型選擇方法;如果使用了virtual,程序?qū)⒏鶕?jù)引用或指針指向的對象類型來選擇方法,因此對于虛函數(shù),到底使用哪個(gè)函數(shù)(父類或者子類中的函數(shù))是不能再編譯器確定的,因?yàn)榫幾g器不知道指針或者引用指向的是哪種對象,故編譯器需要在程序運(yùn)行時(shí)選擇正確的虛方法的代碼,這被稱為動態(tài)聯(lián)編。為了是動態(tài)聯(lián)編能夠在運(yùn)行時(shí)正確的進(jìn)行決策,必須采用一些跟蹤基類的指針或者引用指向的對象類型。
通常,編譯器處理虛函數(shù)的方法是:給每個(gè)對象添加一個(gè)隱藏成員。隱藏成員中保存了一個(gè)指向虛函數(shù)表的指針。虛函數(shù)表是一個(gè)保存函數(shù)地址的數(shù)組,每一個(gè)地址都對應(yīng)該類中的一個(gè)虛函數(shù)?;悓ο蟀粋€(gè)指針,該指針指向基類中所有虛函數(shù)的地址表。派生類對象將包含一個(gè)指向自己(獨(dú)立)虛函數(shù)表的指針。
??如果派生類重寫(override)了虛函數(shù),則派生類的虛函數(shù)表將保存新函數(shù)的地址。

因此,當(dāng)用指針調(diào)用一個(gè)函數(shù)時(shí),回去指針?biāo)笇ο蟮奶摵瘮?shù)表中去查找被重寫后的虛函數(shù)。
重定義(redefining)
參照隱藏規(guī)則,派生類的成員函數(shù)隱藏了基類的同名函數(shù)。所謂隱藏就是指派生類類型的對象、引用、指針訪問基類和派生類都有的同名函數(shù)的時(shí)候,訪問的是派生類的函數(shù),隱藏了基類同名函數(shù)。派生類既然自動繼承了基類的成員,那么基類成員就可以被派生類直接訪問,那么為什么訪問的是派生類的成員函數(shù)呢?所以隱藏 規(guī)則實(shí)際上就是默認(rèn)的c++名字解析過程。
??在繼承機(jī)制下,派生類的類域被嵌套在基類的類域中,派生類的名字解析過程如下:
- 1)首先在派生類中查找改名字
- 2)如果第一步未查找到,及派生類的類域?qū)Ω拿譄o法進(jìn)行解析,則編譯器在外圍基類類域查找改名字的定義
所以準(zhǔn)確來說,當(dāng)派生類和基類有同一名字的成員時(shí),派生類成員是隱藏了對基類成員的直接訪問。那么如果要訪問基類同名成員呢?加上類作用域限定例如:Base::g(float)就可以了。
二.Java 方法重載和重寫
1.重載(overload)
Java中的重載和C++中的基本一致,這里再強(qiáng)調(diào)一下:
- 重載的方法必須具有不同的參數(shù)列表
- 重載的方法可以有不同的返回值類型
- 重載的方法可以有不同的訪問修飾符
2.重寫(override)
若子類中聲明的方法與父類中的某一方法具有相同的方法名、返回值類型和參數(shù)列表,則子類中的方法將覆蓋父類中的方法。 如需要調(diào)用父類中原有的方法,可以使用super關(guān)鍵字,該關(guān)鍵字引用了當(dāng)前類的父類。
方法重寫的規(guī)則如下:
- 重寫方法的參數(shù)列表必須與被重寫方法的參數(shù)列表完全相同
- 重寫方法的返回值類型必須與被重寫方法的返回值類型完全相同
- 重寫方法的訪問權(quán)限不能比被重寫方法的訪問權(quán)限更嚴(yán)格。比如:如果父類的一個(gè)方法被聲明為public,那么在子類中重寫該方法就不能聲明為protected
- 聲明為final的方法不能被重寫
- 聲明為static的方法不能被重寫,但是能夠在子類中再次聲明,父類中的static方法會被隱藏
- 子類和父類在同一個(gè)包中,那么子類可以重寫父類所有方法,除了聲明為private和final的方法
- 子類和父類不在同一個(gè)包中,那么子類只能夠重寫父類的聲明為public和protected的非final方法
- 構(gòu)造方法不能被重寫
3.Java沒有“重定義”這個(gè)概念
三.重寫的意義
重寫伴隨著繼承,他的的意義主要在于實(shí)現(xiàn)多態(tài),用父類的引用來操作子類對象,但在實(shí)際的運(yùn)行中對象將運(yùn)行自己重寫的方法。
??實(shí)際應(yīng)用中,用得最多的一種運(yùn)行時(shí)多態(tài),就是用只知道父類型(可能是類,更多的可能是接口)的定義,這樣只能調(diào)用父類型的方法,繼承該父類并重寫該方法的子類,就會自動執(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對象,因此其調(diào)用的也是子類Horse的eat方法。但是,如果調(diào)用子類特有的方法,如上例的h.buck(); 會出現(xiàn)編譯錯(cuò)誤,除非強(qiáng)制轉(zhuǎn)換為子類對象((Horse) h).buck();。
??同時(shí)需要注意的是向上轉(zhuǎn)形是自動的,即Animal h = new Horse();父類的引用指向子類對象是可以的,但是子類的引用指向父類對象就是錯(cuò)的,必須顯示的進(jìn)行強(qiáng)制轉(zhuǎn)換。
C++中由于動態(tài)聯(lián)編,指針或者引用指向的虛函數(shù)可能會與上述行為略有不同。
四.Java和C++的一些比較
1.Java和C++中的重寫
可以看到,Java中重寫只要寫一個(gè)和父類函數(shù)同名同參數(shù)的同返回值類型的函數(shù)即可(這在C++中就是“重定義”),不需要抽象函數(shù)(即C艸中的虛函數(shù)),因此Java中的普通函數(shù)就能起到C艸中虛函數(shù)的作用。
2.Java中為什么沒有“重定義”?
上面我們說了,只要是一個(gè)和父類函數(shù)同名同參數(shù)的同返回值類型的函數(shù),不管加沒加“@override”注解,他就是重寫的函數(shù)。此時(shí)這個(gè)重載的函數(shù)就相當(dāng)于C艸中“重定義”的函數(shù)。只不過Java中需要用super關(guān)鍵字來調(diào)用父類中的函數(shù)(需要參數(shù)相同),而C++中需要用作用域解析運(yùn)算符“::”來調(diào)用父類中的方法。
3. Java抽象函數(shù)與C++純虛函數(shù)
Java中沒有虛函數(shù)的概念,因?yàn)镃++的虛函數(shù)是用來讓子類重寫的,然而Java中普通方法就可以被子類重寫,因此沒有必要用虛函數(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è)抽象方法,不然聲明的抽象類沒有意義
5.Java接口和C++虛基類
接口的存在是為了形成一種規(guī)約,他更多是對“行為”的一種抽象(抽象類是對“屬性”的一種約束)。在三種的例子中我們也定義了一個(gè)接口
??接口中的方法會被隱式地指定為public abstract,接口中的變量會被隱式地指定為public static final變量,并且接口中所有的方法不能有具體的實(shí)現(xiàn),也就是說,接口中的方法必須都是抽象方法。從這里可以隱約看出接口和抽象類的區(qū)別,接口是一種極度抽象的類型,它比抽象類更加“抽象”,并且一般情況下不在接口中定義變量。
- C++中接口其實(shí)就是全虛基類。
- Java中接口是用interface修飾的類。
6. 小結(jié)
- C++虛函數(shù) == Java普通函數(shù)
- C++純虛函數(shù) == Java抽象函數(shù)
- C++虛基類 == Java抽象類