關於Java與c++隱藏、重寫不同實現機制的探討
一、文章來由
本人如今用c++很多其它。可是曾經Java也寫過不少,Java和c++非常像,可是深入挖一些,Java跟c++的差別非常大,就拿剛剛發的另一篇博文虛函數與多態小覽來說,裏面就感覺有非常多不同了,至少“重寫”在這兩個語言裏面的理解就不同了~~跟基友一番討論。決定把這個問題徹底捋一捋。由於這個是探討。所以有不同想法歡迎提出和評論。
二、關於“重寫”Override的定義
“重寫”多麽常見的一個概念。
1、首先說明“重寫”(Override)全然等於“覆蓋”,可能由於翻譯的問題,是個非常坑爹的問題
2、而且 Override 的定義在c++和Java中卻不同~~
c++中這樣定義“重寫”:
Override 是指派生類函數覆蓋基類函數:
(1)不同的範圍(分別位於派生類與基類)。
(2)函數名字同樣;
(3)參數同樣。參數不同都不稱之為重寫。根本就是兩個函數了;
(4)基類函數必須有virtual 關鍵字。
特別留意最後一點~~這是差別於隱藏的關鍵點
Java中這樣定義“重寫”:
(1)在子類中能夠依據須要對從基類中繼承來的方法進行重寫,方法重寫又稱方法覆蓋。
(2)重寫的方法和被重寫的方法必須具有同樣方法名稱、參數列表和返回類型。
(3)重寫方法不能使用比被重寫的方法更嚴格的訪問權限。如需父類中原有的方法,可使用super關鍵字,該關鍵字引用了當前類的父類。
有沒有發現什麽,就是Java中根本沒有virtual關鍵字,所以沒有“重寫”和“隱藏”的差別。Java中重寫一定隱藏了父類的方法,要訪問就要用super關鍵字就能夠了,所以Java裏面僅僅有 override。
三、深入理解c++中的 Hidden 與 Override
看了定義就已經發現不同了,如今來自己看看c++中 Hidden 與 override 的實現機制。
談到c++的 override 就一定要談到 hidden。他們在c++中是要絕對區分開的。
3.1 Hidden
c++隱藏的定義
(1)假設派生類的函數與基類的函數同名,可是參數不同。
此時。不論有無 virtual 關鍵字,基類的函數將被隱藏(註意別與重載混淆)
(2)假設派生類的函數與基類的函數同名,而且參數也同樣。可是基類函數沒有 virtual關鍵字。此時,基類的函數被隱藏(註意別與覆蓋混淆)
這是上一篇博客也提到的類容。那麽到底實現機制是什麽?
在調用一個類的成員函數的時候。編譯器會沿著類的繼承鏈逐級的向上查找函數的定義。假設找到了那麽就停止查找了。所以假設一個派生類和一個基類都有同一個同名(暫且不論參數是否同樣)的函數。而編譯器終於選擇了在派生類中的函數,那麽我們就說這個派生類的成員函數”隱藏”了基類的成員函數。也就是說它阻止了編譯器繼續向上查找函數的定義。。
。
。
回到隱藏的定義中,前面已經說了有virtual關鍵字而且分別位於派生類和基類的同名,同參數函數構成覆蓋的關系,因此隱藏的關系僅僅有例如以下的可能:
1)必須分別位於派生類和基類中
2)必須同名
3)參數不同的時候本身已經不構成覆蓋關系了,所以此時是否是virtual函數已經不重要了
當參數同樣的時候就要看時候有virtual關鍵字了,有的話就是覆蓋關系,沒有的時候就是隱藏關系了
3.2 Override
覆蓋指的是派生類的虛擬函數覆蓋了基類的同名且參數同樣的函數。
既然是和虛擬函數掛鉤,說明了這個是一個多態支持的特性,所謂的覆蓋指的是用基類對象的指針或者引用時訪問虛擬函數的時候會依據實際的類型決定所調用的函數,因此此時派生類的成員函數能夠”覆蓋”掉基類的成員函數。
註意: 唯有同名且參數同樣還有帶有virtual關鍵字而且分別在派生類和基類的函數才幹構成虛擬函數,這個也是派生類的重要特征。
而且,由於是和多態掛鉤的,所以僅僅有在使用類對象指針或者引用的時候才幹使用上。
總之中的一個句話:覆蓋函數都是虛函數。反之不然~~
四、代碼實例
口說無憑,上代碼~~
Java代碼例如以下:
public class PolyTest
{
public static void main(String[] args)
{
//向上類型轉換
Cat cat = new Cat();
Animal animal = cat;
animal.sing();
//向下類型轉換
Animal a = new Cat();
Cat c = (Cat)a;
c.sing();
c.eat();
//編譯錯誤
//用父類引用調用父類不存在的方法
//Animal a1 = new Cat();
//a1.eat();
//編譯錯誤
//向下類型轉換時僅僅能轉向指向的對象類型
//Animal a2 = new Cat();
//Cat c2 = (Dog)a2;
}
}
class Animal
{
public void sing()
{
System.out.println("Animal is singing!");
}
}
class Dog extends Animal
{
public void sing()
{
System.out.println("Dog is singing!");
}
}
class Cat extends Animal
{
public void sing()
{
System.out.println("Cat is singing!");
}
public void eat()
{
System.out.println("Cat is eating!");
}
}
跑出來結果例如以下:
可見,Java即便第二組向下轉換【父類強轉子類】,輸出任然是子類的東西,並不像c++的晚綁定。
c++代碼例如以下:
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void sing()
{
printf("Animal is singing!\n");
}
};
class Cat : public Animal
{
public:
void sing()
{
printf("Cat is singing!\n");
}
void eat()
{
printf("Cat is eating!\n");
}
};
int main(void)
{
Animal a;
Cat *cp = (Cat *)&a;
cp->sing();
cp->eat();
//編譯錯誤
// Cat c;
// Animal a = c;
// a.eat();
return 0;
}
執行結果例如以下:
c++這裏就良好體現了晚綁定的特性~~~即使強制轉換了,指向的任然是基類對象的地址,所以通過虛函數列表還是能夠找到基類的實現~
關於隱藏,另一個非常好的東西:
(1)子類和父類返回值參數同樣,函數名同樣,有virtual關鍵字。則由對象的類型決定調用哪個函數。
(2)子類和父類僅僅要函數名同樣。沒有virtual關鍵字,則子類的對象沒有辦法調用到父類的同名函數,父類的同名函數被隱藏了,也能夠強制調用父類的同名函數class::funtion_name。
(3)子類和父類參數不同,函數名同樣。有virtual關鍵字,則不存在多態性,子類的對象沒有辦法調用到父類的同名函數,父類的同名函數被隱藏了。也能夠強制調用父類的同名函數class::funtion_name。
(4)子類和父類返回值不同。參數同樣,函數名同樣,有virtual關鍵字。則編譯出錯error C2555編譯器不同意函數名參數同樣返回值不同的函數重載。
五、小結
Java與c++在隱藏、重寫的不同就介紹到這裏。剛剛在線Markdown沒有保存,就打開了上一篇的編輯。差點以為要沖掉這篇了。碼了好多字啊。嚇傻了。。
。看來以後要多導入到本地~~哈哈哈
—END—
參考文獻
[1] http://bbs.chinaunix.net/thread-643467-1-1.html
[2] http://www.cnblogs.com/mengdd/archive/2012/12/25/2832288.html
關於Java與c++隱藏、重寫不同實現機制的探討