1. 程式人生 > >3、【C++】繼承/多型

3、【C++】繼承/多型

    面向物件的三個基本特徵是封裝、繼承、多型。其中,封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。而多型則是為了實現另一個目的——介面重用

    封裝:將一類事物所共有的屬性和行為方法進行總結,封裝在一個類中。該類的形成過程稱為封裝,該封裝的類例項化的物件具有類的所有屬性和行為方法。
    封裝的類的內部按照訪問限定符可以分為:(public:)公有部分、(protected:)保護部分、(private:)私有部分。

一、繼承

1、簡介

    面向物件程式設計中最重要的一個概念是繼承。繼承允許我們依據另一個類來定義一個類,這使得建立和維護一個應用程式變得更容易

。這樣做,也達到了重用程式碼功能和提高執行時間的效果

    當建立一個類時,您不需要重新編寫新的資料成員和成員函式,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱為基類,新建的類稱為派生類

2、分類

繼承分為:公有繼承 、 保護繼承 、 私有繼承。
(1)公有繼承: (格式: class Derived:public Base)
    父類的共有部分繼承到子類的公有部分,父類的保護部分繼承到子類的保護部分,父類的私有部分繼承到子類的私有部分(父類的私有部分在子類不可訪問)。
(2)保護繼承: (格式: class Derived:protected Base)


    父類的共有部分繼承到子類的保護部分,父類的保護部分繼承到子類的保護部分,父類的私有部分繼承到子類的私有部分(父類的私有部分在子類不可訪問)。
(3)私有繼承: (格式: class Derived:private Base)
    父類的所有都繼承到子類的私有部分,父類的私有部分在子類不可訪問。
注意:不管何種型別的繼承關係,父類私有成員到子類中都不能被訪問。

3、例項

(1)公開繼承

#include <iostream> 
using namespace std; 
//基類A
class A
{ 
public: 
    int a; 
    int geta
() { a = 300; return a; } /*保護型別成員在本類和子類中可以訪問*/ protected: int b; private: int c; }; //派生類B class B:public A//公開繼承 { public: void getb() { b = 200; } void show() { cout << b << endl; } void showa() { cout<< a<<endl; } }; /*關鍵在於如何設定介面,成功合理的訪問到各種型別的資料*/ int main(){ B pex; /*公開繼承public成員依舊是public,所以可以類外訪問*/ pex.a = 100; /*b是保護型別成員,可以通過設定public介面來訪問*/ pex.getb(); pex.show(); /*隱藏成員的問題,怎麼訪問到隱藏的成員*/ pex.geta(); pex.showa(); //A a = pex;//子類型別賦給了父類型別 //a.geta(); //cout << a.a << endl; }

(2)私有繼承

/*私有繼承下的許可權變化,關鍵是設定合理的介面訪問 父類中的各種型別的資料成員*/ 
#include <iostream> 

using namespace std; 

class A
{ 
private: 
    void showa()
    { 
        cout << "this is showa()" << endl; 
    } 
protected: 
    void showb()
    { 
        cout << "this is showb" << endl; 
    } 
public: 
    void showc()
    { 
        cout << "this is showc" << endl; 
    } 
    void geta()
    {
        //設定合理的介面訪問A中的私有資料 
        showa(); 
    } 
}; 
class B:private A//私有繼承
{ 
public: 
    void show()
    { 
        showc(); 
        showb(); 
        geta(); 
    } 
}; 

int main()
{ 
    B b; 
    //A a = b;對比公開繼承,對比一下 
    b.show(); 
}
4、友元類

    突破成員訪問許可權,可以設定合理的訪問介面,也可以使用友元類。下面我們看一下,友元類的使用:

/*友元類*/ 
#include <iostream> 
using namespace std; 

class A
{ 
private: 
    int x; 
    int y; 
public: 
    A():x(10),y(123){} 
    /*B,C宣告為A的友元類之後,可以訪問到父類的所有型別成員*/ 
    friend class B; 
    friend class C; 
}; 

class B:public A
{ 
public: 
    void show()
    { 
        cout << x << "---" << y << endl; 
    } 
}; 

