NDK開發(fā)---C++學(xué)習(xí)(六):繼承、多態(tài)

前言

前面我們已經(jīng)介紹過了C++中的類與函數(shù),不熟悉的,可以去看看
NDK開發(fā)---C++學(xué)習(xí)(三):類與函數(shù)(上)
NDK開發(fā)---C++學(xué)習(xí)(四):類與函數(shù)(中)
NDK開發(fā)---C++學(xué)習(xí)(五):類與函數(shù)(下)
本篇將介紹C++中的繼承(多繼承、虛擬繼承)、多態(tài)、虛函數(shù)等。

繼承

繼承代碼重用的基本工具,是類的一個重要特征。它克服了面向過程程序設(shè)計語言沒有軟件復(fù)用語言機(jī)制的缺點(diǎn),使軟件復(fù)用變得簡單、易行,可以通過繼承復(fù)用已有的程序資源,提高軟件開發(fā)的效率,縮短軟件開發(fā)的周期。
繼承是在已知類的基礎(chǔ)上創(chuàng)建新類的過程,已有類稱為基類,新類稱為派生類。派生類不僅能夠繼承基類的功能,而且還能夠?qū)惖墓δ苓M(jìn)行補(bǔ)充、修改或重定義。
C++的繼承可分為公有繼承、保護(hù)繼承和私有繼承,也稱為公有派生、保護(hù)派生和私有派生。在C++中,繼承的語法形式如下(下文中關(guān)于基類和派生類的,一律采用父類和子類去替代。):

class 子類類名:[繼承方式] 父類類名 {
    子類成員聲明與定義;
}

其中,繼承方式可以是public、protected、private,分別對應(yīng)公有繼承、保護(hù)繼承和私有繼承。如果省略繼承方式,C++默認(rèn)為private繼承。
相信作為Android開發(fā)的你,對于繼承,應(yīng)該是了然于胸,那么接下來我們來看看C++繼承的一個簡單例子

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Human {
protected:
    char* name;
    int age;
public:
    void say() {
        cout << "說話" << endl;
    }
};

class Man : public Human {
private:
    char* brother;
public:
    void chasing() {
        cout << "泡妞" << endl;
    }
};

void work(Human &h) {
    h.say();
}

void main() {
    Man m;
    m.say();    //說話

    //1.父類類型的引用或指針
    Human* h_p = &m;
    h_p->say();

    Human &h1 = m;
    h1.say();

    work(m);

    //子類對象初始化父類類型的對象
    Human h2 = m;

    getchar();
}

在C++中沒有extend,取而代之的是:,可以調(diào)用父類的方法

Man m;
m.say();    

父類類型的引用或指針指向子類對象

Human* h_p = &m;
h_p->say();

Human &h1 = m;
h1.say();

子類對象初始化父類類型的對象

Human h2 = m;

1. 公有繼承

繼承方式為public的繼承稱為公有繼承。

1. 公有繼承不改變父類成員在子類中的訪問權(quán)限。在公有繼承方式下,父類中的public、private和protected成員在子類中保持它們在基類中相同的訪問權(quán)限。
2. 在子類中定義的成員函數(shù)不能直接訪問父類的私有成員,只能通過父類的public成員或protected成員訪問它們。

2. 私有繼承

繼承方式為private的繼承稱為私有繼承。

1. 在私有繼承方式下,父類的public和protected成員在子類中都變成了private成員,不再是子類的公有接口函數(shù),不能被父類的外部函數(shù)訪問。
2. 雖然是父類的public和protected成員在子類中都變成了private成員,但它們與父類本身的private成員是有區(qū)別的,它們可被子類的成員函數(shù)直接訪問,而父類中的private成員不能被子類直接訪問。

3. 保護(hù)繼承

繼承方式為protected的繼承稱為保護(hù)繼承。

在保護(hù)繼承方式下,父類的public成員在子類中的訪問權(quán)限被修改為protected權(quán)限,父類的protected成員在子類中仍為protected成員,父類的private成員在子類中仍為private成員。

向父類構(gòu)造函數(shù)傳參

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Human {
protected:
    char* name;
    int age;

public:
    Human(char* name, int age) {
        this->name = name;
        this->age = age;
    }
    void say() {
        cout << "說話" << endl;
    }
};

class Man : public Human {
public:
    void chasing() {
        cout << "泡妞" << endl;
    }
private:
    char* brother;
};

