目錄
一、繼承的概念
二、被繼承成員訪問方式的變化
三、賦值兼容規(guī)則(切片)?
四、繼承中的作用域
五、子類的默認成員函數(shù)
1、父、子類中各自的成員處理方式
2、需要自己寫默認成員函數(shù)的情況
3、子類中默認成員函數(shù)的寫法
3.1父類沒有默認構(gòu)造函數(shù),需要在子類的構(gòu)造函數(shù)里補充
3.2在子類中顯式寫拷貝構(gòu)造
3.3在子類中顯式寫賦值運算符重載
3.4不需要顯式調(diào)用析構(gòu)函數(shù)
六、繼承和友元、靜態(tài)成員的關(guān)系
七、菱形繼承和菱形的虛擬繼承
1、菱形繼承
2、二義性和數(shù)據(jù)冗余
3、虛擬繼承解決二義性和數(shù)據(jù)冗余
4、virtual關(guān)鍵字解決二義性和數(shù)據(jù)冗余的方法
4.1未使用virtual關(guān)鍵字
4.2使用virtual關(guān)鍵字
4.3虛繼承+重寫問題
八、繼承和組合的區(qū)別
1、組合的使用場景
2、繼承和組合的區(qū)別
繼承機制是面向?qū)ο蟪绦蛟O(shè)計使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進行擴展,增加功能,這樣產(chǎn)生新的類,稱子類(派生類)。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類設(shè)計層次的復(fù)用。
現(xiàn)在有學(xué)生類和老師類,類中均有年齡、性別等相同的屬性,這些相同的屬性在每個類中都寫一份就會出現(xiàn)代碼的冗余??梢允褂靡粋€父類包含這些共有的成員變量和成員函數(shù),讓學(xué)生類、老師類作為子類對父類進行繼承即可。
二、被繼承成員訪問方式的變化public繼承 | protected繼承 | private繼承 | |
父類的public成員 | public | protected | private |
父類的protected成員 | protected | protected | private |
父類的private成員 | 子類不可見 | 子類不可見 | 子類不可見 |
1、父類的private成員被子類繼承后是不可見的。不可見指的是父類的private成員會被繼承到子類,但是子類無論是在類里面還是類外面,都無法對這些被繼承的私有成員進行訪問。(但是可以使用繼承的“獲取成員變量的偷家函數(shù)”對這些不可見的成員變量進行修改、訪問)
2、除了父類中的private成員,其他成員被子類繼承后最終的訪問方式取決于該成員在父類中的權(quán)限和繼承權(quán)限兩者權(quán)限較小的那個。
3、可以看出protected限定符是因為繼承才出現(xiàn)的。保護和私有在當(dāng)前類中沒有區(qū)別,但子類繼承時,父類中的私有成員子類是不可見的,而保護成員是可見的。
4、在實際中一般使用都是public繼承,因為protetced/private繼承下來的成員都只能在派生類的類里面使用,實際中擴展維護性不強。父類一般是公有和保護,不使用私有。
5、class默認私有繼承,struct默認公有繼承。但好習(xí)慣是寫明繼承方式。
struct Student : public person
{
};
struct Teacher :person//不提倡,最好寫明繼承方式
{
};
三、賦值兼容規(guī)則(切片)?class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student : public Person
{
public:
int _num;//學(xué)號
};
int main()
{
Person p;
Student s;
//父類=子類 賦值兼容/切割/切片
p = s;//父類對象
Person* ptr = &s;//父類的指針
Person& ref = s;//父類的引用
return 0;
}
1、子類對象可以賦值給父類對象、父類的指針、父類的引用。這種操作叫做賦值兼容/切割/切片,意為將子類對象中繼承于父類的成員切割下來賦值給父類對象。這不是類型轉(zhuǎn)換,是天然的賦值行為。(切片僅限公有繼承。舉例:父類為公有,子類保護或私有繼承后,成員變?yōu)楸Wo和私有,子類再切片給父類,那么被繼承的成員權(quán)限會變,所以切片僅限公有繼承)
2、只能將子類對象賦值給父類,父類對象不能給子類賦值。但是指針和引用卻可以,不過存在越界風(fēng)險。
int main()
{
Person p;
Student s;
//s = (Student)p;//父類對象無法賦值給子類,強轉(zhuǎn)也不行
Student* ptr = (Student*)& p;//類型強轉(zhuǎn),有越界風(fēng)險
Student& ref = (Student&)p;//類型強轉(zhuǎn),有越界風(fēng)險
return 0;
}
3、基類的指針或者引用可以通過強制類型轉(zhuǎn)換賦值給派生類的指針或者引用。但是必須是基類的指針是指向派生類對象時才是安全的。這里基類如果是多態(tài)類型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 來進行識別后進行安全轉(zhuǎn)換。
四、繼承中的作用域1、在繼承中父類和子類都有自己獨立的類域。
2、當(dāng)子類和父類中存在同名成員時,子類將會屏蔽繼承于父類的同名成員,這種情況被稱為隱藏或重定義。(子類內(nèi)部優(yōu)先使用自己類域的同名成員,外部可使用stu.Person::_name進行顯示訪問)
3、子類和父類中的同名成員函數(shù)并不構(gòu)成函數(shù)重載,因為它們所處于不同的類域,子類會隱藏父類同名函數(shù)。
4、父類和子類盡量不要使用同名成員。
五、子類的默認成員函數(shù) 1、父、子類中各自的成員處理方式子類中有兩部分成員,一類是子類原生的成員,另一類是繼承于父類的成員。
對于原生成員,按照普通類調(diào)用默認成員函數(shù)的規(guī)則進行處理;對于繼承于父類的成員,將會調(diào)用父類中的默認成員函數(shù)進行處理。(各管各的)
2、需要自己寫默認成員函數(shù)的情況1、父類沒有默認構(gòu)造函數(shù),需要自己顯式寫構(gòu)造。
2、子類存在淺拷貝問題,需要自己顯式寫拷貝構(gòu)造和賦值。
3、子類有資源需要釋放,需要自己寫顯式析構(gòu)。
3、子類中默認成員函數(shù)的寫法 3.1父類沒有默認構(gòu)造函數(shù),需要在子類的構(gòu)造函數(shù)里補充class Person
{
public:
Person(const char* name)
: _name(name)
{}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name = "張三", int num = 10)
: Person(name)//必須調(diào)用父類的構(gòu)造函數(shù)進行初始化
, _num(num)
{}
protected:
int _num; //學(xué)號
};
父類有提供默認構(gòu)造函數(shù)就可以不用在子類寫了。
3.2在子類中顯式寫拷貝構(gòu)造class Person
{
public:
Person(const Person& p)//形參:引用切片對象
: _name(p._name)
{}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const Student& s)
:Person(s)//切片
,_num(s._num)
{}
protected:
int _num; //學(xué)號
};
利用切片傳入父類對象構(gòu)造父類。Student s1(s2),在初始化列表中,利用s2中的父類成員去拷貝構(gòu)造s1中的父類成員。
3.3在子類中顯式寫賦值運算符重載class Person
{
public:
Person& operator=(const Person& p)
{
if (this != &p)
_name = p._name;
return *this;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student& operator=(const Student& s)
{
if (this != &s)//防止自己給自己賦值
{
Person::operator=(s);//切片傳入父類賦值運算符重載中
//根據(jù)子類成員進行深淺拷貝
_num = s._num;
}
return *this;
}
protected:
int _num; //學(xué)號
};
在子類賦值運算符重載中調(diào)用父類賦值運算符重載,通過切片,完成父類成員的賦值。
3.4不需要顯式調(diào)用析構(gòu)函數(shù)錯誤代碼:
~Student()
{
Person::~Person();
}
//子類析構(gòu)函數(shù)結(jié)束后會調(diào)用一次父類的析構(gòu)函數(shù)
~Person前必須加類域Person。因為析構(gòu)函數(shù)的名字會被編譯器統(tǒng)一處理為destructor(),子類的析構(gòu)函數(shù)和父類的析構(gòu)函數(shù)之間構(gòu)成隱藏,所以這里需要寫明類域。
但是,我們并不需要顯式調(diào)用父類的析構(gòu)函數(shù),因為出了子類析構(gòu)函數(shù)的作用域,編譯器會自動調(diào)用父類的析構(gòu)。手動調(diào)用父類析構(gòu)將會造成重復(fù)析構(gòu)。
六、繼承和友元、靜態(tài)成員的關(guān)系1、友元關(guān)系不能被繼承
2、父類中的靜態(tài)成員也會被繼承,但是整個繼承關(guān)系中共用這個靜態(tài)成員
七、菱形繼承和菱形的虛擬繼承 1、菱形繼承從上圖可以看出,Assistant中會有兩份Person成員,調(diào)用時存在二義性和數(shù)據(jù)冗余。
2、二義性和數(shù)據(jù)冗余int main()
{
// 這樣會有二義性無法明確知道訪問的是哪一個
Assistant a;
//a._name = "peter";//不能這么寫,因為a中有兩個_name成員,需要指定類域
// 需要顯示指定訪問哪個父類的成員可以解決二義性問題,但是數(shù)據(jù)冗余問題無法解決
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}
由于Assistant中有兩個_name成員,直接調(diào)用存在二義性,需要在成員之前指定類域。
_name這個成員變量有兩個問題不大,畢竟一個人可以叫蔣靈瑜,在其他場合也可以叫小蔣。但如果這個成員變量是一個int _arr[50000]的數(shù)組呢,一個類中同時有兩份這么大的數(shù)組,將會導(dǎo)致數(shù)據(jù)冗余。
3、虛擬繼承解決二義性和數(shù)據(jù)冗余產(chǎn)生二義性和數(shù)據(jù)冗余的本質(zhì)就是子類繼承了多份相同成員。
解決方法是在“腰部”類增加virtual關(guān)鍵字。
class Person
{
public:
string _name; // 姓名
};
class Student :virtual public Person
{
protected:
int _num; //學(xué)號
};
class Teacher : virtual public Person
{
protected:
int _id; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修課程
};
4、virtual關(guān)鍵字解決二義性和數(shù)據(jù)冗余的方法先來一段菱形繼承的代碼,_a、_b、_c、_d分別是類A、類B、類C、類D中的原生成員。
class A
{
public:
int _a;
};
class B : public A
//class B : virtual public A
{
public:
int _b;
};
class C : public A
//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._b = 2;
d.C::_a = 3;
d._c = 4;
d._d = 5;
return 0;
}
4.1未使用virtual關(guān)鍵字通過調(diào)用內(nèi)存,會發(fā)現(xiàn)對象d中存在兩份的_a,存在二義性和數(shù)據(jù)冗余。
4.2使用virtual關(guān)鍵字當(dāng)使用了虛擬繼承,通過調(diào)用內(nèi)存,發(fā)現(xiàn)對象d中僅有一份_a,但是繼承于B類和C類的_b和_c上方多了一串地址,再次要通過內(nèi)存查找這串地址,發(fā)現(xiàn)這串地址之后的位置存放一個數(shù)字0x14,這個數(shù)字就是繼承于B的成員到_a的偏移量,通過這個偏移量,對象d便能到d.B::_a。這樣就解決了菱形繼承成員冗余的問題。
這里的A叫做虛基類,在對象d中,將虛基類的成員放到一個公共的位置,繼承的B、C類需要找到A的成員,通過虛基表中的偏移量進行計算。我們看到虛基表中第一行還空置了4個字節(jié),這塊空間存放的也是一個偏移量,它用于尋找d對象中的虛函數(shù)指針表。
實際使用時,盡量不要使用用菱形繼承,因為它本質(zhì)就是C++設(shè)計的一個坑!
4.3虛繼承+重寫問題1、如果A類中還存在一個虛函數(shù),那么對象d會在_a后面存放虛函數(shù)指針表;
2、如果A類中存在一個虛函數(shù),并且B、C類均對這個虛函數(shù)進行了重寫,那么D類中必須對這個函數(shù)進行重寫,否則將會發(fā)生虛函數(shù)表重命名的問題。
八、繼承和組合的區(qū)別組合也是一種類復(fù)用的手段。
1、組合的使用場景適用組合的代碼:輪胎和車的關(guān)系
class Tire
{
protected:
string _brand = "Michelin"; ?// 品牌
size_t _size = 18; ? ? ? ? // 尺寸
};
?
class Car{
protected:
string _colour = "白色"; // 顏色
string _num = "xxxxx"; // 車牌號
Tire _t; // 輪胎
}; ?
2、繼承和組合的區(qū)別public繼承是一種is-a的關(guān)系,每個子類對象都是一個父類對象,例如“學(xué)生”是“人”(子類學(xué)生,父類人)
組合是一種has-a的關(guān)系,B組合了A,每個B對象中都有一個A,例如“車”包含“輪胎”
如果兩個類既可以是is-a,又可以是has-a的關(guān)系,那么優(yōu)先使用組合。
繼承是一種白盒復(fù)用,父類內(nèi)部的細節(jié)對子類可見,破壞了封裝。子類將會繼承父類的公有和保護成員,一旦父類修改了這些成員的實現(xiàn),將會影響子類的功能。子類和父類之間的依賴關(guān)系強,耦合度高。
組合是一種黑盒復(fù)用,父類內(nèi)部的細節(jié)對子類不可見,子類僅可使用父類的公有成員,只要父類的公有成員的實現(xiàn)細節(jié)不變,子類影響較小。父子之間沒有很強的依賴關(guān)系,耦合度較低。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
本文標(biāo)題:【C++】繼承-創(chuàng)新互聯(lián)
網(wǎng)頁地址:http://www.rwnh.cn/article8/jdgop.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號、動態(tài)網(wǎng)站、軟件開發(fā)、網(wǎng)站收錄、用戶體驗、外貿(mào)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容