class C
{ 
public: 
    void show()
    { 
        A a; c
        out <<a.x<< "---" << a.y << endl; 
    } 
}; 
int main()
{ 
    B b; 
    b.show(); 
    C c; 
    c.show(); 
}
5、繼承中的建構函式、解構函式、過載賦值運算子和拷貝建構函式

    建構函式和解構函式是不能被繼承的,但是可以被呼叫。並且子類一定會呼叫父類的建構函式;

    子類建立物件時,首先呼叫父類的建構函式,再呼叫子類自己的建構函式;子類建立的物件被釋放時,先呼叫子類自己的解構函式,再呼叫父類的解構函式

/*繼承中建構函式和解構函式的呼叫:建構函式和解構函式不可以 
被繼承,但是可以被呼叫,而且子類肯定會呼叫父類的建構函式 
和解構函式。這種機制可以很自然的用於訪問父類的私有成員*/ 
#include <iostream> 
using namespace std; 

class A
{ 
private: 
    int x; 
public: 
    A(int x = 0):x(x)
    { 
        cout <<"A()構造"<<endl; 
        cout << x << endl;
    }
    
    ~A()
    {
        cout << "~A()" << endl;
    } 
    
    int _get()
    { 
        return x; 
    }
}; 
class B:public A
{ 
public: 
    /*在初始化引數列表中可以指定呼叫父類的建構函式,
    指定呼叫建構函式並且給 父類中的私有成員賦值*/ 
    /*注意:子類預設呼叫父類的無參構造,如果下面的程式碼沒有:A(100),
    則會呼叫無參構造,但是父類無參構造 被註釋掉,所以會出錯*/ 
    B():A(100)
    { //x = 200; //A(100); 
        cout << "B()" << endl; 
    } 
    //訪問有參構造的方式,理解這種方式的作用
    /*注意,這種機制下的建構函式所賦的值是賦到了子類中的資料x中, 
    而父類中的x仍然為0*/ 
    ~B()
    {
        cout << "~B()" << endl;
    } 
    int getbx()
    { 
        return _get(); 
    } 
}; 
int main()
{ 
    A a;//構建A物件,此時A類構造被呼叫,並打印出了值 
    B b;//B類為無參構造,首先呼叫了A的構造,在呼叫B的構造 
    
    //列印a物件中的x成員 
    cout <<a._get()<<endl;//a物件中的x為0 
    
    //列印b物件中的x 
    cout << b.getbx()<<endl;//是100 
    /*一層一層的退,先呼叫b的析構,在呼叫a的析構*/ 
}

    拷貝建構函式和賦值運算子函式也不能被繼承:在子類不提供拷貝構造和賦值運算子時,子類預設呼叫父類的賦值運算子和拷貝建構函式。但子類一旦提供拷貝構造和賦值運算子函式則不再呼叫父類拷貝構造和賦值運算子函式。

#include <iostream> 
using namespace std; 
/*系統一旦提供建構函式,系統預設的建構函式將被回收 
記住,拷貝構造也是建構函式*/ 
class A
{ 
    int arr; 
    public: A(){} 
    //A(int x = 0):arr(x){} 
    A(const A& a)
    { 
        cout << "父類拷貝構造" << endl; 
    } 
    
    void operator=(const A& a)
    { 
        cout << "父類賦值運算子函式" << endl; 
    } 
}; 
/*有指標型別的成員時,採用預設機制就麻煩了*/ 
class B:public A
{ 
    //int * pi; 
public: 
    B(){} 
    B(const B& b):A(b)
    { 
        //子類中提供了拷貝建構函式將不再呼叫父類的拷貝構造 
        cout << "子類拷貝構造" << endl; 
    } 
    void operator=(const B& b)
    { 
        A::operator=(b); //呼叫父類的拷貝建構函式的機制 
        cout << "子類賦值運算子函式"<< endl; 
    } 
}; 
int main()
{ 
    B a; 
    B b = a; 
    B c; 
    c = a; 
}
6、名字隱藏機制

    名字隱藏機制:子類中如果定義了和父類中同名的資料,這些資料包括成員變數和成員函式。則會把父類中的資料隱藏掉。
    注意:只要名字相同,計算返回值或者形參列表不同,也會被隱藏。隱藏不代表就沒有了,可以通過類名作用域::訪問到被隱藏的成員。

#include <iostream> 
using namespace std; 

