基類和派生類
繼承允許我們依據另一個類來定義一個類,這使得創建和維護一個應用程序變得更容易。這樣做,也達到了重用代碼功能和提高執行時間的效果。
當創建一個類時,您不需要重新編寫新的數據成員和成員函數,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱爲基類,新建的類稱爲派生類。
C++中有分三種繼承方式,如下表所示。
這裏不去過多地討論什麼是繼承,這裏對繼承時產生的一些事進行討論。
繼承時,構造函數做了什麼
話不多說,上代碼
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout<<"this is Father()"<<endl;
}
};
class Son : public Father
{
public:
Son()
{
cout<<"this is son()"<<endl;
}
};
int main()
{
Son p;
return 0;
}
運行結果
this is Father()
this is son()
在上面的代碼中,我在Father這個基類,和Son這個派生類裏面分別添加了構造函數。在主程序裏面定義了一個派生類的實例化對象。然而從運行結果裏發現,分別進行了Father的構造函數和Son的構造函數,從而得出結論:在定義基類的實例化對象時,不僅會執行自身的構造函數,還會執行派生類的構造函數。
既然執行了構造函數,那麼他們執行的先後順序是什麼樣的呢,從上述的代碼中,可以看出基類的構造函數是執行在派生類之前的。那麼遇到多個繼承關係,他們的繼承關係又是什麼樣的呢?
爲了內容不那麼長,這裏省略部分代碼
class GrandFather{
...};
class Mother : public GrandFather{
...};
class Mr_Wang {
};
class Son : public GrandFather, virtual public Mr_Wang{
...};
int main()
{
Son s;
return 0;
}
由上述程序可見,繼承關係如圖所示
這裏Mr_Wang和Son的關係是虛擬繼承(虛擬繼承不在本次討論範圍內),所以這裏用紅色表示。
程序的運行結果如下
Mr_Wang
GrandFather
mother
Son
從運行結果中,不難看出
虛擬繼承構造函數的優先級最高,其次,基類的優先級會高於派生類
使用基類指針指向派生類,會發生什麼
下面分兩點來討論
使用基類指針指向派生類,構造函數的順序是什麼樣
話不多說,依舊上代碼
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout<<"this is Father()"<<endl;
}
};
class Son : public Father
{
public:
Son()
{
cout<<"this is son()"<<endl;
}
};
int main()
{
Father *p = new Son;
delete p;
p = NULL;
return 0;
}
執行結果
this is Father()
this is son()
從運行結果中,不難看出,基類指針指向派生類,構造函數還是會先構造基類,再構造派生類。
使用基類指針指向派生類,對於屬性的訪問是什麼樣的
這裏先說結論:使用基類指針指向派生類,該指針只能引用基類成員。
下面修改代碼
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout<<"this is Father()"<<endl;
}
void test_func_one()
{
cout<<"this is test_func_one";
}
};
class Son : public Father
{
public:
Son()
{
cout<<"this is son()"<<endl;
}
void test_func_two()
{
cout<<"this is test_func_two"<<endl;
}
};
int main()
{
Father *p = new Son;
p->test_func_two();
delete p;
p = NULL;
return 0;
}
這裏我定義了一個基類對象的指針,指向派生類對象。意圖去訪問派生類裏面的函數。看一下編譯結果。
error: ‘class Father’ has no member named ‘test_func_two’; did you mean ‘test_func_one’?
p->test_func_two();
^~~~~~~~~~~~~
顯然,報錯了。不能訪問。驗證了剛剛的結論。
那麼我如果非要訪問派生類對象怎麼辦呢?
繼續修改代碼
#include <iostream>
using namespace std;
class Father
{
public:
Father()
{
cout<<"this is Father()"<<endl;
}
virtual void test_func_one()
{
cout<<"this is test_func_one";
}
};
class Son : public Father
{
public:
Son()
{
cout<<"this is son()"<<endl;
}
void test_func_one()
{
cout<<"this is test_func_two"<<endl;
}
};
int main()
{
Father *p = new Son;
p->test_func_one();
delete p;
p = NULL;
return 0;
}
編譯通過,看一下運行結果
this is Father()
this is son()
this is test_func_two
通過以上現象,不難發現。
基類對象的指針如果指向派生類對象,不能直接訪問派生類對象的屬性。如果要訪問,可以在基類裏面定義虛函數,在派生類裏對這個虛函數進行重新定義。就可以訪問派生類對象。
這種花裏胡哨的用法,有沒有什麼實際用途
我們可以通過基類來定義一個模板,派生類用來定義一些模板往上的東西。這麼說可能有點抽象,舉個具體的例子。
比如我們要做攝像頭,不同型號的攝像頭可能具體實現的操作不太一樣,但是流程應該都差不多。所以可以在基類裏面聲明一些Init,Start這類操作的虛函數作爲模板。然後我們可以在派生類裏針對具體的攝像頭型號來重新定義這些虛函數。