void main() {
    Man m;

    getchar();
}

運(yùn)行報錯,Man沒有合適的構(gòu)造函數(shù),這是為什么呢?明明默認(rèn)是有無參構(gòu)造函數(shù)的啊。因?yàn)?code>Man繼承于Human,Human只有一個有參構(gòu)造參數(shù),看過我之前的博客的同學(xué)肯定知道,只要有有參構(gòu)造函數(shù),就會覆蓋無參構(gòu)造函數(shù)。所以Man的無參構(gòu)造函數(shù)也已經(jīng)被覆蓋了。
我們還可以這樣理解:子類的構(gòu)造函數(shù)除了要負(fù)責(zé)本類成員的初始化外,還要調(diào)用父類和成員對象的構(gòu)造函數(shù),并向它們傳遞參數(shù),以完成父類子對象和成員對象的建立和初始化,而此例中的子類Man的無參構(gòu)造顯然不能完成上述操作,需要一個有參構(gòu)造函數(shù)來完成父類Human的初始化。
那么針對這個問題,我們可以這樣做,給Man添加一個有參構(gòu)造函數(shù)

Man(char* brother, char* h_name, int h_age) : Human(h_name, h_age){
        this->brother = brother;
    }
void main() {
    Man m("jack", "john", 23);

    getchar();
}

編譯運(yùn)行通過。
看到這里大家是不是覺得特別像之前我介紹的構(gòu)造函數(shù)的成員初始化列表NDK開發(fā)---C++學(xué)習(xí)(四):類與函數(shù)(中),不熟悉的,我已插眼,可以隨時TP。
我們在Man類中添加Human對象屬性,去復(fù)習(xí)下構(gòu)造函數(shù)的成員初始化列表

class Man : public Human {
private:
    char* brother;
    Human h1;

public:
    Man(char* brother, char* h_name, int h_age, char* s_name, int s_age) : Human(h_name, h_age), h1(s_name, s_age){
        this->brother = brother;
    }
    void chasing() {
        cout << "泡妞" << endl;
    }
};

void main() {
    Man m("jack", "john", 23, "rose", 25);

    getchar();
}

構(gòu)造函數(shù)與析構(gòu)函數(shù)調(diào)用順序

#include<iostream>
using namespace std;
class Human {
protected:
    char* name;
    int age;

public:
    Human(char* name, int age) {
        this->name = name;
        this->age = age;
        cout << "父類構(gòu)造函數(shù)" << endl;
    }

    ~Human() {
        cout << "父類析構(gòu)函數(shù)" << endl;
    }

    void say() {
        cout << "說話" << endl;
    }
};

class Man : public Human {
private:
    char* brother;

public:
    //給父類構(gòu)造函數(shù)傳參
    Man(char* brother, char* h_name, int h_age) : Human(h_name, h_age) {
        this->brother = brother;
        cout << "子類構(gòu)造函數(shù)" << endl;
    }

    ~Man() {
        cout << "子類析構(gòu)函數(shù)" << endl;
    }

    void chasing() {
        cout << "泡妞" << endl;
    }
};

void func() {
    Man m("jack", "john", 23);
}

void main() {
    func();

    getchar();
}

運(yùn)行打印結(jié)果為:

父類構(gòu)造函數(shù)
子類構(gòu)造函數(shù)
子類析構(gòu)函數(shù)
父類析構(gòu)函數(shù)

也就是父類構(gòu)造函數(shù)先調(diào)用,子類析構(gòu)函數(shù)先調(diào)用。

子類對象調(diào)用父類的成員

子類不僅可以添加父類沒有的新成員,而且還可以對父類的成員函數(shù)進(jìn)行重定義(類似于Java中的重寫)或重載(類似于Java中的重載)。
重定義是指子類可以定義與父類具有相同函數(shù)原型的成員函數(shù)(即具有相同的函數(shù)名、參數(shù)列表和返回值類型),而重載則要求成員函數(shù)名相同、參數(shù)列表不同,與返回值類型無關(guān)。
需要指出的是:子類對父類成員函數(shù)的重定義或重載會影響父類成員函數(shù)在子類中的可見性,父類的同名成員函數(shù)會被子類重載的同名函數(shù)所隱藏。

using namespace std;
class Human {
public:
    char* name;
    int age;

public:
    Human(char* name, int age) {
        this->name = name;
        this->age = age;
        cout << "父類構(gòu)造函數(shù)" << endl;
    }