class A
{ 
public: 
    int x; 
    int show()
    { 
        cout << "show A" << endl; 
        return 0; 
    } 
    A()
    {
        x=20;
    } 
    A(int x):x(x)
    {
        cout << "show A(int x)" << endl;
    } 
    void shouu()
    { 
        cout <<"shouu()"<<endl; 
    } 
}; 

class B:public A
{ 
public: 
    int x; 
    int y; 
    B()
    {
        cout << "B()" << endl;
    } 
    void show()
    { 
        cout << "show B" << endl; 
        //A::show(); 
    } 
}; 
int main()
{ 
    B b; 
    b.shouu(); 
    //cout << b.x << endl; 
    //cout << b.A::x << endl; //突破名字隱藏機制 
    //int c = b.show();被隱藏,無法訪問 
    //b.A::show(); 
}
7、多繼承和函式重寫

    多繼承是c++特有的語法機制,表現為一個子類有多個直接的父類。

#include <iostream> 
using namespace std; 
class phone
{ 
    double price; 
public: 
    //phone(); 
    phone(double price = 15):price(price)
    {
        cout << "phone" << endl;
    } 
    ~phone()
    {
        cout << "~phone" << endl;
    } 
    void call()
    { 
        cout << "use calling" << endl; 
    } 
    double getprice()
    { 
        return price; 
    } 
}; 
                        
class MP3
{ 
    double price; 
public: 
    MP3(double price = 20):price(price)
    {
        cout << "MP3" << endl;
    } 
    ~MP3()
    {
        cout << "~MP3" << endl;
    } 
    void play()
    { 
        cout << "use to listening music" << endl; 
    } 
    double getprice()
    { 
        return price; 
    } 
}; 

class vedio
{ 
    double price; 
public: 
    vedio(double price = 0):price(price)
    {
        cout << "vedio" << endl;
    } 
    ~vedio()
    {
        cout << "~vedio" << endl;
    } 
    void vcd()
    { 
        cout << "watch vedio" << endl; 
    } 
    double getprice()
    { 
        return price; 
    } 
}; 
/*多繼承*/ 
class iphone:public phone,public MP3,public vedio
{ 
public: 
    double getprice()
    { 
        return phone::getprice() + MP3::getprice() + vedio::getprice(); 
    } 
}; 

int main()
{ 
    iphone iphone6; 
    //cout << sizeof(iphone) << endl; 
    cout << iphone6.MP3::getprice() << endl; 
    cout << iphone6.phone::getprice() << endl; 
    //用名字隱藏機制解決多分資料同名衝突的問題 
    cout << iphone6.getprice() << endl; 
}

    多繼承遇到的問題:上面的程式碼用sizeof就可以看到,子類在多繼承的時候會多次複製頂層資料,而我們期望的是price這個成員只需要複製一份就可以了,因為多餘的複製是無意義的。首先採用頂層抽象的方式,將三個父類抽象到更高的層面上。

#include <iostream> 
using namespace std; 
/*抽象到更高層的類中*/ 
class product
{ 
    double price; 
public: 
    double getprice()
    { 
        return price; 
    } 
    product(double price = 0):price(price)
    {
        cout <<"product"<<endl;
    } 
}; 

class phone:public product{ 
public: 
    //phone(); 
    phone(double price = 15):product(price)
    {
        cout << "phone" << endl;
    } 
    ~phone()
    {
        cout << "~phone" << endl;
    } 
    void call()
    { 
        cout << "use calling" << endl; 
    } 
}; 

class MP3:public product
{ 
public: 
    MP3(double price = 20):product(price){cout << "MP3" << endl;} 
    ~MP3(){cout << "~MP3" << endl;} 
    void play(){ cout << "use to listening music" << endl; } 
}; 

class vedio:public product{ 
public: 
    vedio(double price = 0):product(price){cout << "vedio" << endl;} 
    ~vedio(){cout << "~vedio" << endl;} 
    void vcd(){ cout << "watch vedio" << endl; } 
}; 

