首頁 > 軟體

一文搞懂C++中繼承的概念與使用

2022-07-21 18:03:45

前言

我們都知道物件導向語言的三大特點是:**封裝,繼承,多型。**之前在類和物件部分,我們提到了C++中的封裝,那麼今天呢,我們來學習一下C++中的繼承。

繼承概念及定義

繼承概念

繼承(inheritance)機制是物件導向程式設計使程式碼可以複用的最重要的手段,它允許程式設計師在保持原有類特性的基礎上進行擴充套件,增加功能,這樣產生新的類,稱派生類。繼承呈現了物件導向程式設計的層次結構,體現了由簡單到複雜的認知過程。以前我們接觸的複用都是函數複用,繼承是類設計層次的複用

看概念是一件很讓人疑惑的東西,接下來我就來舉個例子來看看繼承具體是什麼東西

首先我們定義兩個類,一個Student類,一個Teacher類,二者都有年齡和姓名,學生有學號,老師有工號。

class Student
{
private:
    int _age;     //年齡
    string _name; //姓名
    int _stuid;   //學號
};

class Teacher
{
private:
    int _age;     //年齡
    string _name; //姓名
    int _jobid;   //工號
};

我們發現這兩個類有一些重複的地方,比如年齡和姓名,這二者是他們的成員,此時程式碼就產生了冗餘。那麼我們可不可以像個方法去複用這兩個成員呢?繼承此時就可以發揮它的重大作用。

我們將他們重複的地方提取出來,重新定義一個Person類。而Student類和Teacher類將Person類繼承下來,此時我們就實現了程式碼的複用。

class Person
{
protected:
	int _age;     //年齡
	string _name; //姓名
};

class Student : public Person
{
private:
	int _stuid; //學號
};

class Teacher : public Person
{
private:
	int _jobid; //工號
};

我們首先分別用Student和Teacher類來建立兩個物件,來看看物件裡面有什麼。

int main()
{
    Teacher t;
    Student s;
    return 0;
}

此時我們可以看到我們建立的兩個物件裡面都含有從Person類中繼承過來的 age 和 name 兩個成員。

所以繼承實際上是一個程式碼的複用,我們可以借用已完成的類的程式碼來完善我們需要創造的新類。

繼承定義

以我們剛剛建立的Student類來舉例:我們看到Person是父類別,也稱作基礎類別。Student是子類,也稱作派生類。

繼承方式

我們在類和物件的時候介紹了三種存取限定符:public(公有),protected(保護)和private(私有)。存取限定符限定了我們在類外如何去存取類中的成員。

在繼承中我們一樣使用這三種限定符來限定子類該如何去存取父類別的的成員,下面有一張表來表示他們的關係。

類成員繼承方式public繼承protected繼承private繼承
父類別的public成員派生類的public成員派生類的protected成員派生類的private成員
父類別的protected成員派生類的protected成員派生類的protected成員派生類的private成員
父類別的private成員在派生類中不可見在派生類中不可見在派生類中不可見

首先解釋一下在派生類中不可見是什麼意思,就如同我們在類外無法直接去修改類中的private成員一樣,我們在子類中也無法直接修改父類別的private成員。

如何簡潔的去記這個表呢?在C++中許可權的關係:public > protected > private。在繼承的時候呢,父類別成員的許可權取的是:父類別成員原本許可權和繼承方式中較小的那個。

比如父類別的A成員原本許可權為public,而子類的繼承方式為private。此時A成員相對子類來說就為private成員

父類別和子類物件賦值轉換

子類的物件可以賦值給 父類別的物件/父類別的指標/父類別的應用,那麼是如何進行賦值的呢?形象一點來說就是切片,將子類中父類別的部分切割父類別。

但我們無法反過來,將父類別物件賦值給子類物件。

繼承中的作用域

在繼承體系中父類別和子類都有獨立的作用域,如果子類和父類別中有同名的成員,子類成員將遮蔽對父類別成員的直接存取,這種情況叫隱藏,也叫重定義。

下面還是用我們的Person類和Student類來舉個栗子,我們分別在Person類和Student類中加入一個print函數,通過列印內容來區分呼叫的為哪一print函數。