    ~Human() {
        cout << "父類析構(gòu)函數(shù)" << endl;
    }

    void say() {
        cout << "Human說話" << endl;
    }
};

class Man : public Human {
private:
    char* brother;

public:
    //給父類構(gòu)造函數(shù)傳參
    Man(char* brother, char* h_name, int h_age) : Human(h_name, h_age) {
        this->brother = brother;
        cout << "子類構(gòu)造函數(shù)" << endl;
    }

    ~Man() {
        cout << "子類析構(gòu)函數(shù)" << endl;
    }

    void chasing() {
        cout << "泡妞" << endl;
    }

    void say() {
        cout << "Man說話" << endl;
    }
};

void main() {
    Man m("jack", "john", 23);
    m.say();

    getchar();
}

運(yùn)行打印輸出

Man說話

很明顯m調(diào)用say函數(shù)的時候,找的是子類的say函數(shù),因?yàn)樽宇惖?code>say函數(shù)重定義了父類中的say函數(shù),父類的say函數(shù)隱藏,如果我們想要調(diào)用父類的say函數(shù)的話,該怎樣去做呢?別著急,讓我逐一敘述:

m.Human::say();

這樣調(diào)用的就是父類的say函數(shù),當(dāng)然我們還可以修改父類屬性的值

m.Human::age = 10;

多繼承

C++允許一個類從一個或多個父類派生。如果一個類只有一個父類,就稱為單繼承;如果一個類具有兩個以上的父類,就稱為多繼承。多繼承的形式如下:

class 子類類名:[繼承方式] 父類類名1,[繼承方式] 父類類名2,... {
    子類成員聲明或定義;
}

其中,繼承方式可以是private、protected、private,分別對應(yīng)公有繼承、保護(hù)繼承和私有繼承,其含義與單繼承相同。
C++在解析子類的成員函數(shù)調(diào)用時,按照以下次序查找成員函數(shù)所屬的類。

1. 在子類中查找該函數(shù),若找到就確定該函數(shù)是子類的成員函數(shù)。
2. 如果在子類中沒有找到該成員函數(shù),就在父類中查找該成員函數(shù)。

多繼承方式下的二義性

在多繼承中,子類繼承了多個父類的成員,當(dāng)兩個不同父類擁有同名成員時,容易產(chǎn)生命名沖突問題。

class A {
public:
    char* name;
};

class B1 : public A {

};

class B2 : public A {

};

class C : public B1, public B2 {

};

如果有下面的成員引用:

C c;
c.name = "john";        //錯誤,二義性沖突

C類中擁有A類數(shù)據(jù)成員和成員函數(shù)的兩份備份,在引用來源于A的成員時容易產(chǎn)生二義性。
對于成員屬性name的調(diào)用,編譯器首先在C類中查找,結(jié)果沒有找到,接下來編譯器會在C的父類中查找,但在B1B2的兩個父類中都找到了name,編譯器不能確定調(diào)用哪個父類的name,因此產(chǎn)生二義性的命名沖突。
解決上述二義性命名沖突的辦法是指明成員調(diào)用所屬的類。例如,可以這樣:

C c;
//指定父類顯示調(diào)用
c.B1::name = "john";    //正確

但這樣的調(diào)用方式并未解決本質(zhì)問題,在同一個對象c中存在A的兩份不同數(shù)據(jù)成員,不僅浪費(fèi)存儲空間,而且還容易產(chǎn)生數(shù)據(jù)的不一致性。為了解決這類問題,C++引用了虛擬繼承。

虛擬繼承

利用C++提供的關(guān)鍵字virtual限定繼承方式,將公共父類指定為虛父類,就可以使該父類的成員在子類中只有一份備份。虛父類的定義形式如下:

class 子類類名 : virtual [繼承方式] 父類類名1, virtual [繼承方式] 父類類名2, ... {
    子類成員聲明或定義;
}

針對上述例子

class A {
public:
    char* name;
};

class B1 : virtual public A {

};

class B2 : virtual public A {

};

class C : public B1, public B2 {

};

void main() {
    C c;
    c.name = "john";        

    getchar();
}

通過對公共父類的虛擬繼承,子類只保留了虛父類的一份數(shù)據(jù)成員備份,現(xiàn)在通過子類對象引用虛父類中的成員就不會產(chǎn)生二義性的命名沖突了。

虛函數(shù)

