多繼承可以看作是單繼承的擴展。所謂多繼承是指派生類具有多個基類,派生類與每個基類之間的關系仍可看作是一個單繼承。
多繼承下派生類的定義格式如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,…
{
<派生類類體>
};
其中,<繼承方式1>,<繼承方式2>,…是三種繼承方式:public、private、protected之一。例如:
class A
{
…
};
class B
{
…
};
class C : public A, public B
{
…
};
其中,派生類C具有兩個基類(類A和類B),因此,類C是多繼承的。按照繼承的規定,派生類C的成員包含了基類A, B中成員以及該類本身的成員。
多繼承的構造函數
在多繼承的情況下,派生類的構造函數格式如下:
<派生類名>(<總參數表>):<基類名1>(<參數表1>),<基類名2>(<參數表2>),…
<子對象名>(<參數表n+1>),…
{
<派生類構造函數體>
}
其中,<總參數表>中各個參數包含了其後的各個分參數表。
多繼承下派生類的構造函數與單繼承下派生類構造函數相似,它必須同時負責該派生類所有基類構造函數的調用。同時,派生類的參數個數必須包含完成所有基類初始化所需的參數個數。
派生類構造函數執行順序是先執行所屬基類的構造函數,再執行派生類本身構造函數,處於同一層次的各基類構造函數的執行順序取決於定義派生類時所指定的各基類順序,與派生類構造函數中所定義的成員初始化列表的各項順序無關。也就是說,執行基類構造函數的順序取決於定義派生類時基類的順序。可見,派生類構造函數的成員初始化列表中各項順序可以任意地排列。
下面通過一個例子來說明派生類構造函數的構成及其執行順序。
#include <iostream.h>
class B1
{
public:
B1(int i)
{
b1 = i;
cout《"構造函數 B1."《i《 endl;
}
void print()
{
cout《"B1.print()"《b1《endl;
}
private:
int b1;
};
class B2
{
public:
B2(int i)
{
b2 = i;
cout《"構造函數 B2."《i《 endl;
}
void print()
{
cout《"B2.print()"《b2《endl;
}
private:
int b2;
};
class B3
{
public:
B3(int i)
{
b3 = i;
cout《"構造函數 B3."《i《endl;
}
int getb3()
{
return b3;
}
private:
int b3;
};
class A : public B2, public B1
{
public:
A(int i, int j, int k, int l):B1(i), B2(j), bb(k)
{
a = l;
cout《"構造函數 A."《a《endl;
}
void print()
{
B1::print();
B2::print();
cout《"A.print()"《a《","《bb.getb3()《endl;
}
private:
int a;
B3 bb;
};
void main()
{
A aa(1, 2, 3, 4);
aa.print();
}
該程序的輸出結果為:
構造函數 B2.2
構造函數 B1.1
構造函數 B3.3
構造函數 A.4
B1.print()。1
B2.print()2
A.print()4, 3
在該程序中,作用域運算符::用於解決作用域沖突的問題。在派生類A中的print()函數的定義中,使用了B1::print;和B2::print();語句分別指明調用哪一個類中的print()函數,這種用法應該學會。
二義性問題
一般說來,在派生類中對基類成員的訪問應該是唯一的,但是,由於多繼承情況下,可能造成對基類中某成員的訪問出現了不唯一的情況,則稱為對基類成員訪問的二義性問題。
實際上,在上例已經出現過這一問題,回憶一下上例中,派生類A的兩基類B1和B2中都有一個成員函數print()。如果在派生類中訪問 print()函數,到底是哪一個基類的呢?於是出現了二義性。但是在上例中解決了這個問題,其辦法是通過作用域運算符::進行了限定。如果不加以限定,則會出現二義性問題。
下面再舉一個簡單的例子,對二義性問題進行深入討論。例如:
class A
{
public:
void f();
};
class B
{
public:
void f();
void g();
};
class C : public A, public B
{
public:
void g();
void h();
};
如果定義一個類C的對象c1:
C c1;
則對函數f()的訪問
c1.f();
便具有二義性:是訪問類A中的f(),還是訪問類B中的f()呢?
解決的方法可用前面用過的成員名限定法來消除二義性,例如:
c1.A::f();
或者
c1.B::f();
但是,最好的解決辦法是在類C中定義一個同名成員f(),類C中的f()再根據需要來決定調用A::f(),還是B::f(),還是兩者皆有,這樣,c1.f()將調用C::f()。
同樣地,類C中成員函數調用f()也會出現二義性問題。例如:
viod C::h()
{
f();
}
這裡有二義性問題,該函數應修改為:
void C::h()
{
A::f();
}
或者
void C::h()
{
B::f();
}
或者
void C::f()
{
A::f();
B::f();
}
另外,在前例中,類B中有一個成員函數g(),類C中也有一個成員函數g()。這時,
c1.g();
不存在二義性,它是指C::g(),而不是指B::g()。因為這兩個g()函數,一個出現在基類B,一個出現在派生類C,規定派生類的成員將支配基類中的同名成員。因此,上例中類C中的g()支配類B中的g(),不存在二義性,可選擇支配者的那個名字。
當一個派生類從多個基類派生類,而這些基類又有一個共同的基類,則對該基類中說明的成員進行訪問時,也可能會出現二義性。例如:
class A
{
public:
int a;
};
class B1 : public A
{
private:
int b1;
};
class B2 : public A
{
private:
int b2;
};
class C : public B1, public B2
{
public:
int f();
private:
int c;
};
已知:C c1;
下面的兩個訪問都有二義性:
c1.a;
c1.A::a;
而下面的兩個訪問是正確的:
c1.B1::a;
c1.B2::a;
類C的成員函數f()用如下定義可以消除二義性:
int C::f()
{
retrun B1::a + B2::a;
}
由於二義性的原因,一個類不可以從同一個類中直接繼承一次以上,例如:
class A : public B, public B
{
…
}
這是錯誤的。