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++编译器对于每一个虚拟函数﹐均建立一个虚拟函数表与之应对﹐因为每一个虚拟函数在一个继承树可能有许多子类别实作之因