我們先看一個例子:
創(chuàng)建一個頭文件Plane.h

#pragma once
//普通飛機(jī)
class Plane {
public:
    void fly();
    void land();
};

創(chuàng)建cpp文件Plane.cpp

#include "Plane.h"
#include <iostream>
using namespace std;
void Plane::fly() {
    cout << "普通飛機(jī)起飛" << endl;
}

void Plane::land() {
    cout << "普通飛機(jī)著陸" << endl;
}

再創(chuàng)建一個頭文件Helicopter.h

#pragma once
#include"Plane.h"
//直升飛機(jī)
class Helicopter : public Plane {
public:
    void fly();
    void land();
};

創(chuàng)建cpp文件Helicopter.cpp

#include "Plane.h"
#include "Helicopter.h"
#include <iostream>
using namespace std;
void Helicopter::fly() {
    cout << "直升飛機(jī)起飛" << endl;
}

void Helicopter::land() {
    cout << "直升飛機(jī)著陸" << endl;
}

業(yè)務(wù)邏輯

#include "Plane.h"
#include "Helicopter.h"
void bizPlay(Plane &p) {
    p.fly();
    p.land();
}

void main() {
    Plane p1;
    bizPlay(p1);    

    Helicopter p2;
    bizPlay(p2);    
                    
    getchar();
}

運(yùn)行打印結(jié)果為:

普通飛機(jī)起飛
普通飛機(jī)著陸
普通飛機(jī)起飛
普通飛機(jī)著陸

這顯然不是我們想要看見的,我們想要在調(diào)用bizPlay(p2)調(diào)用子類的函數(shù)。C++給出了一種更好的解決方案——虛函數(shù)。
只有類的成員函數(shù)才能被定義為虛函數(shù),不屬于任何類的普通函數(shù)不能被定義成虛函數(shù)。虛函數(shù)的運(yùn)行機(jī)制可以概括如下:如果父類中的非靜態(tài)成員函數(shù)被定義為虛函數(shù),且子類重寫了父類的虛函數(shù),則通過指向父類對象的指針或引用調(diào)用子類對象中的虛函數(shù)時,就會調(diào)用到該指針(或引用)實(shí)際所指對象的成員函數(shù)。
虛函數(shù)的定義方法非常簡單,把限定詞virtual加在類成員函數(shù)的聲明前面,就將此函數(shù)指定為虛函數(shù)了,形式如下:

class X {
    virtual 返回值類型 函數(shù)名(參數(shù)表);
}

virtual的意義在于指示編譯器,對這類函數(shù)采取遲后聯(lián)編(動態(tài)綁定)(關(guān)于多態(tài)與聯(lián)編這方面的知識可閃現(xiàn)到文末進(jìn)行了解)的方法,在程序運(yùn)行過程中才確定與之相對應(yīng)的函數(shù)。而沒有用virtual限定的函數(shù)則采用早期聯(lián)編(靜態(tài)綁定)的方式,在編譯過程中就把函數(shù)調(diào)用與它的函數(shù)實(shí)現(xiàn)關(guān)聯(lián)起來。

#pragma once
//普通飛機(jī)
class Plane {
public:
    virtual void fly();
    virtual void land();
};
#pragma once
#include"Plane.h"
//直升飛機(jī)
class Helicopter : public Plane {
public:
    void fly();
    void land();
};

再次運(yùn)行打印結(jié)果為:

普通飛機(jī)起飛
普通飛機(jī)著陸
直升飛機(jī)起飛
直升飛機(jī)著陸

虛函數(shù)的特性:

一旦將某個類成員函數(shù)聲明為虛函數(shù)后,該成員函數(shù)在子類的繼承體系中就永遠(yuǎn)為虛函數(shù)了。即使子類在重寫該函數(shù)時并沒有將它聲明為虛函數(shù),它仍然是虛函數(shù)。

發(fā)生動態(tài)多態(tài)的條件:

1. 繼承
2. 父類的引用或者指針指向子類的對象
3. 函數(shù)的重寫

純虛函數(shù)

純虛函數(shù)是指在聲明時被初始化為0的虛類成員函數(shù)。純虛函數(shù)在父類中聲明,但它在父類中沒有具體的函數(shù)實(shí)現(xiàn)代碼,要求繼承它的子類為純虛函數(shù)提供實(shí)現(xiàn)代碼。
純虛函數(shù)的聲明形式如下:

