作者
egreenworld
依據DDJ的C/C++專欄作家Al Steven表示
他雖然不是很懂得Java﹐但是看到這些書中對於C++的物件導向概念的闡釋﹐有些地方明顯錯誤﹐真是令人擔心
本文假設讀者您已熟悉一些C/C++語言的概念﹐對Java也有初步的認識
而談論Java的interface與C++的多重繼承之主要異同處
interface與多重繼承的觀念
不管是Java的interface或是C++的多重繼承﹐在物件導向的理論裡﹐都算是蠻新穎的概念
所以這裡我們談的﹐是以程式語言的角度﹐看看Java interface的所有意義與功能﹐是否C++的多重繼承能全部诠釋?或是相反地以Java的來诠釋C++的
首先讓我們來復習一下什麼是C++的多重繼承
「繼承」通常在物件導向程式語言中﹐扮演著程式碼的重復利用的重責大任﹐而C++的多重繼承則讓某一個子類別可以繼承許多分屬於不同資料型別的父類別如下
#include
class Test {
public:
virtual void f() {puts(Test::f()); }
virtual void g() {puts(Test::g()); }
};
class Test {
public:
virtual void f() { puts(Test::f()); }
virtual void g() { puts(Test::g()); }
};
class Test : public Test public Test {
public:
virtual void gg() { puts(Test::gg()); }
};
void main() {
Test t; tf(); tf();
tg(); tg(); tgg();
}
// 程式輸出
Test::f() Test::f() Test::g()
Test::g() Test::gg()
程式﹑C++的多重繼承
根據[Rie]﹐認為正確使用物件導向技術中之「多重繼承」觀念﹐應該如下面的例子
假設有一個木造門﹐則
此木造門是門的一種(a kind of)
但門不是木造門的一部份(a part of)
木造門是木制品的一種
但木制品不是木造門的一部份
木制品不是門的一種
門也不是木制品的一種
所以您可以發現﹐多重繼承在使用時﹐必須非常小心﹐而且在許多時候﹐其實我們並不需要多重繼承的
Java也提供繼承機制﹐但還另外提供一個叫interface的概念由於Java的繼承機制只能提供單一繼承(就是只能繼承一種父類別)﹐所以就以Java的interface來代替C++的多重繼承interface就是一種介面﹐規定欲溝通的兩物件﹐其通訊該有的規范有哪些如以Java程式語言的角度來看﹐Java的interface則表示一些函數或資料成員﹐為另一些屬於不同類別的物件所需共同擁有﹐則將這些函數與資料成員﹐定義在一個interface中﹐然後讓所有不同類別的Java物件可以共同操作使用之
所以﹐對於Java的繼承與interface﹐我們總結如下
Java的class只能繼承一個父類別(用extends關鍵字)﹐但可以擁有(或稱實作)許多interface(用implements關鍵字)
Java的interface可以繼承許多別的interface(也是用extends關鍵字)﹐但不可以實作任何interface
因此﹐我們可以利用Java的interface來模擬C++的多重繼承如上面的例子可以轉化如下
interface Test {
public void f();
public void g();
}
interface Test {
public void f();
public void g();
}
interface Test extends Test Test {
public void gg();
}
class CTest implements Test {
public void f() { Systemoutprintln(Test::f()); }
public void g() { Systemoutprintln(Test::g()); }
public void f() { Systemoutprintln(Test::f()); }
public void g() { Systemoutprintln(Test::g()); }
public void gg() { Systemoutprintln(Test::gg()); }
}
class Run {
public void run() {
CTest ct=new CTest(); ctf();ctf();
ctg();ctg(); ctgg();
}}
class Main {
public static void main (String args[]) {
Run rr=new Run();
rrrun();
}}
// 程式輸出
Test::f() Test::f() Test::g()
Test::g() Test::gg()
程式﹑利用Java的interface完成C++的多重繼承功能
然而﹐根據[Ait]的文章顯示﹐他認為Java的interface比C++的多重繼承好學很多﹐也較容易懂﹐但是有其限制對於Java interface的易懂﹐在文章中﹐並沒有說明其主要即為「介面繼承」與「實作繼承」概念的差異
「介面繼承」就是只繼承父類別的函數名稱﹐然後子類別一定會實作取代之所以當我們以父類別的指標「多型」於各子類別時﹐由於子類別一定會實作父類別的多型函數﹐所以每個子類別的實作都不一樣﹐此時我們(使用父類別指標的人)並不知道此多型函數到底怎麼完成﹐因之稱為「黑箱設計」
「實作繼承」就是繼承父類別的函數名稱﹐子類別在實作時﹐也會用到父類別的函數實作所以我們(使用父類別指標的人)知道此多型函數怎麼完成工作﹐因為大概也跟父類別的函數實作差不多﹐因之稱為「白箱設計」
套用的Java的interface上﹐我們發現﹐Java的interface就是介面繼承﹐因為Java interface只能定義函數名稱﹐無法定義函數實作﹐所以子類別必須用「implements」關鍵字來實作之﹐且每個實作同一介面的子類別當然彼此不知道對方如何實作﹐因此為一個黑箱設計
Java的類別繼承則為實作繼承﹐子類別會用到父類別的實作(更正確地說應該是父類別有定義實作﹐所以子類別可能會使用到﹐即使不使用到也會遵循父類別實作的演算法)﹐所以父類別與子類別有一定程度的相關性﹔不像介面繼承﹐彼此只有函數名字剛好一樣而已
介面繼承與實作繼承﹐應對至Java的interface﹑class﹑extends與implements關鍵字﹐很容易了解其含意但是C++的繼承機制﹐似乎就沒有那麼容易解釋清楚的!所以這就是[Ait]文章中所表示的意思C++多重機制比較復雜
所以接下來我們將討論
C++的多重繼承有什麼功能﹐是Java的interface所達不到的?
在C++的arm中﹐或是[Str]的多重繼承章節裡﹐皆提到了下述著名的例子
#include
class t {
public:
virtual void f() { puts(t::f()); }
virtual void g() { puts(t::g()); }
};
class t : public virtual t {
public:
virtual void g() { puts(t::g()); }
};
class t : public virtual t {
public:
virtual void f() { puts(t::f()); }
};
class t : public t public t public virtual t { };
void main() {
t *tt=new t; t *tt=tt; t *tt=tt;
tt>f(); tt>g(); tt>f(); tt>g();
}
// 程式輸出
t::f() t::g() t::f() t::g()
程式﹑C++著名的環狀繼承
由上例﹐我們發現﹐C++的多重繼承具有下列兩個特質是Java的interface所不能達到的功能
圖﹑C++的環狀多重繼承
C++的多重繼承可以形成環狀繼承關系﹐如圖但是不管是Java的繼承機制或是interface﹐都不容許有環狀的情況發生換句話說﹐因為C++有virtual base的屬性的父類別﹐所有在多重繼承時﹐允許父類別被繼承兩次以上但Java則完全不行
本題中的tt指標﹐轉成tt指標後﹐執行f()函數時﹐仍然會正確地執行tt中的f()函數﹐也就是t::f()我們可以發現﹐這種找函數的方式﹐是先找函數的正確名稱﹐再找函數所屬的類別的正確名稱與Java的虛擬函數(或稱為abstract函數)不同Java的是先找指標(或參考)所屬的正確類別名稱﹐再繼續找類別名稱下的正確函數名稱
圖﹑對於虛擬函數C++與Java的各別作法
對於第二點參考圖C++的虛擬函數﹐可以參考[Sou]﹐C++編譯器對於每一個虛擬函數﹐均建立一個虛擬函數表與之應對﹐因為每一個虛擬函數在一個繼承樹可能有許多子類別實作之因
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25996.html