C++单继承
在《基類和派生類》中講述了單繼承的基本概念,這節著重講述繼承的具體應用。
在單繼承中,每個類可以有多個派生類,但是每個派生類只能有一個基類,從而形成樹形結構。
成員訪問權限的控制
在《基類和派生類》一講中,我們講述了派生類和派生類的對象對基類成員的訪問權限的若干規定,這里通過一個實例進一步討論訪問權限的具體控制,然后得出在使用三種繼承方式時的調用方法。
//繼承性的public繼承方式的訪問權限的例子
#include
file://定義基類A
class A
{
public:
A() { cout<<"類A的構造函數!"< A(int a) { Aa = a, aa = a, aaa = a; }
void Aprint() { cout<<"類A打印自己的private成員aa:"< int Aa;
private:
int aa;
protected:
int aaa;
};
file://定義由基類A派生的類B
class B : public A
{
public:
B() { cout<<"類B的構造函數!"< B(int i, int j, int k);
void Bprint() { cout<<"類B打印自己的private成員bb和protected成員bbb:"< void B_Aprint() { cout<<"類B的public函數訪問類A的public數據成員Aa:"< cout<<"類B的public函數訪問類A的protected數據成員aaa:"< GetAaaa();
GetAaaa1();}
private:
int bb;
void GetAaaa() { cout<<"類B的private函數訪問類A的public數據成員Aa:"< cout<<"類B的private函數訪問類A的protected數據成員aaa:"< protected:
int bbb;
void GetAaaa1() { cout<<"類B的protected函數訪問類A的public數據成員Aa:"< cout<<"類B的protected函數訪問類A的protected數據成員aaa:"< };
file://基類B的構造函數,需負責對基類A的構造函數的初始化
B::B(int i, int j, int k):A(i), bb(j), bbb(k) {}
file://程序主函數
void main()
{
B b1(100, 200, 300); file://定義類B的一個對象b1,并初始化構造函數和基類構造函數
b1.B_Aprint(); file://類B調用自己的成員函數B_Aprint函數
b1.Bprint(); file://類B對象b1訪問自己的private和protected成員
b1.Aprint(); file://通過類B的對象b1調用類A的public成員函數
}
該程序的輸出結果為:
類B的public函數訪問類A的public數據成員Aa:100
類B的public函數訪問類A的protected數據成員aaa:100
類B的private函數訪問類A的public數據成員Aa:100
類B的private函數訪問類A的protected數據成員aaa:100
類B的protected函數訪問類A的public數據成員Aa:100
類B的protected函數訪問類A的protected數據成員aaa:100
類B打印自己的private成員bb和protected成員bbb:200,300
類A打印自己的private成員aa:100
上述是屬public繼承方式,我們可以得出以下結論:
在公有繼承(public)時,派生類的public、private、protected型的成員函數可以訪問基類中的公有成員和保護成員;派生類的對象僅可訪問基類中的公有成員。
讓我們把繼承方式public改為private,編譯結果出現1處如下錯誤:
'Aprint' : cannot access public member declared in class 'A'
出錯語句在于:b1.Aprint();,因此,我們可以得出以下結論:
在公有繼承(private)時,派生類的public、private、protected型的成員函數可以訪問基類中的公有成員和保護成員;但派生類的對象不可訪問基類中的任何成員。另,使用class關鍵字定義類時,缺省的繼承方式是private,也就是說,當繼承方式為私有繼承時,可以省略private。
讓我們把繼承方式public改為protected,可以看出,結果和private繼承方式一樣。
構造函數和析構函數
派生類的構造函數和析構函數的構造是討論的主要問題,讀者要掌握它。
1. 構造函數
我們已知道,派生類的對象的數據結構是由基類中說明的數據成員和派生類中說明的數據成員共同構成。將派生類的對象中由基類中說明的數據成員和操作所構成的封裝體稱為基類子對象,它由基類中的構造函數進行初始化。
構造函數不能夠被繼承,因此,派生類的構造函數必須通過調用基類的構造函數來初始化基類子對象。所以,在定義派生類的構造函數時除了對自己的數據成員進行初始化外,還必須負責調用基類構造函數使基類數據成員得以初始化。如果派生類中還有子對象時,還應包含對子對象初始化的構造函數。
派生類構造函數的一般格式如下:
<派生類名>(<派生類構造函數總參數表>):<基類構造函數>(參數表1),<子對象名>(<參數表2>)
{
<派生類中數據成員初始化>
};
派生類構造函數的調用順序如下:
· 基類的構造函數
· 子對象類的構造函數(如果有的話)
· 派生類構造函數
在前面的例子中,B::B(int i, int j, int k):A(i), bb(j), bbb(k)就是派生類構造函數的定義,下面再舉一個構造派生類構造函數的例子。
#include
class A
{
public:
A() { a=0; cout<<"類A的缺省構造函數./n"; }
A(int i) { a=i; cout<<"類A的構造函數./n"; }
~A() { cout<<"類A的析構函數./n"; }
void Print() const { cout< int Geta() { reutrn a; }
private:
int a;
}
class B : public A
{
public:
B() { b=0; cout<<"類B的缺省構造函數./n"; }
B(int i, int j, int k);
~B() { cout<<"類B的析構函數./n"; }
void Print();
private:
int b;
A aa;
}
B::B(int i, int j, int k):A(i), aa(j)
{
b=k;
cout<<"類B的構造函數./n";
}
void B::Print()
{
A::Print();
cout< }
void main()
{
B bb[2];
bb[0] = B(1, 2, 5);
bb[1] = B(3, 4, 7);
for(int i=0; i<2; i++)
bb[i].Print();
}
2. 構造函數
當對象被刪除時,派生類的析構函數被執行。由于析構函數也不能被繼承,因此在執行派生類的析構函數時,基類的析構函數也將被調用。執行順序是先執行派生類的構造函數,再執行基類的析構函數,其順序與執行構造函數時的順序正好相反。這一點從前面講過的例子可以看出,請讀者自行分析。
3. 派生類構造函數使用中應注意的問題
(1) 派生類構造函數的定義中可以省略對基類構造函數的調用,其條件是在基類中必須有缺省的構造函數或者根本沒有定義構造函數。當然,基類中沒有定義構造函數,派生類根本不必負責調用基類的析構函數。
(2) 當基類的構造函數使用一個或多個參數時,則派生類必須定義構造函數,提供將參數傳遞給基類構造函數途徑。在有的情況下,派生類構造函數的函數體可能為空,僅起到參數傳遞作用。如本講第一個例子就屬此種情況。
子類型化和類型適應
1. 子類型化
子類型的概念涉及到行為共享,它與繼承有著密切關系。
有一個特定的類型S,當且僅當它至少提供了類型T的行為,由稱類型S是類型T的子類型。子類型是類型之間的一般和特殊的關系。
在繼承中,公有繼承可以實現子類型。例如:
class A
{
public:
void Print() const { cout<<"A::print() called./n"; }
};
class B : public A
{
public:
void f() {}
};
類B繼承了類A,并且是公有繼承方式。因此,可以說類B是類A的一個子類型。類A還可以有其他的子類型。類B是類A的子類型,類B具備類A中的操作,或者說類A中的操作可被用于操作類B的對象。
子類型關系是不可逆的。這就是說,已知B是A的子類型,而認為A也是B的子類型是錯誤的,或者說,子類型關系是不對稱不。
因此,可以說公有繼承可以實現子類型化。
2. 類型適應
類型適應是指兩種類型之間的關系。例如,B類型適應A類型是指B類型的對象能夠用于A類型的對象所能使用的場合。
前面講過的派生類的對象可以用于基類對象所能使用的場合,我們說派生類適應于基類。
同樣道理,派生類對象的指針和引用也適應于基類對象的指針和引用。
子類型化與類型適應是致的。A類型是B類型的子類型,那么A類型必將適應于B類型。
子類型的重要性就在于減輕程序人員編寫程序代碼的負擔。因為一個函數可以用于某類型的對象,則它也可以用于該類型的各個子類型的對象,這樣就不必為處理這些子類型的對象去重載該函數。
在單繼承中,每個類可以有多個派生類,但是每個派生類只能有一個基類,從而形成樹形結構。
成員訪問權限的控制
在《基類和派生類》一講中,我們講述了派生類和派生類的對象對基類成員的訪問權限的若干規定,這里通過一個實例進一步討論訪問權限的具體控制,然后得出在使用三種繼承方式時的調用方法。
//繼承性的public繼承方式的訪問權限的例子
#include
file://定義基類A
class A
{
public:
A() { cout<<"類A的構造函數!"< A(int a) { Aa = a, aa = a, aaa = a; }
void Aprint() { cout<<"類A打印自己的private成員aa:"< int Aa;
private:
int aa;
protected:
int aaa;
};
file://定義由基類A派生的類B
class B : public A
{
public:
B() { cout<<"類B的構造函數!"< B(int i, int j, int k);
void Bprint() { cout<<"類B打印自己的private成員bb和protected成員bbb:"< void B_Aprint() { cout<<"類B的public函數訪問類A的public數據成員Aa:"< cout<<"類B的public函數訪問類A的protected數據成員aaa:"< GetAaaa();
GetAaaa1();}
private:
int bb;
void GetAaaa() { cout<<"類B的private函數訪問類A的public數據成員Aa:"< cout<<"類B的private函數訪問類A的protected數據成員aaa:"< protected:
int bbb;
void GetAaaa1() { cout<<"類B的protected函數訪問類A的public數據成員Aa:"< cout<<"類B的protected函數訪問類A的protected數據成員aaa:"< };
file://基類B的構造函數,需負責對基類A的構造函數的初始化
B::B(int i, int j, int k):A(i), bb(j), bbb(k) {}
file://程序主函數
void main()
{
B b1(100, 200, 300); file://定義類B的一個對象b1,并初始化構造函數和基類構造函數
b1.B_Aprint(); file://類B調用自己的成員函數B_Aprint函數
b1.Bprint(); file://類B對象b1訪問自己的private和protected成員
b1.Aprint(); file://通過類B的對象b1調用類A的public成員函數
}
該程序的輸出結果為:
類B的public函數訪問類A的public數據成員Aa:100
類B的public函數訪問類A的protected數據成員aaa:100
類B的private函數訪問類A的public數據成員Aa:100
類B的private函數訪問類A的protected數據成員aaa:100
類B的protected函數訪問類A的public數據成員Aa:100
類B的protected函數訪問類A的protected數據成員aaa:100
類B打印自己的private成員bb和protected成員bbb:200,300
類A打印自己的private成員aa:100
上述是屬public繼承方式,我們可以得出以下結論:
在公有繼承(public)時,派生類的public、private、protected型的成員函數可以訪問基類中的公有成員和保護成員;派生類的對象僅可訪問基類中的公有成員。
讓我們把繼承方式public改為private,編譯結果出現1處如下錯誤:
'Aprint' : cannot access public member declared in class 'A'
出錯語句在于:b1.Aprint();,因此,我們可以得出以下結論:
在公有繼承(private)時,派生類的public、private、protected型的成員函數可以訪問基類中的公有成員和保護成員;但派生類的對象不可訪問基類中的任何成員。另,使用class關鍵字定義類時,缺省的繼承方式是private,也就是說,當繼承方式為私有繼承時,可以省略private。
讓我們把繼承方式public改為protected,可以看出,結果和private繼承方式一樣。
構造函數和析構函數
派生類的構造函數和析構函數的構造是討論的主要問題,讀者要掌握它。
1. 構造函數
我們已知道,派生類的對象的數據結構是由基類中說明的數據成員和派生類中說明的數據成員共同構成。將派生類的對象中由基類中說明的數據成員和操作所構成的封裝體稱為基類子對象,它由基類中的構造函數進行初始化。
構造函數不能夠被繼承,因此,派生類的構造函數必須通過調用基類的構造函數來初始化基類子對象。所以,在定義派生類的構造函數時除了對自己的數據成員進行初始化外,還必須負責調用基類構造函數使基類數據成員得以初始化。如果派生類中還有子對象時,還應包含對子對象初始化的構造函數。
派生類構造函數的一般格式如下:
<派生類名>(<派生類構造函數總參數表>):<基類構造函數>(參數表1),<子對象名>(<參數表2>)
{
<派生類中數據成員初始化>
};
派生類構造函數的調用順序如下:
· 基類的構造函數
· 子對象類的構造函數(如果有的話)
· 派生類構造函數
在前面的例子中,B::B(int i, int j, int k):A(i), bb(j), bbb(k)就是派生類構造函數的定義,下面再舉一個構造派生類構造函數的例子。
#include
class A
{
public:
A() { a=0; cout<<"類A的缺省構造函數./n"; }
A(int i) { a=i; cout<<"類A的構造函數./n"; }
~A() { cout<<"類A的析構函數./n"; }
void Print() const { cout< int Geta() { reutrn a; }
private:
int a;
}
class B : public A
{
public:
B() { b=0; cout<<"類B的缺省構造函數./n"; }
B(int i, int j, int k);
~B() { cout<<"類B的析構函數./n"; }
void Print();
private:
int b;
A aa;
}
B::B(int i, int j, int k):A(i), aa(j)
{
b=k;
cout<<"類B的構造函數./n";
}
void B::Print()
{
A::Print();
cout< }
void main()
{
B bb[2];
bb[0] = B(1, 2, 5);
bb[1] = B(3, 4, 7);
for(int i=0; i<2; i++)
bb[i].Print();
}
2. 構造函數
當對象被刪除時,派生類的析構函數被執行。由于析構函數也不能被繼承,因此在執行派生類的析構函數時,基類的析構函數也將被調用。執行順序是先執行派生類的構造函數,再執行基類的析構函數,其順序與執行構造函數時的順序正好相反。這一點從前面講過的例子可以看出,請讀者自行分析。
3. 派生類構造函數使用中應注意的問題
(1) 派生類構造函數的定義中可以省略對基類構造函數的調用,其條件是在基類中必須有缺省的構造函數或者根本沒有定義構造函數。當然,基類中沒有定義構造函數,派生類根本不必負責調用基類的析構函數。
(2) 當基類的構造函數使用一個或多個參數時,則派生類必須定義構造函數,提供將參數傳遞給基類構造函數途徑。在有的情況下,派生類構造函數的函數體可能為空,僅起到參數傳遞作用。如本講第一個例子就屬此種情況。
子類型化和類型適應
1. 子類型化
子類型的概念涉及到行為共享,它與繼承有著密切關系。
有一個特定的類型S,當且僅當它至少提供了類型T的行為,由稱類型S是類型T的子類型。子類型是類型之間的一般和特殊的關系。
在繼承中,公有繼承可以實現子類型。例如:
class A
{
public:
void Print() const { cout<<"A::print() called./n"; }
};
class B : public A
{
public:
void f() {}
};
類B繼承了類A,并且是公有繼承方式。因此,可以說類B是類A的一個子類型。類A還可以有其他的子類型。類B是類A的子類型,類B具備類A中的操作,或者說類A中的操作可被用于操作類B的對象。
子類型關系是不可逆的。這就是說,已知B是A的子類型,而認為A也是B的子類型是錯誤的,或者說,子類型關系是不對稱不。
因此,可以說公有繼承可以實現子類型化。
2. 類型適應
類型適應是指兩種類型之間的關系。例如,B類型適應A類型是指B類型的對象能夠用于A類型的對象所能使用的場合。
前面講過的派生類的對象可以用于基類對象所能使用的場合,我們說派生類適應于基類。
同樣道理,派生類對象的指針和引用也適應于基類對象的指針和引用。
子類型化與類型適應是致的。A類型是B類型的子類型,那么A類型必將適應于B類型。
子類型的重要性就在于減輕程序人員編寫程序代碼的負擔。因為一個函數可以用于某類型的對象,則它也可以用于該類型的各個子類型的對象,這樣就不必為處理這些子類型的對象去重載該函數。
總結
- 上一篇: [scala-spark]11. RDD
- 下一篇: C++子对象和堆对象