class X {
    virtual 返回值類型 函數(shù)名(參數(shù)列表) = 0;
}

接著我們來看一個例子:

//形狀
class Shape {
    //純虛函數(shù)
    virtual void sayArea() = 0;
};

//圓
class Circle : public Shape {
private:
    int r;
public: 
    Circle(int r) {
        this->r = r;
    }
};

void main() {
    Circle c(10);    //錯誤

    getchar();
}

上述代碼中Circle c(10)是錯誤的,因?yàn)?code>Circle繼承Shape這個抽象類了,必須重寫里面的純虛函數(shù)。

//形狀
using namespace std;
class Shape {
    //純虛函數(shù)
    virtual void sayArea() = 0;
};

//圓
class Circle : public Shape {
private:
    int r;
public: 
    Circle(int r) {
        this->r = r;
    }

    void sayArea() {
        cout << (3.14 * r * r) << endl;
    }
};

void main() {
    Circle c(10);

    getchar();
}

重寫之后就不存在問題了。

接口

class Drawable {
    virtual void draw() = 0;
};

多態(tài)與聯(lián)編

可能很多人還不清楚什么是多態(tài)和聯(lián)編,下面我將逐一介紹:
多態(tài)就是指不同對象收到相同消息時會執(zhí)行不同的操作。通俗地講,就是用一個相同的名字定義許多不同的函數(shù),這些函數(shù)可以針對不同數(shù)據(jù)類型實(shí)現(xiàn)相同或相似的功能,即所謂的“一個接口,多種實(shí)現(xiàn)”。
C++中的多態(tài)性與聯(lián)編這一概念密切相關(guān)。一個源程序需要經(jīng)過編譯、連接才能形成可執(zhí)行文件,在這個過程中要把調(diào)用函數(shù)名與對應(yīng)函數(shù)關(guān)聯(lián)在一起,這個過程就是綁定,又稱聯(lián)編。
聯(lián)編又分為靜態(tài)聯(lián)編和動態(tài)聯(lián)編,靜態(tài)聯(lián)編(靜態(tài)綁定)是指在程序執(zhí)行前,編譯器根據(jù)函數(shù)調(diào)用提供的信息,在程序編譯時就把調(diào)用函數(shù)名與具體函數(shù)綁定在一起。
動態(tài)聯(lián)編(動態(tài)綁定)是指在程序編譯時還不能確定函數(shù)調(diào)用所對應(yīng)的具體函數(shù),只有在程序運(yùn)行過程中根據(jù)具體的數(shù)據(jù)類型才能夠確定函數(shù)調(diào)用所對應(yīng)的具體函數(shù),即在程序運(yùn)行時才把函數(shù)名與具體函數(shù)綁定在一起。
靜態(tài)聯(lián)編和動態(tài)聯(lián)編都能夠?qū)崿F(xiàn)多態(tài)性,采用靜態(tài)聯(lián)編實(shí)現(xiàn)的多態(tài)就稱為靜態(tài)多態(tài)性,采用動態(tài)聯(lián)編實(shí)現(xiàn)的多態(tài)就稱為動態(tài)多態(tài)性。靜態(tài)多態(tài)性是通過函數(shù)重載和運(yùn)算符重載在程序編譯時通過靜態(tài)綁定實(shí)現(xiàn)的。動態(tài)多態(tài)性是通過繼承和虛函數(shù)在程序執(zhí)行時通過動態(tài)綁定實(shí)現(xiàn)的。平常所說的面向?qū)ο蟪绦蛟O(shè)計的多態(tài)性,常指運(yùn)行時的多態(tài)性。
靜態(tài)多態(tài)性在編譯時就確定了調(diào)用的具體函數(shù),不需要在執(zhí)行程序時從多個同名函數(shù)中匹配調(diào)用函數(shù),所以執(zhí)行速度快。而動態(tài)多態(tài)性需要在執(zhí)行程序時從多個函數(shù)中匹配調(diào)用函數(shù),所以它比靜態(tài)多態(tài)性的執(zhí)行效率低,但它提供了更多的靈活性、問題的抽象性和程序的可維護(hù)性。

展望

關(guān)于C++的知識目前已經(jīng)基本介紹完了,接下來將介紹NDK相關(guān)的技術(shù)文章,敬請期待!
喜歡本篇博客的簡友們,就請來一波點(diǎn)贊,您的每一次關(guān)注,將成為我前進(jìn)的動力,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容