☆ C++ 繼承與派生
在友元類中我們知道,一旦在一個類中聲明瞭友元類,那麼友元類便擁有了訪問該類的所有許可權,可以在自己的類中對宣告自己的類進行一系列操作。
友元類主要目的是為了拓展友元類的功能,但是友元類的許可權未免太多了,有什麼辦法可以削減其訪問許可權呢?
繼承與派生應運而生;
本篇文章主要介紹如下內容:
1> 公有繼承(public)
2> 私有繼承(private)
3> 保護繼承(protected)
4> 三種繼承方式的共同點與差異
5> 派生類的建構函式
6> 多層派生 (儲存地址)
7> 多繼承
8> 多繼承時派生類的建構函式
9> 多繼承時派生類的解構函式
10> 多繼承時的同名隱藏原則
11> 一些基本問題 [ review ]
****************************************************************************************************************************************
一:公有繼承
1> 公有繼承特點:
2> 公有繼承示例 (這裡的建構函式將在第五點講到)
class Person{ private: int age; string name; string sex; public: Person(int a, string b, string c) { age = a; name = b; sex = c; } void Print1(); }; void Person::Print1() { cout << name << " " << sex << " " << age << endl; } class Student2:public Person{ private: public: Student2(int a, string b, string c) :Person(a, b, c) { } void Print(); }; void Student2::Print(){ cout << "Student2:"; Person::Print1(); } int main() { Student2 S2(25,"Student2","Male"); S2.Print(); S2.Print1(); return 0; }
這裡派生類的物件是可以直接訪問基類public成員函式的,但是私有繼承則不允許訪問:
二:私有繼承
1> 私有繼承特點
2> 私有繼承舉例 (將上面的繼承方式改為private)
這裡我們就可以直接看到私有繼承的第三條特點:
一旦繼承方式由公有變為私有,使用類的物件訪問基類的許可權已經被完全抹殺了,本來公有繼承還可以訪問public資料成員的~
三:保護繼承
1> 保護繼承特點
2> 保護繼承舉例
除了第一條不私有繼承不同外,其他都是相同的。
四:三種繼承方式的共同點與差異
1> 三種繼承方式的第一個特點都是控制基類被繼承後的屬性,
這樣做是為了當繼承類再被繼承後明確各個繼承類與基類之間的關係
2> 第二條特點三者是一致的,規定了在派生類中對基類中各部分的訪問許可權。
而第三條特點則是對在派生類的外部,通過物件對基類進行操作許可權進行了限制。
五:派生類的建構函式
1> 基類的建構函式不會被派生類繼承,但是當派生類中沒有對基類的函式進行初始化的時候,會自動呼叫基類中的建構函式進行初始化;
2> 當繼承類基類中都沒有建構函式,一律使用系統預設的建構函式;
3> 如第一點所說,這時就要在派生類中使用指定的格式呼叫基類建構函式進行初始化。
在開篇的公有繼承示例中:
class Person{
private:
int age;
string name;
string sex;
public:
Person(int a, string b, string c)
{
age = a; name = b; sex = c;
}
void Print1();
};
void Person::Print1()
{
cout << name << " " << sex << " " << age << endl;
}
class Student2:public Person{
private:
int pocket_month;
public:
Student2(int a, string b, string c,int d) :Person(a, b, c) {
pocket_month = d;
}
void Print();
};
void Student2::Print(){
cout << "Student2:";
Person::Print1();
}
我們可以發現Person基類中有相應的建構函式,當被繼承的時候,我們就要考慮如何對基類資料進行初始化,那如何對基類中的資料進行初始化呢?
這裡額外提一點,那到底為什麼要初始化基類資料呢?
回到本質,繼承與派生的最初目的是為了減少程式碼的重用,即當兩個類A、B中有大量一致的成員,可以將一致的成員提取出來,獨自封裝為一個類C,然後再通過繼承來使用這個重複的類C,
然後分別在A、B類中給C中的資料賦予不同的資料進而完善A、B類,這樣做就使得這些重複的部分在系統記憶體中只有一個備份,從而減少了資源開銷,而且A、B類所需求的資料沒有改變。
派生類中建構函式格式:
建構函式中的部分稱呼:
六:多層派生 (派生類繼續作為基類派生)
1> 示例:
#include <iostream>
using namespace std;
class B0 //基類B0宣告
{
public:
void display() { cout << "B0::display()" << endl; } //公有成員函式
};
class B1 : public B0
{
public:
void display() { cout << "B1::display()" << endl; }
};
class D1 : public B1
{
public:
void display() { cout << "D1::display()" << endl; }
};
void fun(B0 *ptr)
{
ptr->display();
} //"物件指標->成員名"
void main() //主函式
{
B0 b0; //宣告B0類物件
B1 b1; //宣告B1類物件
D1 d1; //宣告D1類物件
B0 *p; //宣告B0類指標
p = &b0; //B0類指標指向B0類物件
fun(p);
p = &b1; //B0類指標指向B1類物件
fun(p);
p = &d1; //B0類指標指向D1類物件
fun(p);
}
2> 輸出結果
3> 結果分析以及多層派生儲存方式理解
為了解決輸出一致的問題,引入了虛擬函式的概念:
點我即達<虛擬函式部分>
七:多繼承
1> 舉例
#include <iostream>
using namespace std;
class A {
public:
void setA(int);
void showA();
private:
int a;
};
class B {
public:
void setB(int);
void showB();
private:
int b;
};
class C : public A, private B {
public:
void setC(int, int, int);
void showC();
private:
int c;
};
void A::setA(int x)
{
a = x;
}
void A::showA(){
cout << a << endl;
}
void B::setB(int x)
{
b = x;
}
void B::showB() {
cout << b << endl;
}
void C::setC(int x, int y, int z)
{ //派生類成員直接訪問基類的
//公有成員
setA(x);
setB(y);
c = z;
}
void C::showC() {
cout << c << endl;
}
int main()
{
C obj;
obj.setA(5);
obj.showA();
// obj.setB(6); //錯誤,因為B被私有繼承,使用物件無法訪問B類中的任何資料成員
// obj.showB(); //錯誤
obj.setC(6, 7, 9);
obj.showC();
return 0;
}
2> 多繼承的語法格式
這裡需要額外注意一個問題,因為每一個派生類都是複製了一份基類資料成員作為自己的模板,因而就會產生另一種情況(二義性):
這個時候,子類C就會因為繼承不明確而使得程式出現錯誤:
為了結局這種文體,引入了虛基類的概念:
八:多繼承時派生類的建構函式
1> 多繼承時繼承類含有內嵌物件,此時建構函式呼叫順序 (基類建構函式->內嵌建構函式->本身建構函式 )
-->>舉例:
#include <iostream>
using namespace std;
class B1 //基類B1,建構函式有引數
{
public:
B1(int i) { cout << "constructing B1 " << i << endl; }
};
class B2 //基類B2,建構函式有引數
{
public:
B2(int j) { cout << "constructing B2 " << j << endl; }
};
class B3 //基類B3,建構函式無引數
{
public:
B3() { cout << "constructing B3 *" << endl; }
};
class C : public B2, public B1, public B3
{
public: //派生類的公有成員
C(int a, int b, int c, int d) :
B1(a), memberB2(d), memberB1(c), B2(b) { }
private: //派生類的私有物件成員
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
void main()
{
C obj(1, 2, 3, 4);
}
2> 輸出結果
3> 具體輸出順序分析 (與建構函式中的宣告順序無關 )
九:多繼承時派生類的解構函式
1> 原始碼示例
#include <iostream>
using namespace std;
class B1 {
public:
B1(int i) {
cout << "Constructint B1 " << i << endl;
}
~B1() {
cout << "Destructing B1 " << endl;
}
};
class B2 {
public:
B2(int i) {
cout << "Constructint B2 " << i << endl;
}
~B2() {
cout << "Destructing B2 " << endl;
}
};
class B3 {
public:
B3(int i) {
cout << "Constructint B3 " << i << endl;
}
~B3() {
cout << "Destructing B3 " << endl;
}
};
class D1:public B2,public B1,public B3 {
public:
D1(int a, int b, int c, int d,int e,int f):B2(a),B1(b),B3(c),memberB1(d),memberB2(e),memberB3(f){
}
private:
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
D1 d1(1, 2, 3, 4, 5, 6);
return 0;
}
2> 執行結果
十:多繼承時的同名隱藏原則
1> 介紹
當基類中的成員函式和繼承類的成員函式同名時,使用派生物件呼叫該重名函式的時候,呼叫的是派生物件中自己的函式;
如果需要使用派生物件來呼叫基類中的重名函式,
需要使用基類名限制其作用域:
2> 原始碼示例
#include <iostream>
using namespace std;
class B1 {
public:
B1(int i) {
cout << "Constructint B1 " << i << endl;
}
~B1() {
cout << "Destructing B1 " << endl;
}
void Print() {
cout << "This is B1 !" << endl;
}
};
class B2 {
public:
B2(int i) {
cout << "Constructint B2 " << i << endl;
}
~B2() {
cout << "Destructing B2 " << endl;
}
void Print() {
cout << "This is B2 !" << endl;
}
};
class B3 {
public:
B3(int i) {
cout << "Constructint B3 " << i << endl;
}
~B3() {
cout << "Destructing B3 " << endl;
}
};
class D1:public B2,public B1,public B3 {
public:
D1(int a, int b, int c, int d,int e,int f):B2(a),B1(b),B3(c),memberB1(d),memberB2(e),memberB3(f){
}
void Print() {
cout << "This is D1 !" << endl;
}
private:
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
D1 d1(1, 2, 3, 4, 5, 6);
d1.Print();
d1.B1::Print();
d1.B2::Print();
return 0;
}
十一:一些基本問題 [ review ]
1> 類的繼承方式有幾種?不同的繼承方式下,基類成員的訪問屬性到派生類中有怎樣的變化?
繼承方式分為三種:public、private、protected
public繼承方式下基類的public和protected成員的訪問屬性在派生類中保持不變,但基類的private成員不可以直接訪問;
private繼承方式下基類的public和protected成員的訪問屬性在派生類中訪問屬性變為private,但基類的private成員不可以直接訪問;
priotected繼承方式下基類的public和protected成員的訪問屬性在派生類中訪問屬性變為protected,但基類的private成員不可以直接訪問;
2> 類的保護成員有哪些特徵?
protected成員可以被派生類的成員函式訪問,但是對於外界是隱蔽的;若為公有派生,則基類的protecyed成員在派生類中屬性不變;若為私有派生,則基類中的protected成員在派生類中屬性變為private。
3> 使成員函式成為行內函數的方法有哪些?
(一)在類定義的時候直接給出成員函式的實現;
(二)在類中僅僅給出函式宣告,在類定義之外使用inline關鍵字給出成員函式的實現。
4> 友元函式與成員函式、一般函式有何差別?
友元函式不是類的成員函式,它可以在類中的任何位置進行宣告,聲明後可以訪問類的全部成員;
友元函式與一般函式的不同點在於友元函式需要在類中進行宣告,且可以訪問該類的所有成員,而一般函式卻不可以。