如何做網(wǎng)站頁(yè)面免費(fèi)的人民日?qǐng)?bào)官網(wǎng)
1. 繼承的簡(jiǎn)單介紹
1.1 繼承的概念
繼承(inheritance)機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許我們?cè)诒3衷蓄愄匦缘幕A(chǔ)上進(jìn)行擴(kuò)展,增加方法(成員函數(shù))和屬性(成員變量),這樣產(chǎn)生新的類,稱派生類。
繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過(guò)程。以前我們接觸的函數(shù)層次的復(fù)用,繼承是類設(shè)計(jì)層次的復(fù)用。
簡(jiǎn)單來(lái)說(shuō),被繼承的類叫做父類(或基類),繼承自父類的類叫做子類(或派生類)。
子類擁有父類的所有成員,在此基礎(chǔ)之上可以對(duì)父類進(jìn)行拓展。
1.2 子類的定義方式
class 子類名 : 訪問(wèn)限定符 父類名
{// 拓展內(nèi)容
}
通常來(lái)說(shuō),父類和子類具有類別上的包含關(guān)系。
例如,老師和同學(xué)不僅具有人的基本特點(diǎn),還在人的基礎(chǔ)之上有了自己的拓展,而老師和同學(xué)都屬于人。
我們?cè)谟肅++進(jìn)行描述的時(shí)候,就可以將老師和同學(xué)設(shè)計(jì)成人的子類:
人類(父類):
class Person
{
public:// 進(jìn)?校園/圖書(shū)館/實(shí)驗(yàn)室刷?維碼等?份認(rèn)證void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "張三"; // 姓名string _address; // 地址string _tel; // 電話int _age = 18; // 年齡
};
學(xué)生類(子類):
class Student : public Person
{
public:// 學(xué)習(xí)void study(){// ...}
protected:int _stuid; // 學(xué)號(hào)
};
老師類(子類):
class Teacher : public Person
{
public:// 授課void teaching(){//...}
protected:string title; // 職稱
};
1.3 繼承方式
訪問(wèn)限定符限定的是繼承的方式,不同訪問(wèn)限定符下的繼承方式如下:
類成員 / 繼承方式 | public | protected | private |
父類的public成員 | 子類的public成員 | 子類的protected成員 | 子類的private成員 |
父類的protected成員 | 子類的protected成員 | 子類的protected成員 | 子類的private成員 |
父類的private成員 | 子類無(wú)法直接顯式訪問(wèn) | 子類無(wú)法直接顯式訪問(wèn) | 子類無(wú)法直接顯式訪問(wèn) |
子類成員的訪問(wèn)權(quán)限:public?> protected > private > 父類中被修飾為private
其中,protected訪問(wèn)限定符是伴隨著繼承的出現(xiàn)而出現(xiàn)的。被它修飾的成員,意味著無(wú)法在類外部進(jìn)行訪問(wèn),而可以在類內(nèi)部或其子類中被訪問(wèn)。
無(wú)論子類以何種方式繼承,在父類中被修飾為private的成員子類都不可直接顯式訪問(wèn)。
繼承方式用于限定繼承下來(lái)的成員訪問(wèn)權(quán)限不能高于繼承方式。
當(dāng)不指定繼承方式時(shí),class子類默認(rèn)為private方式繼承,struct子類默認(rèn)為public方式繼承。
在實(shí)際運(yùn)用中一般使用都是public繼承,幾乎很少使用protetced/private繼承,也不提倡使用protetced/private繼承,因?yàn)閜rotetced/private繼承下來(lái)的成員都只能在派生類的類類里使用,實(shí)際中擴(kuò)展維護(hù)性不強(qiáng)。
可以采用protected/private繼承的一個(gè)例子:
#include<vector>
#define CONTAINER std::vectortemplate<class T>
class stack : private CONTAINER<T>
{
public:void push(const T& x){CONTAINER<T>::push_back(x);}void pop(){CONTAINER<T>::pop_back();}const T& top(){return CONTAINER<T>::back();}bool empty(){return CONTAINER<T>::empty();}
};int main()
{stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();} return 0;
}
這里采用了繼承vector的方式來(lái)實(shí)現(xiàn)stack,但我希望用戶只用stack的接口而不直接訪問(wèn)vector的成員函數(shù),此時(shí)就可以采取protected/private的繼承方式。
1.4 繼承類模板
相信細(xì)心的小伙伴已經(jīng)發(fā)現(xiàn)了,在上面stack的例子中,我們每次調(diào)用vector的接口時(shí)都對(duì)其類域進(jìn)行了指定,否則會(huì)發(fā)生編譯報(bào)錯(cuò):
error C3861: “push_back”: 找不到標(biāo)識(shí)符
這是因?yàn)?#xff0c;模板是按需實(shí)例化的,當(dāng)類模板中的函數(shù)沒(méi)有被調(diào)用時(shí),其就不會(huì)實(shí)例化。
我們?cè)趯?shí)例化stack<T>對(duì)象時(shí),vector<T>對(duì)象也跟著實(shí)例化。但是vector<T>中,只有成員變量和構(gòu)造函數(shù)被實(shí)例化了。
我們?cè)谡{(diào)用push_back()函數(shù)時(shí),由于this指針為stack<T>類型,所以編譯器并不會(huì)將vector模板中的push_back()函數(shù)實(shí)例化,而是直接去尋找其定義或聲明。
所以,繼承模板類時(shí),調(diào)用父類函數(shù)要注意指定類域。
2. 父類和子類的轉(zhuǎn)換
1. public繼承的子類對(duì)象可以賦值給父類的指針 / 基類的引用。這里有個(gè)形象的說(shuō)法叫切片或者切割。寓意把子類中父類那部分切出來(lái),父類指針或引用指向的是子類中切出來(lái)的父類那部分。
2. 父類對(duì)象不能賦值給子類對(duì)象。
3. 父類的指針或者引用可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給子類的指針或者引用。但是必須是父類的指針是指向子類對(duì)象時(shí)才是安全的。(這里父類如果是多態(tài)類型,可以使用RTTI(Run-Time TypeInformation)的dynamic_cast 來(lái)進(jìn)行識(shí)別后進(jìn)行安全轉(zhuǎn)換)
class Person
{
protected:string _name; // 姓名string _sex; // 性別int _age; // 年齡
};class Student : public Person
{
public:int _No; // 學(xué)號(hào)
};int main()
{Student sobj;// 1.派?類對(duì)象可以賦值給基類的指針/引?Person* pp = &sobj;Person& rp = sobj;// ?類對(duì)象可以賦值給基類的對(duì)象是通過(guò)調(diào)?后?會(huì)講解的基類的拷?構(gòu)造完成的Person pobj = sobj;//2.基類對(duì)象不能賦值給派?類對(duì)象,這?會(huì)編譯報(bào)錯(cuò)sobj = pobj;return 0;
}
?3. 繼承中的作用域及"隱藏"規(guī)則
隱藏規(guī)則:
1. 在繼承體系中父類和子類都有獨(dú)立的作用域。
2. 子類和基類中有同名成員,子類成員將屏蔽父類對(duì)同名成員的直接訪問(wèn),這種情況叫隱藏。(在子類成員函數(shù)中,可以使用"父類::父類成員"顯式訪問(wèn))
// Student的_num和Person的_num構(gòu)成隱藏關(guān)系,可以看出這樣代碼雖然能跑,但是?常容易混淆
class Person
{
protected:string _name = "?李?"; // 姓名int _num = 111; // ?份證號(hào)
};class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " ?份證號(hào):" << Person::_num << endl;cout << " 學(xué)號(hào):" << _num << endl;}
protected:int _num = 999; // 學(xué)號(hào)
};int main()
{Student s1;s1.Print();return 0;
};
?這里,程序運(yùn)行的結(jié)果為
3. 需要注意的是如果是成員函數(shù)的隱藏,只需要函數(shù)名相同(分別在父類和子類中定義的函數(shù)只會(huì)觸發(fā)隱藏而不會(huì)觸發(fā)函數(shù)重載)就構(gòu)成隱藏。
4. 注意在實(shí)際中在繼承體系里面最好不要定義同名的成員
class A
{
public:void fun(){cout << "func()" << endl;}
};class B : public A
{
public:void fun(int i){cout << "func(int i)" << i << endl;}
};
此時(shí),B中的fun函數(shù)會(huì)隱藏掉A中的fun函數(shù),而不會(huì)構(gòu)成重載。
4. 子類的默認(rèn)成員函數(shù)
子類的默認(rèn)成員函數(shù)主要是用于處理父類沒(méi)有的成員成員變量,父類自身的成員交由其自己的默認(rèn)成員函數(shù)去處理。
4.1 構(gòu)造函數(shù)
子類在構(gòu)造函數(shù)的初始化列表中可以顯式調(diào)用父類的構(gòu)造函數(shù)對(duì)父類成員變量進(jìn)行初始化,若未顯式調(diào)用則會(huì)調(diào)用父類的默認(rèn)構(gòu)造函數(shù)。
如果沒(méi)有顯式調(diào)用父類的構(gòu)造函數(shù),且父類沒(méi)有默認(rèn)構(gòu)造函數(shù),那么此時(shí)就會(huì)發(fā)生報(bào)錯(cuò)。
顯式調(diào)用父類構(gòu)造函數(shù)的方式是:
父類名(參數(shù)列表)
Student(const char* name, int num):Person(name), _num(num)
{cout << "Student()" << endl;
}
由于父類和子類的可以發(fā)生轉(zhuǎn)換,所以在子類的拷貝構(gòu)造中,將子類對(duì)象直接傳給父類的構(gòu)造函數(shù)即可調(diào)用父類的拷貝構(gòu)造:
Student(const Student& s):Person(s), _num(s._num)
{cout << "Student(const Student& s)" << endl;
}
4.2 賦值重載
必須要在子類的operator=中顯式調(diào)用父類的operator=才能按照預(yù)期正常的對(duì)父類成員進(jìn)行拷貝,否則只會(huì)完成淺拷貝。
要注意,子類的operator=會(huì)隱藏父類的operator=,調(diào)用父類的operator=需要指定類域。
Student& operator = (const Student& s)
{cout << "Student& operator= (const Student& s)" << endl;if (this != &s){// 構(gòu)成隱藏,所以需要顯?調(diào)?Person::operator =(s);_num = s._num;} return* this;
}
4.3 析構(gòu)函數(shù)
父類的析構(gòu)函數(shù)會(huì)在子類的析構(gòu)函數(shù)被調(diào)用之后自動(dòng)調(diào)用,無(wú)需顯式調(diào)用。