如何作做網(wǎng)站百度一下進(jìn)入首頁(yè)
現(xiàn)在也是結(jié)束了初階部分的內(nèi)容,今天開(kāi)始就進(jìn)入進(jìn)階部分了。一刻也沒(méi)有為初階的結(jié)束而哀悼,立刻趕來(lái)“戰(zhàn)場(chǎng)”的是進(jìn)階部分里的繼承
文章目錄
- 1.繼承的概念和定義
- 1.1繼承的概念
- 1.2繼承的定義
- 1.2.1繼承的格式
- 1.2.2再講訪(fǎng)問(wèn)限定符(詳講protected)
- 1.2.3**繼承基類(lèi)成員三種訪(fǎng)問(wèn)方式**
- 2.基類(lèi)和派生類(lèi)對(duì)象賦值轉(zhuǎn)換
- 3.繼承中的作用域
- 4.派生類(lèi)的默認(rèn)成員函數(shù)
- 4.1構(gòu)造函數(shù)
- 4.2重載=和析構(gòu)函數(shù)
- 5.繼承與友元
- 6.繼承與靜態(tài)成員
- 7.復(fù)雜的菱形繼承及菱形虛擬繼承
- 7.1單繼承、多繼承和菱形繼承
- 7.2解決菱形繼承
- 7.3解決菱形繼承原理
- 8.繼承和組合
1.繼承的概念和定義
1.1繼承的概念
繼承(inheritance)是一種面向?qū)ο缶幊痰臋C(jī)制,允許一個(gè)類(lèi)(派生類(lèi))從另一個(gè)類(lèi)(基類(lèi))繼承屬性和行為。
繼承機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)中使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類(lèi)特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類(lèi),稱(chēng)派生類(lèi)。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過(guò)程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類(lèi)設(shè)計(jì)層次的復(fù)用
#include<iostream>
#include<string>
using namespace std;class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "Nero";int _age = 18;
};class Teacher :public Person//公有繼承
{
protected:int _jobid;//老師的工號(hào)
};class Student :public Person
{
protected:int _stuid;//學(xué)生的學(xué)號(hào)
};int main(){Student s;Teacher t;s.Print();t.Print();return 0;
}
1.2繼承的定義
1.2.1繼承的格式
class Base {// 基類(lèi)的成員和方法
};class Derived : [訪(fǎng)問(wèn)權(quán)限] Base {// 派生類(lèi)的成員和方法
};
class Base
表示基類(lèi)的定義。class Derived : [訪(fǎng)問(wèn)權(quán)限] Base
表示派生類(lèi)的定義,并指定基類(lèi)為Base
。[訪(fǎng)問(wèn)權(quán)限]
可選,表示繼承的訪(fǎng)問(wèn)權(quán)限,可以是public
、protected
或private
。如果不指定,默認(rèn)為private
。
1.2.2再講訪(fǎng)問(wèn)限定符(詳講protected)
- Public成員:
public
成員在類(lèi)的內(nèi)部和外部都可以被訪(fǎng)問(wèn)。- 類(lèi)的外部代碼可以直接訪(fǎng)問(wèn)對(duì)象的
public
成員。
- Protected成員:
protected
成員在類(lèi)的內(nèi)部可以被訪(fǎng)問(wèn),但在類(lèi)的外部不能直接訪(fǎng)問(wèn)。- 派生類(lèi)(子類(lèi))==可以訪(fǎng)問(wèn)基類(lèi)(父類(lèi))的
protected
==成員。
- Private成員:
private
成員只能在類(lèi)的內(nèi)部被訪(fǎng)問(wèn),外部不能直接訪(fǎng)問(wèn)。- 派生類(lèi)也不能訪(fǎng)問(wèn)基類(lèi)的
private
成員。
所以,protected和private的訪(fǎng)問(wèn)權(quán)限相同(針對(duì)本類(lèi)內(nèi)外來(lái)說(shuō)),但是二者對(duì)繼承的子類(lèi)來(lái)說(shuō)是不一樣的
相對(duì)應(yīng)的,訪(fǎng)問(wèn)限定符有三個(gè)。那么繼承方式也是有三種繼承:
1.2.3繼承基類(lèi)成員三種訪(fǎng)問(wèn)方式
類(lèi)成員/繼承方式 | public繼承 | protected繼承 | private繼承 |
---|---|---|---|
基類(lèi)的public成員 | 派生類(lèi)的public成員 | 派生類(lèi)的protected成員 | 派生類(lèi)的private成員 |
基類(lèi)的protected成員 | 派生類(lèi)的protected成員 | 派生類(lèi)的protected成員 | 派生類(lèi)的private成員 |
基類(lèi)的private成員 | 在派生類(lèi)中不可見(jiàn) | 在派生類(lèi)中不可見(jiàn) | 在派生類(lèi)中不可見(jiàn) |
基類(lèi)private成員在派生類(lèi)中無(wú)論以什么方式繼承都是不可見(jiàn)的。這里的不可見(jiàn)是指基類(lèi)的私有成員還是被繼承到了派生類(lèi)對(duì)象中,但是語(yǔ)法上限制派生類(lèi)對(duì)象不管在類(lèi)里面還是類(lèi)外面都不能去訪(fǎng)問(wèn)它。
基類(lèi)private成員在派生類(lèi)中是不能被訪(fǎng)問(wèn),如果基類(lèi)成員不想在類(lèi)外直接被訪(fǎng)問(wèn),但需要在派生類(lèi)中能訪(fǎng)問(wèn),就定義為protected。可以看出保護(hù)成員限定符是因繼承才出現(xiàn)的
實(shí)際上面的表格我們進(jìn)行一下總結(jié)會(huì)發(fā)現(xiàn),基類(lèi)的私有成員在子類(lèi)都是不可見(jiàn)?;?lèi)的其他成員在子類(lèi)的訪(fǎng)問(wèn)方式相等于Min(成員在基類(lèi)的訪(fǎng)問(wèn)限定符,繼承方式),public > protected > private
使用關(guān)鍵字class時(shí)默認(rèn)的繼承方式是private,使用struct時(shí)默認(rèn)的繼承方式是public,當(dāng)然我們最好還是顯示的寫(xiě)出繼承方式。
在實(shí)際運(yùn)用中一般使用都是public繼承,幾乎很少使用protetced/private繼承,也不提倡使用protetced/private繼承,因?yàn)閜rotetced/private繼承下來(lái)的成員都只能在派生類(lèi)的類(lèi)里面使用
class A
{
public:int _public;
protected:int _protected;
private:int _private;
};class B :public A
{
public:void print_public(){cout << _public << endl;}void print_protected(){cout << _protected << endl;}void print_private(){cout << _private << endl;}
};int main()
{B b;return 0;
}
這里可以看到已經(jīng)報(bào)錯(cuò)了,因?yàn)榛?lèi)的private成員在派生類(lèi)中是不可見(jiàn)的
2.基類(lèi)和派生類(lèi)對(duì)象賦值轉(zhuǎn)換
派生類(lèi)對(duì)象可以賦值給基類(lèi)的對(duì)象/ 基類(lèi)的指針/基類(lèi)的引用。有個(gè)形象的說(shuō)法叫切片或者切割(走了一個(gè)特殊處理:中間不會(huì)產(chǎn)生一個(gè)臨時(shí)變量):寓意把派生類(lèi)中父類(lèi)那部分切來(lái)賦值過(guò)去。
基類(lèi)對(duì)象不能賦值給派生類(lèi)對(duì)象(目前是這樣的)
基類(lèi)的指針或者引用可以通過(guò)強(qiáng)制類(lèi)型轉(zhuǎn)換賦值給派生類(lèi)的指針或者引用。但是必須是基類(lèi)的指針是指向派生類(lèi)對(duì)象時(shí)才是安全的。
class Student : public Person//學(xué)生類(lèi)繼承于Person類(lèi)
{
public:int _No; // 學(xué)號(hào)(學(xué)生類(lèi)自己的成員變量)
};
void test1()
{Student stu;//子類(lèi)對(duì)象可以賦值給父類(lèi)對(duì)象/指針/引用Person per = stu;//走了一個(gè)特殊處理:中間不會(huì)產(chǎn)生一個(gè)臨時(shí)變量Person* pper = &stu;//這個(gè)指針也是直接指向?qū)ο筚M(fèi)父類(lèi)那一部分Person& rper = stu;//基類(lèi)對(duì)象不能賦值給派生類(lèi)對(duì)象stu = per;//基類(lèi)的指針可以通過(guò)強(qiáng)制類(lèi)型轉(zhuǎn)換賦值給派生類(lèi)的指針pper = &stu;Student * ps1 = (Student*)pper; // 這種情況轉(zhuǎn)換時(shí)可以的。ps1->_No = 10;pper = &per;Student* ps2 = (Student*)pper; // 這種情況轉(zhuǎn)換時(shí)雖然可以,但是會(huì)存在越界訪(fǎng)問(wèn)的問(wèn)題ps2->_No = 10;
}int main()
{test1();return 0;
}
可以看到,有些地方是報(bào)錯(cuò)了的
3.繼承中的作用域
-
在繼承體系中基類(lèi)和派生類(lèi)都有獨(dú)立的作用域。
-
子類(lèi)和父類(lèi)中有同名成員,子類(lèi)成員將屏蔽父類(lèi)對(duì)同名成員的直接訪(fǎng)問(wèn),這種情況叫隱藏,也叫重定義。(在子類(lèi)成員函數(shù)中,可以使用 基類(lèi)::基類(lèi)成員來(lái)顯示訪(fǎng)問(wèn)父類(lèi)的)
-
需要注意的是如果是成員函數(shù)的隱藏,只需要函數(shù)名相同(返回值和參數(shù)數(shù)量、類(lèi)型可以不同)就構(gòu)成隱藏。
-
注意在實(shí)際中在繼承體系里面最好不要定義同名的成員
class Person
{
protected:string _name = "Neor"; // 姓名int _id = 111;//身份證號(hào)
};class Student : public Person
{
public:void Print(){cout << "學(xué)號(hào):" << _id << endl;cout << "身份證號(hào)" << Person::_id << endl;}
protected:int _id = 888; // 學(xué)號(hào)
};void test1()
{Student s1;s1.Print();
};int main()
{test1();return 0;
}
4.派生類(lèi)的默認(rèn)成員函數(shù)
4.1構(gòu)造函數(shù)
-
派生類(lèi)的構(gòu)造函數(shù)必須調(diào)用基類(lèi)的構(gòu)造函數(shù)初始化基類(lèi)的那一部分成員。如果基類(lèi)沒(méi)有默認(rèn)的構(gòu)造函數(shù),則必須在派生類(lèi)構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用(順序是先父后子)。
-
派生類(lèi)對(duì)象初始化先調(diào)用基類(lèi)構(gòu)造再調(diào)派生類(lèi)構(gòu)造
-
派生類(lèi)的拷貝構(gòu)造函數(shù)必須調(diào)用基類(lèi)的拷貝構(gòu)造完成基類(lèi)的拷貝初始化
4.2重載=和析構(gòu)函數(shù)
-
派生類(lèi)的operator=必須要調(diào)用基類(lèi)的operator=完成基類(lèi)的復(fù)制
-
派生類(lèi)的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類(lèi)的析構(gòu)函數(shù)清理基類(lèi)成員。因?yàn)檫@樣才能保證派生類(lèi)對(duì)象先清理派生類(lèi)成員再清理基類(lèi)成員的順序(先子后父)
- 派生類(lèi)對(duì)象析構(gòu)清理先調(diào)用派生類(lèi)析構(gòu)再調(diào)基類(lèi)的析構(gòu)
- 編譯器會(huì)對(duì)析構(gòu)函數(shù)名進(jìn)行特殊處理,處理成destrutor(),所以父類(lèi)析構(gòu)函數(shù)不加virtual的情況下,子類(lèi)析構(gòu)函數(shù)和父類(lèi)析構(gòu)函數(shù)構(gòu)成隱藏關(guān)系
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "構(gòu)造函數(shù)Person()" << endl;}Person(const Person& p): _name(p._name){cout << "拷貝構(gòu)造Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "析構(gòu)函數(shù)~Person()" << endl;}
protected:string _name; // 姓名
};class Student : public Person
{
public:Student(const char* name, int num): Person(name)//當(dāng)沒(méi)有默認(rèn)構(gòu)造函數(shù)時(shí),就要這樣進(jìn)行顯示調(diào)用, _num(num){cout << "Student()" << endl;}Student(const Student& s): Person(s)//如果想要自己進(jìn)行顯示調(diào)用,怎么得到子類(lèi)當(dāng)中父類(lèi)的部分呢?利用截?cái)? _num(s._num){cout << "Student(const Student& s)" << endl;}Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){Person::operator =(s);//直接operator =(s)會(huì)因?yàn)殡[藏,一直調(diào)用_num = s._num;}return *this;}~Student(){cout << "~Student()" << endl;}
protected:int _num; //學(xué)號(hào)
};void test2()
{Student s1("Nero", 18);
}int main()
{test2();return 0;
}
派生這些默認(rèn)成員函數(shù)規(guī)則,其實(shí)跟以前類(lèi)似,
唯一不同的是,不管是構(gòu)造初始化/拷貝/析構(gòu),多了父類(lèi)那一部分。原則: 父類(lèi)那部分調(diào)用父類(lèi)的對(duì)應(yīng)函數(shù)完成
5.繼承與友元
友元關(guān)系不能繼承,也就是說(shuō)基類(lèi)友元不能訪(fǎng)問(wèn)子類(lèi)私有和保護(hù)成員
class A
{
public:int _pa;friend void print(B& bb);//這個(gè)print是A的友元
protected:int _a;
};class B :public A
{
public:int _pb;
protected:int _b;
};void print(B& bb)
{cout << "公有的_pb" << bb._pa;cout << "私有的_b" << bb._b;//cout << "公有的_pb" << bb._pb;//cout << "私有的_b" << bb._b;
}
class A
{
public:int _pa;friend void print(B& bb);//這個(gè)print是A的友元
protected:int _a;
};class B :public A
{
public:int _pb;friend void print(B& bb);
protected:int _b;
};void print(B& bb)
{cout << "公有的_pb" << bb._pa;cout << "私有的_b" << bb._b;//cout << "公有的_pb" << bb._pb;//cout << "私有的_b" << bb._b;
}
這樣才對(duì)
6.繼承與靜態(tài)成員
基類(lèi)定義了static靜態(tài)成員,則整個(gè)繼承體系里面只有一個(gè)這樣的成員。無(wú)論派生出多少個(gè)子類(lèi),都只有一個(gè)static成員實(shí)例
class Person
{
public:Person() {++_count; }
protected:string _name; // 姓名
public:static int _count; // 統(tǒng)計(jì)人的個(gè)數(shù)。
};int Person::_count = 0;class Student : public Person
{
protected:int _stuNum; // 學(xué)號(hào)
};int main()
{Person p1;Person p2;Student s2;cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}
7.復(fù)雜的菱形繼承及菱形虛擬繼承
7.1單繼承、多繼承和菱形繼承
單繼承:一個(gè)子類(lèi)只有一個(gè)直接父類(lèi)時(shí)稱(chēng)這個(gè)繼承關(guān)系為單繼承
多繼承:一個(gè)子類(lèi)有兩個(gè)或以上直接父類(lèi)時(shí)稱(chēng)這個(gè)繼承關(guān)系為多繼承
就有可能出現(xiàn)一種特殊情況:菱形繼承
菱形繼承的問(wèn)題:從下面的對(duì)象成員模型構(gòu)造,可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問(wèn)題。在Assistant的對(duì)象中Person成員會(huì)有兩份
class Person
{
public:string _name; // 姓名
};class Student : public Person
{
protected:int _num; //學(xué)號(hào)
};class Teacher :public Person
{
protected:int _id; // 老師編號(hào)
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修課程
};void test3()
{Assistant a;a._name = "Nero";
}int main()
{test3();return 0;
}
7.2解決菱形繼承
虛擬繼承可以解決菱形繼承的二義性和數(shù)據(jù)冗余的問(wèn)題。如上面的繼承關(guān)系,在Student和Teacher的繼承Person時(shí)使用虛擬繼承(在:后面加上virtual),即可解決問(wèn)題。需要注意的是,虛擬繼承不要在其他地方去使用。
- 我們要知道是哪幾個(gè)繼承時(shí)導(dǎo)致了二義性,把那些繼承變?yōu)樘摂M繼承就行了,上面這個(gè)例子就是Student和Teacher的繼承
class Person
{
public:string _name; // 姓名
};class Student : virtual public Person
{
protected:int _num; //學(xué)號(hào)
};class Teacher : virtual public Person
{
protected:int _id; // 老師編號(hào)
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修課程
};void test3()
{Assistant a;a._name = "Nero";//此時(shí)就可以了
}int main()
{test3();return 0;
}
7.3解決菱形繼承原理
這里為了探討這個(gè)問(wèn)題,先給出下列代碼:
class A
{
public:int _a;
};class B : virtual public A
{
public:int _b;
};class C : virtual public A
{
public:int _c;
};class D : public B, public C
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
D對(duì)象中將A放到的了對(duì)象組成的最下面,這個(gè)A同時(shí)屬于B和C,那么B和C如何去找到公共的A呢?這里是通過(guò)了B和C的兩個(gè)指針,指向的一張表。這兩個(gè)指針叫虛基表指針,這兩個(gè)表叫虛基表。虛基表中存的偏移量。通過(guò)偏移量可以找到下面的A
8.繼承和組合
public繼承是一種is-a的關(guān)系。也就是說(shuō)每個(gè)派生類(lèi)對(duì)象都是一個(gè)基類(lèi)對(duì)象 ; 組合是一種has-a的關(guān)系。假設(shè)B組合了A,每個(gè)B對(duì)象中都有一個(gè)A對(duì)象。
優(yōu)先使用對(duì)象組合,而不是類(lèi)繼承(繼承的耦合度高) 。
繼承允許你根據(jù)基類(lèi)的實(shí)現(xiàn)來(lái)定義派生類(lèi)的實(shí)現(xiàn)。這種通過(guò)生成派生類(lèi)的復(fù)用通常被稱(chēng)為白箱復(fù)用(white-box reuse)。術(shù)語(yǔ)“白箱”是相對(duì)可視性而言:在繼承方式中,基類(lèi)的內(nèi)部細(xì)節(jié)對(duì)子類(lèi)可見(jiàn) 。繼承一定程度破壞了基類(lèi)的封裝,基類(lèi)的改變,對(duì)派生類(lèi)有很大的影響。派生類(lèi)和基類(lèi)間的依賴(lài)關(guān)系很強(qiáng),耦合度高。
對(duì)象組合是類(lèi)繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過(guò)組裝或組合對(duì)象來(lái)獲得。對(duì)象組合要求被組合的對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱(chēng)為黑箱復(fù)用(black-box reuse),因?yàn)閷?duì)象的內(nèi)部細(xì)節(jié)是不可見(jiàn)的。對(duì)象只以“黑箱”的形式出現(xiàn)。組合類(lèi)之間沒(méi)有很強(qiáng)的依賴(lài)關(guān)系,耦合度低。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類(lèi)被封裝。
實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過(guò)繼承也有用武之地的,有些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類(lèi)之間的關(guān)系可以用繼承,可以用組合,就用組合
繼承講完了,那下次肯定就是多態(tài)啦!!!感謝大家支持!!!