摘要:例如,在關(guān)鍵字為的派生類當(dāng)中,所繼承的基類成員的訪問方式變?yōu)?。繼承中的作用域在繼承體系中的基類和派生類都有獨(dú)立的作用域。為了避免類似問題,實(shí)際在繼承體系當(dāng)中最好不要定義同名的成員。
繼承(inheritance)機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱為派生類。
繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,而繼承便是類設(shè)計(jì)層次的復(fù)用。
例如,以下代碼中Student類和Teacher類就繼承了Person類。
//父類class Person{public: void Print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; }protected: string _name = "張三"; //姓名 int _age = 18; //年齡};//子類class Student : public Person{protected: int _stuid; //學(xué)號(hào)};//子類class Teacher : public Person{protected: int _jobid; //工號(hào)};
繼承后,父類Person的成員,包括成員函數(shù)和成員變量,都會(huì)變成子類的一部分,也就是說,子類Student和Teacher復(fù)用了父類Person的成員。
繼承的定義格式如下:
說明: 在繼承當(dāng)中,父類也稱為基類,子類是由基類派生而來的,所以子類又稱為派生類。
我們知道,訪問限定符有以下三種:
而繼承的方式也有類似的三種:
基類當(dāng)中被不同訪問限定符修飾的成員,以不同的繼承方式繼承到派生類當(dāng)中后,該成員最終在派生類當(dāng)中的訪問方式將會(huì)發(fā)生變化。
類成員/繼承方式 | public繼承 | protected繼承 | private繼承 |
---|---|---|---|
基類的public成員 | 派生類的public成員 | 派生類的protected成員 | 派生類的private成員 |
基類的protected成員 | 派生類的protected成員 | 派生類的protected成員 | 派生類的private成員 |
基類的private成員 | 在派生類中不可見 | 在派生類中不可見 | 在派生類中不可見 |
稍作觀察,實(shí)際上基類成員訪問方式的變化規(guī)則也不是無跡可尋的,我們可以認(rèn)為三種訪問限定符的權(quán)限大小為:public > protected > private,基類成員訪問方式的變化規(guī)則如下:
基類的private成員在派生類當(dāng)中不可見是什么意思?
這句話的意思是,我們無法在派生類當(dāng)中訪問基類的private成員。例如,雖然Student類繼承了Person類,但是我們無法在Student類當(dāng)中訪問Person類當(dāng)中的private成員_name。
//基類class Person{private: string _name = "張三"; //姓名};//派生類class Student : public Person{public: void Print() { //在派生類當(dāng)中訪問基類的private成員,error! cout << _name << endl; }protected: int _stuid; //學(xué)號(hào)};
也就是說,基類的private成員無論以什么方式繼承,在派生類中都是不可見的,這里的不可見是指基類的私有成員雖然被繼承到了派生類對(duì)象中,但是語法上限制派生類對(duì)象不管在類里面還是類外面都不能去訪問它。
因此,基類的private成員在派生類中是不能被訪問的,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就需要定義為protected,由此可以看出,protected限定符是因繼承才出現(xiàn)的。
注意: 在實(shí)際運(yùn)用中一般使用的都是public繼承,幾乎很少使用protected和private繼承,也不提倡使用protected和private繼承,因?yàn)槭褂胮rotected和private繼承下來的成員都只能在派生類的類里面使用,實(shí)際中擴(kuò)展維護(hù)性不強(qiáng)。
在使用繼承的時(shí)候也可以不指定繼承方式,使用關(guān)鍵字class時(shí)默認(rèn)的繼承方式是private,使用struct時(shí)默認(rèn)的繼承方式是public。
例如,在關(guān)鍵字為class的派生類當(dāng)中,所繼承的基類成員_name的訪問方式變?yōu)閜rivate。
//基類class Person{public: string _name = "張三"; //姓名};//派生類class Student : Person //默認(rèn)為private繼承{protected: int _stuid; //學(xué)號(hào)};
而在關(guān)鍵字為struct的派生類當(dāng)中,所繼承的基類成員_name的訪問方式仍為public。
//基類class Person{public: string _name = "張三"; //姓名};//派生類struct Student : Person //默認(rèn)為public繼承{protected: int _stuid; //學(xué)號(hào)};
注意: 雖然繼承時(shí)可以不指定繼承方式而采用默認(rèn)的繼承方式,但還是最好顯示的寫出繼承方式。
派生類對(duì)象可以賦值給基類的對(duì)象、基類的指針以及基類的引用,因?yàn)樵谶@個(gè)過程中,會(huì)發(fā)生基類和派生類對(duì)象之間的賦值轉(zhuǎn)換。
例如,對(duì)于以下基類及其派生類。
//基類class Person{protected: string _name; //姓名 string _sex; //性別 int _age; //年齡};//派生類class Student : public Person{protected: int _stuid; //學(xué)號(hào)};
代碼當(dāng)中可以出現(xiàn)以下邏輯:
Student s;Person p = s; //派生類對(duì)象賦值給基類對(duì)象Person* ptr = &s; //派生類對(duì)象賦值給基類指針Person& ref = s; //派生類對(duì)象賦值給基類引用
對(duì)于這種做法,有個(gè)形象的說法叫做切片/切割,寓意把派生類中基類那部分切來賦值過去。
派生類對(duì)象賦值給基類對(duì)象圖示:
派生類對(duì)象賦值給基類指針圖示:
派生類對(duì)象賦值給基類引用圖示:
注意: 基類對(duì)象不能賦值給派生類對(duì)象,基類的指針可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針,但是此時(shí)基類的指針必須是指向派生類的對(duì)象才是安全的。
在繼承體系中的基類和派生類都有獨(dú)立的作用域。若子類和父類中有同名成員,子類成員將屏蔽父類對(duì)同名成員的直接訪問,這種情況叫隱藏,也叫重定義。
例如,對(duì)于以下代碼,訪問成員_num時(shí)將訪問到子類當(dāng)中的_num。
#include #include using namespace std;//父類class Person{protected: int _num = 111;};//子類class Student : public Person{public: void fun() { cout << _num << endl; }protected: int _num = 999;};int main(){ Student s; s.fun(); //999 return 0;}
若此時(shí)我們就是要訪問父類當(dāng)中的_num成員,我們可以使用作用域限定符進(jìn)行指定訪問。
void fun(){ cout << Person::_num << endl; //指定訪問父類當(dāng)中的_num成員}
需要注意的是,如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。
例如,對(duì)于以下代碼,調(diào)用成員函數(shù)fun時(shí)將直接調(diào)用子類當(dāng)中的fun,若想調(diào)用父類當(dāng)中的fun,則需使用作用域限定符指定類域。
#include #include using namespace std;//父類class Person{public: void fun(int x) { cout << x << endl; }};//子類class Student : public Person{public: void fun(double x) { cout << x << endl; }};int main(){ Student s; s.fun(3.14); //直接調(diào)用子類當(dāng)中的成員函數(shù)fun s.Person::fun(20); //指定調(diào)用父類當(dāng)中的成員函數(shù)fun return 0;}
特別注意: 代碼當(dāng)中,父類中的fun和子類中的fun不是構(gòu)成函數(shù)重載,因?yàn)楹瘮?shù)重載要求兩個(gè)函數(shù)在同一作用域,而此時(shí)這兩個(gè)fun函數(shù)并不在同一作用域。為了避免類似問題,實(shí)際在繼承體系當(dāng)中最好不要定義同名的成員。
默認(rèn)成員函數(shù),即我們不寫編譯器會(huì)自動(dòng)生成的函數(shù),類當(dāng)中的默認(rèn)成員函數(shù)有以下六個(gè):
下面我們看看派生類當(dāng)中的默認(rèn)成員函數(shù),與普通類的默認(rèn)成員函數(shù)的不同之處。
例如,我們以下面這個(gè)Person類為基類。
//基類class Person{public: //構(gòu)造函數(shù) Person(const string& name = "peter") :_name(name) { cout << "Person()" << endl; } //拷貝構(gòu)造函數(shù) Person(const Person& p) :_name(p._name) { cout << "Person(const Person& p)" << endl; } //賦值運(yùn)算符重載函數(shù) Person& operator=(const Person& p) { cout << "Person& operator=(const Person& p)" << endl; if (this != &p) { _name = p._name; } return *this; } //析構(gòu)函數(shù) ~Person() { cout << "~Person()" << endl; }private: string _name; //姓名};
我們用該基類派生出Student類,Student類當(dāng)中的默認(rèn)成員函數(shù)的基本邏輯如下:
//派生類class Student : public Person{public: //構(gòu)造函數(shù) Student(const string& name, int id) :Person(name) //調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員 , _id(id) //初始化派生類的成員 { cout << "Student()" << endl; } //拷貝構(gòu)造函數(shù) Student(const Student& s) :Person(s) //調(diào)用基類的拷貝構(gòu)造函數(shù)完成基類成員的拷貝構(gòu)造 , _id(s._id) //拷貝構(gòu)造派生類的成員 { cout << "Student(const Student& s)" << endl; } //賦值運(yùn)算符重載函數(shù) Student& operator=(const Student& s) { cout << "Student& operator=(const Student& s)" << endl; if (this != &s) { Person::operator=(s); //調(diào)用基類的operator=完成基類成員的賦值 _id = s._id; //完成派生類成員的賦值 } return *this; } //析構(gòu)函數(shù) ~Student() { cout << "~Student()" << endl; //派生類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù) }private: int _id; //學(xué)號(hào)};
派生類與普通類的默認(rèn)成員函數(shù)的不同之處概括為以下幾點(diǎn):
在編寫派生類的默認(rèn)成員函數(shù)時(shí),需要注意以下幾點(diǎn):
destructor();
。因此,派生類和基類的析構(gòu)函數(shù)也會(huì)因?yàn)楹瘮?shù)名相同構(gòu)成隱藏,若是我們需要在某處調(diào)用基類的析構(gòu)函數(shù),那么就要使用作用域限定符進(jìn)行指定調(diào)用。operator=
當(dāng)中調(diào)用基類的拷貝構(gòu)造函數(shù)和operator=
的傳參方式是一個(gè)切片行為,都是將派生類對(duì)象直接賦值給基類的引用。說明一下:
基類的構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、賦值運(yùn)算符重載函數(shù)我們都可以在派生類當(dāng)中自行進(jìn)行調(diào)用,而基類的析構(gòu)函數(shù)是當(dāng)派生類的析構(gòu)函數(shù)被調(diào)用后由編譯器自動(dòng)調(diào)用的,我們?nèi)羰亲孕姓{(diào)用基類的構(gòu)造函數(shù)就會(huì)導(dǎo)致基類被析構(gòu)多次的問題。
我們知道,創(chuàng)建派生類對(duì)象時(shí)是先創(chuàng)建的基類成員再創(chuàng)建的派生類成員,編譯器為了保證析構(gòu)時(shí)先析構(gòu)派生類成員再析構(gòu)基類成員的順序析構(gòu),所以編譯器會(huì)在派生類的析構(gòu)函數(shù)被調(diào)用后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)。
友元關(guān)系不能繼承,也就是說基類的友元可以訪問基類的私有和保護(hù)成員,但是不能訪問派生類的私有和保護(hù)成員。
例如,以下代碼中Display函數(shù)是基類Person的友元,當(dāng)時(shí)Display函數(shù)不是派生類Student的友元,即Display函數(shù)無法訪問派生類Student當(dāng)中的私有和保護(hù)成員。
#include #include using namespace std;class Student;class Person{public: //聲明Display是Person的友元 friend void Display(const Person& p, const Student& s);protected: string _name; //姓名};class Student : public Person{protected: int _id; //學(xué)號(hào)};void Display(const Person& p, const Student& s){ cout << p._name << endl; //可以訪問 cout << s._id << endl; //無法訪問}int main(){ Person p; Student s; Display(p, s); return 0;}
若想讓Display函數(shù)也能夠訪問派生類Student的私有和保護(hù)成員,只能在派生類Student當(dāng)中進(jìn)行友元聲明。
class Student : public Person{public: //聲明Display是Student的友元 friend void Display(const Person& p, const Student& s);protected: int _id; //學(xué)號(hào)};
若基類當(dāng)中定義了一個(gè)static靜態(tài)成員變量,則在整個(gè)繼承體系里面只有一個(gè)該靜態(tài)成員。無論派生出多少個(gè)子類,都只有一個(gè)static成員實(shí)例。
例如,在基類Person當(dāng)中定義了靜態(tài)成員變量_count,盡管Person又繼承了派生類Student和Graduate,但在整個(gè)繼承體系里面只有一個(gè)該靜態(tài)成員。
我們?nèi)羰窃诨怭erson的構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)當(dāng)中設(shè)置_count進(jìn)行自增,那么我們就可以隨時(shí)通過_count來獲取該時(shí)刻已經(jīng)實(shí)例化的Person、Student以及Graduate對(duì)象的總個(gè)數(shù)。
#include #include using namespace std;//基類class Person{public: Person() { _count++; } Person(const Person& p) { _count++; }protected: string _name; //姓名public: static int _count; //統(tǒng)計(jì)人的個(gè)數(shù)。};int Person::_count = 0; //靜態(tài)成員變量在類外進(jìn)行初始化//派生類class Student : public Person{protected: int _stuNum; //學(xué)號(hào)};//派生類class Graduate : public Person{protected: string _seminarCourse; //研究科目};int main(){ Student s1; Student s2(s1); Student s3; Graduate s4; cout << Person::_count << endl; //4 cout << Student::_count << endl; //4 return 0;}
此時(shí)我們也可以通過打印Person類和Student類當(dāng)中靜態(tài)成員_count的地址來證明它們就是同一個(gè)變量。
cout << &Person::_count << endl; //00F1F320cout << &Student::_count << endl; //00F1F320
單繼承:一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承。
多繼承:一個(gè)子類有兩個(gè)或兩個(gè)以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承。
菱形繼承:菱形繼承是多繼承的一種特殊情況。
從菱形繼承的模型構(gòu)造就可以看出,菱形繼承的繼承方式存在數(shù)據(jù)冗余和二義性的問題。
例如,對(duì)于以上菱形繼承的模型,當(dāng)我們實(shí)例化出一個(gè)Assistant對(duì)象后,訪問成員時(shí)就會(huì)出現(xiàn)二義性問題。
#include #include using namespace std;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; //主修課程};int main(){ Assistant a; a._name = "peter"; //二義性:無法明確知道要訪問哪一個(gè)_name return 0;}
Assistant對(duì)象是多繼承的Student和Teacher,而Student和Teacher當(dāng)中都繼承了Person,因此Student和Teacher當(dāng)中都有_name成員,若是直接訪問Assistant對(duì)象的_name成員會(huì)出現(xiàn)訪問不明確的報(bào)錯(cuò)。
對(duì)于此,我們可以顯示指定訪問Assistant哪個(gè)父類的_name成員。
//顯示指定訪問哪個(gè)父類的成員a.Student::_name = "張同學(xué)";a.Teacher::_name = "張老師";
雖然該方法可以解決二義性的問題,但仍然不能解決數(shù)據(jù)冗余的問題。因?yàn)樵贏ssistant的對(duì)象在Person成員始終會(huì)存在兩份。
為了解決菱形繼承的二義性和數(shù)據(jù)冗余問題,出現(xiàn)了虛擬繼承。如前面說到的菱形繼承關(guān)系,在Student和Teacher繼承Person是使用虛擬繼承,即可解決問題。
虛擬繼承代碼如下:
#include #include using namespace std;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; //主修課程};int main(){ Assistant a; a._name = "peter"; //無二義性 return 0;}
此時(shí)就可以直接訪問Assistant對(duì)象的_name成員了,并且之后就算我們指定訪問Assistant的Student父類和Teacher父類的_name成員,訪問到的都是同一個(gè)結(jié)果,解決了二義性的問題。
cout << a.Student::_name << endl; //petercout << a.Teacher::_name << endl; //peter
而我們打印Assistant的Student父類和Teacher父類的_name成員的地址時(shí),顯示的也是同一個(gè)地址,解決了數(shù)據(jù)冗余的問題。
cout << &a.Student::_name << endl; //0136F74Ccout << &a.Teacher::_name << endl; //0136F74C
在此之前,我們先看看不使用菱形虛擬繼承時(shí),以下菱形繼承當(dāng)中D類對(duì)象的各個(gè)成員在內(nèi)存當(dāng)中的分布情況。
測(cè)試代碼如下:
#include using namespace std;class A{public: int _a;};class B : public A{public: int _b;};class C : public A{public: int _c;};class D : public B, public C{public: int _d;
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/122420.html
摘要:繼承繼承,就是子類繼承父親的特征和行為,使得子類具有父類的成員變量和方法。此時(shí),被繼承的類稱為父類或基類,而繼承的類稱為子類或派生類。,如果存在繼承關(guān)系的時(shí)候,和就不一樣了基類中的成員可以在派生類中使用,但是基類中的成員不能再派生類中使用。 ...
摘要:博主在公眾號(hào)后臺(tái)設(shè)置了關(guān)鍵字回復(fù),回復(fù)下面的里面的內(nèi)容,可免費(fèi)獲得學(xué)習(xí)視頻和資料。 博主在公眾號(hào)后臺(tái)設(shè)置了關(guān)鍵字回復(fù), 回復(fù)下面的【】里面的內(nèi)容, 可免費(fèi)獲得C++學(xué)習(xí)視頻和資料。 如回復(fù):C++基礎(chǔ) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 【C++】 【1】...
摘要:基類中的構(gòu)造函數(shù)和析構(gòu)函數(shù)不能被繼承,在派生類中需要定義新的構(gòu)造函數(shù)和析構(gòu)函數(shù),私有成員不能被繼承。對(duì)象訪問在派生類外部,通過派生類的對(duì)象對(duì)從基類繼承來的成員的訪問。 ...
摘要:類的定義假如要定義一個(gè)類,表示二維的坐標(biāo)點(diǎn)最最基本的就是方法,相當(dāng)于的構(gòu)造函數(shù)。嚴(yán)格來講,并不支持多態(tài)。靜態(tài)類型的缺失,讓很難實(shí)現(xiàn)那樣嚴(yán)格的多態(tài)檢查機(jī)制。有時(shí)候,需要在子類中調(diào)用父類的方法。 類的定義 假如要定義一個(gè)類 Point,表示二維的坐標(biāo)點(diǎn): # point.py class Point: def __init__(self, x=0, y=0): se...
閱讀 1278·2021-11-08 13:25
閱讀 1466·2021-10-13 09:40
閱讀 2793·2021-09-28 09:35
閱讀 760·2021-09-23 11:54
閱讀 1162·2021-09-02 15:11
閱讀 2457·2019-08-30 13:18
閱讀 1693·2019-08-30 12:51
閱讀 2714·2019-08-29 18:39