class Person
{
protected:
    int _age;
    string _name;

public:
    void print()
    {
        cout << "Person"<< endl;
    }
};

class Student : public Person
{
private:
    int _stuid; //學號
    public:
    void print()
    {
        cout << "Student" << endl;
    }
};

接下來我們建立一個物件然後來試一下結果。

int main()
{
    Student s;
    s.print();
    return 0;
}

我們可以看到我們呼叫的為Student中的print函數。此時子類的print函數已經對父類別的print函數進行了重定義。重定義不代表子類無法去呼叫父類別的同名函數,只是不那麼直接而已。使用下面這種方法我們就可以呼叫父類別中的同名函數。

int main()
{
    Student s;
    s.Person::print();
    return 0;
}

通過指定類域,我們就可以去呼叫父類別的print函數。但在實際中最好不要去定義同名函數以免帶來問題。

派生類的預設成員函數

首先我們來回顧一下有哪幾個預設成員函數。

那麼在子類中,這些預設成員函數是怎麼生成的呢?

1.子類別建構函式必須呼叫父類別的建構函式初始化父類別的那一部分成員。如果父類別沒有預設的建構函式,則必須在派生類建構函式的初始化列表中顯式呼叫。還是用我們的Person類和Student類舉例。

情況一:有預設建構函式

class Person
{
protected:
    int _age;
    string _name;

public:
    Person()
    {
        cout << "Person" << endl; //呼叫就列印
    }
};

class Student : public Person
{
private:
    int _stuid; //學號
public:
    Student()
    {
        cout << "Student" << endl; //呼叫就列印
    }
};

int main()
{
    Student s;
    return 0;
}

情況二:無預設建構函式

class Person
{
protected:
    int _age;
    string _name;

public:
    Person(int age, string name)
    {
        cout << "Person" << endl;
    }
};

class Student : public Person
{
private:
    int _stuid; //學號
public:
    Student()
        : Person(19, "wanku") //無預設構造,此時我們需要在初始化列表中初始化
    {
        cout << "Student" << endl;
    }
};

int main()
{
    Student s;
    return 0;
}

int main()
{
    Student s;
    return 0;
}

2.子類的拷貝建構函式必須呼叫父類別的拷貝構造完成父類別的拷貝初始化化。

class Person
{
protected:
    int _age;
    string _name;

public:
    Person(int age = 10, string name = "wanku")
    {
        cout << "Person" << endl;
    }

    Person(const Person &p)
        : _age(p._age), _name(p._name)
    {}
};

class Student : public Person
{
private:
    int _stuid; //學號
public:
    Student()
    {
        cout << "Student" << endl;
    }

    Student(const Student &s)
        : Person(s) /*顯示呼叫父類別的拷貝構造*/, _stuid(s._stuid)
    {}
};

有些朋友可能會疑惑,在Person類中的拷貝建構函式引數明明是Person類,為什麼我們的Student類可以傳過去呢?那是因為我們剛剛講的切片原理,當我們把子類物件傳過去時,編譯器會進行切分,然後再傳給父類別。

3.派生類的operator=必須要呼叫基礎類別的operator=完成基礎類別的複製。(原理和拷貝構造大體相似,值得注意的是:當我們在子類直接想去呼叫父類別的operator= 時,會發生重定義,使用時記得加上父類別的作用域)

4.在繼承中一個物件的歷程如下:父類別的建構函式 –> 子類別建構函式 –> 子類的解構函式 –> 父類別的解構函式。這個過程相當於把這些行為存在一個棧中,然後再把行為從棧中拿出來一般

派生類的友元與靜態成員

父類別的友元不是子類的友元。(你爸爸的朋友不一定是你的朋友)

父類別中有一個靜態成員,那麼子類和父類別共用一個靜態成員。(靜態成員並不存在物件中,只開闢一個空間,所以只能共用一個)

繼承關係

單繼承

一個子類只有一個直接父類別。

多繼承

一個子類有兩個及以上的父類別

菱形繼承

多繼承的一種特殊情況

以上就是一文搞懂C++中繼承的概念與使用的詳細內容,更多關於C++繼承的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com