class iphone:public phone,public MP3,public vedio
{ 

};

 int main()
 { 
     iphone iphone6; 
     //cout << iphone6.getprice() << endl;同樣會產生衝突的問題 
     //cout << sizeof(iphone) << endl; 
     cout << iphone6.MP3::getprice() << endl; 
     cout << iphone6.phone::getprice() << endl; 
     //直接呼叫產生衝突問題,編譯器不知道該呼叫哪一個
     //cout << iphone6.getprice() << endl;
}

    上面的程式碼中,product的建構函式 被呼叫了三次,因為這種繼承是一級一級的來的,構造子類的時候找父類,發現父類還有父類,就去呼叫爺爺類的建構函式,三次繼承,三次呼叫。

    這種繼承方式構成了一種菱形或者鑽石型的繼承,叫做菱形繼承或者鑽石繼承,但鑽石繼承並沒有實際解決資料多次複製的問題,為了解決菱形繼承,c++提出了虛繼承。虛繼承就是在繼承的時候加上virtual關鍵字修飾即可。虛繼承對於共同的成員父親類從爺爺類那裡繼承來的,這裡為double price,子類直接越級訪問,直接從爺爺類那裡繼承price。

/*類中也會有對齊和補齊*/ 
#include <iostream> 
using namespace std; 

/*抽象到更高層的類中*/ 
class product
{ 
    int price; 
public: 
    int getprice(){ return price; } 
    product(double price = 0):price(price){cout << "product" << endl;
    } 
}; 
//C++ 虛繼承
class phone:virtual public product
{ 
public: 
    //phone(); 
    phone(double price = 15):product(price){cout << "phone" << endl;}
    ~phone(){cout << "~phone" << endl;} 
    void call(){ cout << "use calling" << endl; } 
}; 

class MP3:virtual public product
{ 
public: 
    MP3(double price = 20):product(price){cout << "MP3" << endl;} 
    ~MP3(){cout << "~MP3" << endl;} 
    void play(){ cout << "use to listening music" << endl; } 
}; 

class vedio:virtual public product
{ 
public: 
    vedio(double price = 0):product(price){cout << "vedio" << endl;} 
    ~vedio(){cout << "~vedio" << endl;} 
    void vcd(){ cout << "watch vedio" << endl; } 
}; 

