1. 程式人生 > >C++:繼承總結

C++:繼承總結

繼承的相關概念

繼承是面向物件複用的重要手段。繼承是型別之間的關係建模,通過繼承類的關係,可以達到複用的目的。比如下面這個例子:

老師,學生,保安都可以由人這個類繼承下來。

這裡寫圖片描述

實現一個簡單的類

這裡寫圖片描述

繼承是一種複用手段,在繼承關係裡父類的成員都會變成子類的一部分

三種繼承方式

  • 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的資料。

所以:菱形繼承會有二義性和資料冗餘的問題

虛繼承

虛繼承就是用來解決菱形繼承中二義性與資料冗餘的問題

但是需要注意的一點是,一般不要定義菱形結構的虛繼承體系結構,解決資料冗餘問題的同時也帶來了效能上的消耗。