C++:繼承總結
阿新 • • 發佈:2019-01-22
繼承的相關概念
繼承是面向物件複用的重要手段。繼承是型別之間的關係建模,通過繼承類的關係,可以達到複用的目的。比如下面這個例子:
老師,學生,保安都可以由人這個類繼承下來。
實現一個簡單的類
繼承是一種複用手段,在繼承關係裡父類的成員都會變成子類的一部分
三種繼承方式
- public:公有繼承
- private:私有繼承
- protected:保護繼承
三種繼承關係下父類成員在子類的訪問關係變化:
總的來說:當繼承方式與基類的成員限定符不一致時,誰小就取誰。
基類的private成員不可見,這裡的不可見的意思是,該private成員就在那裡,是實際存在的,但是誰都不可以訪問。
總結:
- public繼承是is-a的關係,每個子類物件也是一個父類物件
- private/protected繼承是has-a的關係。
賦值相容規則
切割的行為:
但是注意:在切片的過程中並沒有型別的轉換,是天然的操作,只是把父類需要的東西從子類中取出來;
//父類的指標可以指向子類的物件
Person* p1=&s;
//父類的引用可以指向子類的物件
Person& p2=s;
//子類的指標/引用不能指向父類的物件(可以通過強制型別轉換完成)
Student* s1=(Student*)&p;
Student& s2=(Student&)p;
//但是這種方式可能會造成越界,因為強轉之後編譯器會多向後找四個位元組,這樣就會造成越界的可能性。
繼承中的作用域
- 在繼承體系中基類和派生類都有獨立的作用域
- 子類和父類中有同名成員,子類成員將遮蔽父類對成員的直接訪問。這稱作–隱藏/重定義
- 所以在繼承體系中最好不要定義同名的成員
class Person
{
public:
void Display()
{
cout<<_name<<endl;
}
void f()
{
cout<<"Person()"<<endl;
}
protected:
string _name;
};
class Student: public Person
{
public:
void f(int a)
{
cout<<"Person()"<<endl;
}
public:
int _num;
};
此時,子類的f()同樣會隱藏父類的f(),在不同作用域內,並不構成過載。所以這裡所說的函式名相同,是指不論返回值,引數列表是否相同,函式名相同的就會被隱藏。但是可以通過指定類域顯式地訪問。
繼承的預設成員函式
在繼承關係中,如果沒有顯式地定義這六個預設成員函式,編譯系統會預設合成這六個預設成員函式。
寫一個例子:
class Person//父類的成員函式
{
public:
Person(char* name)
:_name(name)
{
cout<<"Person()"<<endl;
}
Person(const Person& p)
:_name(p._name)
{
cout<<"Person(const Person& p)"<<endl;
}
Person& operator=(const Person& p)
{
cout<<"operator=()"<<endl;
if(this!=&p)
{
_name=p._name;
}
return *this;
}
~Person()
{
cout<<"~Person()"<<endl;
}
protected:
string _name;
};
class Student: public Person
{
public:
Student(char* name,int num)
:Person(name)
,_num(num)
{
cout<<"Student()"<<endl;
}
Student(const Student& s)
:Person(s)
,_num(s._num)
{
cout<<"Student(const Student& s)"<<endl;
}
Student& operator=(const Student& s)
{
if(this!=&s)
{
Person::operator=(s);//必須指定父類的域,否則會無限遞迴
_num=s._num;
}
return *this;
}
~Student()
{
cout<<"~Student()"<<endl;
}
private:
int _num;
};
void test()
{
Student s1("jack",18);
Student s2(s1);
Student s3("rode",12);
s1=s3;
}
結果如下:
子類的建構函式不需要顯式地呼叫父類地構造,子類會自動呼叫父類的構造。顯式地呼叫會隱藏父類的析構,原因是編譯器呼叫解構函式,會將解構函式命名為Destroctor,函式名相同會構成隱藏。如果有需要清理地工作,則就有可能造成記憶體洩漏。
單繼承&多繼承&菱形繼承
畫一張說明這三種繼承:
但是,很容易就會發現,菱形繼承中地Assistant中會有兩份Person的資料。
所以:菱形繼承會有二義性和資料冗餘的問題
虛繼承
虛繼承就是用來解決菱形繼承中二義性與資料冗餘的問題
但是需要注意的一點是,一般不要定義菱形結構的虛繼承體系結構,解決資料冗餘問題的同時也帶來了效能上的消耗。