class iphone:virtual public phone,virtual public MP3,virtual public vedio
{ 
public: 
    iphone(int m = 0
            
           

相關推薦

3C++繼承/

    面向物件的三個基本特徵是:封裝、繼承、多型。其中,封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。而多型則是為了實現另一個目的——介面重用!     封裝:將一類事物所共有的屬性和行為方法進行總結,封裝在一個類中

C++繼承

物件模型:     物件中成員變數在記憶體中的佈局形式。     面向物件程式設計的核心思想是封裝(資料抽象)、繼承(程式碼複用)和多型(動態繫結)。 1.通過使用資料抽象,我們可以將類的介面與實現分離; 2.使用繼承,可以定義相似的型別並對其相似關係建模; 3.使用動態繫

11C++匈牙利命名法

    對於程式中的變數名稱,有人主張儘量讓變數名簡單易懂。在定義變數時,不僅讓變數名稱易於理解,而且資料型別、屬效能夠一眼看出,這種觀念的命名稱為匈牙利命名。     匈牙利命名法是一種程式設計時的命名規範。這種命名法的出發點是把變數名按:屬性+型別+物件描述的順序組合起來,以使程式設計師

9C++記憶體洩露

記憶體洩露 1、記憶體洩露的定義     一般我們常說的記憶體洩漏是指堆記憶體的洩漏。堆記憶體是指程式從堆中分配的,大小任意的(記憶體塊的大小可以在程式執行期決定),使用完後必須顯示釋放的記憶體。     應用程式一般使用malloc,realloc,new等函式從堆中分配到一塊

8C++記憶體管理

C++記憶體管理 一、記憶體分配方式     在C++中記憶體被分為5個區,分別是:棧區、堆區、自由儲存區、全域性/靜態儲存區和常量儲存區。 1、棧區     在執行函式時,函式內區域性變數的儲存單元都可一在棧上建立,函式執行結束時,這些儲存單元自動被釋放。棧記憶體分配運算內建與

7C++單例模式/工廠模式

一、單例模式     單例模式,可以說設計模式中最常應用的一種模式了,據說也是面試官最喜歡的題目。但是如果沒有學過設計模式的人,可能不會想到要去應用單例模式,面對單例模式適用的情況,可能會優先考慮使用全域性或者靜態變數的方式,這樣比較簡單,也是沒學過設計模式的人所能想到的最簡單的方式了。

6C++模板

C++ 模板     模板是泛型程式設計的基礎,泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼。 1、函式模板 int swap(int &a, int &b){int temp = a;a = b; b=temp;} float swap

5C++指標/引用(指標和引用的區別)

一、指標 int p=12; int *q; int *q=&p;     這裡p為int型別的變數,&p就是p的記憶體地址,*q是一個int型別的變數(是一個值),q為指標是地址,int q=&p;把p的地址賦給了指標q,所以q就

4C++靜態成員變數/靜態成員函式/行內函數/友元函式/友元類/友元成員函式

一、靜態成員     我們可以使用 static 關鍵字來把類成員定義為靜態的。當我們宣告類的成員為靜態時,這意味著無論建立多少個類的物件,靜態成員都只有一個副本。     靜態成員在類的所有物件中是共享的。如果不存在其他的初始化語句,在建立第一個物件時,所有的靜態資料都會被初始化為

2C++資料抽象/資料封裝/介面(抽象類)

一、C++資料抽象 1、定義     資料抽象是指,只向外界提供關鍵資訊,並隱藏其後臺的實現細節,即只表現必要的資訊而不呈現細節。資料抽象是一種依賴於介面和實現分離的程式設計(設計)技術。     C++類為資料抽象提供了可能。它們向外界提供了大量用於操作物件資料的公共方法,也就

1C++類&物件/建構函式/拷貝建構函式/操作符過載/解構函式

一、C++類 & 物件     C++ 在 C 語言的基礎上增加了面向物件程式設計,C++ 支援面向物件程式設計。類是 C++ 的核心特性,通常被稱為使用者定義的型別。     類用於指定物件的形式,它包含了資料表示法和用於處理資料的方法。類中的資料和方法稱為類的成員。函式在

C#List權重排序

List.Sort((x, y) => -x.CompareTo(y));  倒序排序 List.Sort((x, y) => x.CompareTo(y));  正序排序   如果需要比較多個條件,比如裝備排序:橙>紫>藍,品質越高

3PythonPython 3入門(流程控制/迭代器/生成器/函式/變數作用域)

一、流程控制 1、if 控制 if 表示式1: 語句 if 表示式2: 語句 elif 表示式3: 語句 else: 語句 elif 表示式4: 語句 else: 語句     1、

15C++C++11新特性:Lamda表示式/可變引數模板

一、Lamda表示式     Lamda表示式是C++11中引入的一項新技術,利用Lamda表示式可以編寫內嵌的匿名函式,用以替換獨立函式或者函式物件,並且使得程式碼更可讀。是一種匿名函式,即沒有函式名的函式;Lamda函式的語法定義如下: [capture] :捕捉

10C++前向宣告巢狀類區域性類(內部類)

一、前向宣告     在C++中,類需要先定義,而後才能被例項化,但是實際存在一種場景是:兩個類需要相互引用或相互成為類中的子物件成員時,就無法先定義使用,在編譯環節就出現錯誤導致編譯失敗,這時就需要用到前向宣告,此外,前向宣告的類不能被例項化。 【示例】 //

C#獲取泛的真實型別

需求:在包含泛型T的類或方法中,想要根據T的具體型別,進行相應的處理,需求虛擬碼如下: public void Test<T>() { if(T is string)

C#訪問泛中的List列表資料

光看標題的確不好說明問題,下面描述一下問題場景: 已知後端自定義的返回的Json資料結構如下: response: { "message": "返回成功", "result": [ { "na

C#winform語言方案

1.CultureInfo的獲取和設定 CultureInfo通常由兩位小寫的LanguageCode+兩位大寫的Country/RegionCode組成,如:zh-CN,zh-TW,jr-JP,en-US,zh-HK。部分地區由languageCode+sr

C++——繼承方式

一、 繼承的概念相關: 1、概念 在C++語言中,一個派生類可以從一個基類派生,也可以從多個基類派生。從一個基類派生的繼承稱為單繼承;從多個基類派生的繼承稱為多繼承。 繼承機制是面向物件程式設計使程式碼可以複用的最重要的手段,它允許程式設計師在保持原有類特性的基礎上進行擴充套件,

C++封裝繼承總結

面向物件的三個基本特徵 面向物件的三個基本特徵是:封裝、繼承、多型。其中,封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。而多型則是為了實現另一個目的——介面重用! 封裝