1. 程式人生 > >多型與多型物件模型

多型與多型物件模型

多型即多種形態。多型分兩種

  • 編譯時多型:在編譯期間編譯器根據函式實參的型別確定要調哪個函式(這個我們之前已經接觸過了,和函式過載差不多,是同名函式,但是引數不同)

  • 執行時多型:在程式執行期間才會去判斷到底會呼叫哪個函式。這裡我們主要講的就是動態多型。

C++怎麼實現多型?(動態多型,靜態多型,函式多型,巨集多型)

  • 動態多型通過虛擬函式和繼承實現
  • 靜態多型通過泛型來實現
  • 函式多型通過函式過載來實現
  • 巨集多型通過巨集替換來實現

函式的重寫

  • 被重寫的那個函式一定要是虛擬函式(即父類中的那個函式必須是虛擬函式,在父類中是虛擬函式的情況下,子類中重寫的那個函式可以不加上virtual關鍵字)

  • 重寫的兩個函式一定是一個在父類,一個在子類,子類的虛擬函式對父類的虛擬函式進行重寫

  • 兩個函式的函式名和引數列表必須相同,一模一樣!

多型的形成條件

  • 子類有對父類中的虛擬函式重寫

  • 有一個父類的指標/引用來呼叫重寫的虛擬函式(指向父類調父類的,指向子類調子類的)

注意:如果不構成多型,即不滿足上面的兩個條件,具體呼叫哪個函式是根據呼叫者的型別來確定的。一旦構成多型,具體呼叫哪個函式則是根據呼叫者的物件來確定的(因為父類的指標/引用可以指向子類的物件)

通過下面的程式碼來感受一下多型

class Person

{

public:

        virtual void BuyTickets()

        {

               cout << "普通人全票" << endl;

        }

protected:

        string _name;

};

class Student :public Person

{

public:

        virtual void BuyTickets()

        {

               cout << "學生半票" << endl;

        }

};

void Test(Person& p)

{

        p.BuyTickets();

}

int main()

{

        Person p;

        Student s;

        Test(p);

        Test(s);

        return 0;

}

上述的結果則是Test(p)輸出“普通人全票”,Test(s)輸出“學生半票”,這兩個函式構成了多型。

那麼,注意,假設這兩個函式沒有構成重寫,即假設,把兩個函式virtual關鍵字去掉,這兩個函式其實是構成了重定義,即子類中的函式重定義了父類中的函式。那麼這個時候會輸出什麼呢?

注意這個時候沒有構成多型,那麼就不看呼叫者到底是什麼物件了,而是看呼叫者是什麼型別。這裡Test函式中的引數p是一個Person型別,那麼調的就是父類的函式,即輸出兩個“普通人全票”。

這個時候,我們假設子類中的函式是虛擬函式,父類中的函式不是虛擬函式,即子類函式有關鍵字virtual,父類沒有,構不構成多型呢?

注意到這個時候並不構成多型。

那麼反過來呢?即父類中函式有virtual關鍵字,子類函式中沒有,會怎樣?

這種情況下其實是構成多型的,函式重寫是預設支援這樣的方式的,即子類中的函式預設支援父類的虛擬函式屬性。但是這樣的寫法是不被提倡的,最好還是在兩個函式中都加上virtual關鍵字。

關於多型的一些總結

  • 派生類重寫基類的虛擬函式實現多型,要求函式名、引數列表、返回值完全相同(協變除外),注意這裡返回值也必須要相同,協變的話返回值可以不同,下面是一個協變的例子

class A

{

public:

        virtual A* f1()

        {

               cout << "A::f1()" << endl;

               return this;

        }

};

class B :public A

{

public:

        virtual B* f1()

        {

               cout << "B::f1()" << endl;

               return this;

        }

};

上面的這種情況就是協變,能夠構成多型,即一個虛擬函式返回值可以是父類的指標或引用,一個虛擬函式可以是子類的指標或引用

  • 基類中定義了虛擬函式,在派生類中該函式始終保持虛擬函式的特性(即可以不加virtual關鍵字,但最好加

  • 只有類的非靜態成員函式才能定義為虛擬函式,靜態成員函式不能定義為虛擬函式(靜態成員不能被繼承

  • 如果在類外定義虛擬函式,只能在宣告函式時加virtual關鍵字,定義時不用加。

  • 建構函式不能定義為虛擬函式,雖然可以將operator=定義為虛擬函式,但最好不要這麼做,使用時容易混淆

  • 不要在建構函式和解構函式中呼叫虛擬函式,在建構函式和解構函式中,物件是不完整的,可能會出現未定義的行為。

  • 最好將基類的解構函式宣告為虛擬函式。(這是為了正確地釋放子類物件,考慮到A* p = new B();delete p;

    如果父類的解構函式不是虛擬函式,那麼delete的時候會去呼叫父類的解構函式,這樣就子類物件中屬於子類的那部分就沒有被釋放(父類那部分呼叫父類解構函式釋放了)。如果父類解構函式是虛擬函式,那麼子類的解構函式會預設繼承父類解構函式的虛擬函式屬性,雖然兩者的名字不一樣,但在這裡是一個特殊,在彙編裡面的名字其實已經變了的,仍然構成重寫。這樣的話,如果父類的指標指向的是父類的物件就會呼叫父類的解構函式,父類的指標指向子類的物件就會呼叫子類的解構函式,從而構成多型),下面就是解構函式在彙編中的名字

        

  • 虛表是所有類物件例項共用的

過載、重寫、重定義的區別

返回值

函式名

作用域

引數列表

其他

過載

可以相同,可以不同

相同

同一作用域

必須不同

訪問修飾符可以不同,可以一個是公有的,一個是私有的

重寫

相同(協變除外)

相同

父子作用域

必須相同

父類函式必須有virtual關鍵字

訪問修飾符可以不同

重定義

可以相同,可以不同

相同

父子作用域

可以相同,可以不同

在父子作用域中相同名字的,不是重寫就是重定義

純虛擬函式

     在成員函式形參後面寫上=0,則該成員函式就是純虛擬函式。包含純虛擬函式的類叫做抽象類(介面類),抽象類不能例項化出物件。這個純虛擬函式宣告出來就是讓子類來重寫的,在子類中重寫了這個純虛擬函式之後,才能夠例項化出物件。純虛擬函式不用在父類中初始化

class A

{

public:

virtual void f1()=0;

};

class B :public A

{

public:

        virtual void f1()

        {

               cout << "B::f1()" << endl;

        }

};

虛表(虛擬函式表)

        對於有虛擬函式的類,編譯器都會維護一張虛表,物件的前四個位元組就是指向虛表的指標(注意和菱形繼承虛繼承中的虛基表的區別)

class Base

{

public:

        virtual void func1()

        {

               cout << "Base::func1()" << endl;

        }

private:

        int _a;

};

我們先看看這個Base型別的物件的大小,我們本身期望的是4個位元組(注:這是在32位平臺下)

void Test1()

{

        Base b;

        cout << sizeof(Base) << endl;

}

結果是8個位元組,這多出來的4個位元組就是指向存放虛表位置的指標。只要該物件模型有了虛擬函式,那麼編譯器就會為這個類維護一張存放虛擬函式的表。注意虛擬函式並不是放在虛表中的,虛擬函式還是放在程式碼段,指向虛擬函式的指標放在虛表中。

接下來看下重寫了虛擬函式和沒有重寫虛擬函式的差別

首先來看下如果沒有虛擬函式的重寫

class Base

{

public:

        virtual void func1()

        {

               cout << "Base::func1()" << endl;

        }

        virtual void func2()

        {

               cout << "Base::func2()" << endl;

        }

};

class Derive :public Base

{

public:

        virtual void func3()

        {

               cout << "Derived::func3()" << endl;

        }

        virtual void func4()

        {

               cout << "Derived::func4()" << endl;

        }

};

typedef void(*ptr)();//設一個函式指標

void PrintVTable()//通過這個函式來輸出函式在記憶體中分佈的地址

{

        Base b;

        for (int i = 0; i < 2; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&b + i));

               p();

               cout << ":" << (int*)p << endl;

        }

        cout << endl;

        Derive d;

        for (int i = 0; i < 4; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&d + i));

               p();

               cout << ":" << (int*)p << endl;

        }

}

再來看看有虛擬函式重寫的虛表

class Base

{

public:

        virtual void func1()

        {

               cout << "Base::func1()" << endl;

        }

        virtual void func2()

        {

               cout << "Base::func2()" << endl;

        }

};

class Derive :public Base

{

public:

        virtual void func1()//重寫父類的虛擬函式func1

        {

               cout << "Derived::func1()" << endl;

        }

        virtual void func4()

        {

               cout << "Derived::func4()" << endl;

        }

};

typedef void(*ptr)();//設一個函式指標

void PrintVTable()//通過這個函式來輸出函式在記憶體中分佈的地址

{

        Base b;

        for (int i = 0; i < 2; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&b + i));

               p();

               cout << ":" << (int*)p << endl;

        }

        cout << endl;

        Derive d;

        for (int i = 0; i < 3; ++i)

        {

               ptr p = (ptr)(*((int*)*(int*)&d + i));

               p();

               cout << ":" << (int*)p << endl;

        }

}

看完了上面的單繼承,再來看看多繼承的(給一串程式碼,給一個模型)

class Base1

{

public:

        virtual void fun1()

        {

               cout << "Base::fun1" << endl;

        }

        virtual void fun2()

        {

               cout << "Base::fun2" << endl;

        }

private:

        int b1;

};

class Base2

{

public:

        virtual void fun1()

        {

               cout << "Base::fun1" << endl;

        }

        virtual void fun2()

        {

               cout << "Base::fun2" << endl;

        }

private:

        int b2;

};

class Derive :public Base1, public Base2

{

public:

        virtual void fun1()

        {

               cout << "Derive::fun1" << endl;

        }

        virtual void fun3()

        {

               cout << "Derive::fun3" << endl;

        }

private:

        int d;

};

      

相關推薦

物件模型

多型即多種形態。多型分兩種 編譯時多型:在編譯期間編譯器根據函式實參的型別確定要調哪個函式(這個我們之前已經接觸過了,和函式過載差不多,是同名函式,但是引數不同) 執行時多型:在程式執行期間才會去判斷到底會呼叫哪個函式。這裡我們主要講的就是動態多型。 C+

面向物件【林老師版】:性(十三)

一、多型 多型指的是一類事物有多種形態,比如 1、動物有多種形態:人,狗,豬 import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動物 @abc.abstractmethod def talk(self):

面向物件-

多型是指的多種形態;比如水的多種形態:水,冰多型性:在不考慮例項型別情況下使用例項,多型性分為靜態多型性和動態多型性靜態多型性:如任何型別都可以用運算子+進行運算 多型:同一類事物的多種形態 import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:

Python:父類的繼承

#父類的多繼承 例:孩子繼承父親以及母親的屬性 #父親屬性 class Father(object) : def __init__(self,money): self.money = money def Running(self): pri

案例

多型的思想 面向物件3大概念 封裝: 突破c函式的概念....用類做函式引數的時候,可以使用物件的屬性 和物件的方法 繼承: A B 程式碼複用 多型 : 可以使用未來... 多型很重要 實現多型的三個條件 好比C語言 間接賦值 是指標存在的最大意義是c語言的特有的現象 (

java的引數

1.    型別變數:是一個無條件的識別符號       泛型類:其定義中包含了型別變數2.    如果一個介面/方法聲明瞭型別變數,則其是泛型的。3.    型別變數位於<>中,方便識別:        例如:                型別變數也是一種特殊

python的鴨子類

運行時 作用 的確 好處 接收 覆蓋 實際類型 pytho ron 鴨子類型 對於靜態語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。 對於Python這樣的動態語言來說,則不

程序執行緒(五)--Linux 執行緒模型的比較:LinuxThreads 和 NPTL(轉)

當 Linux 最初開發時,在核心中並不能真正支援執行緒。但是它的確可以通過 clone() 系統呼叫將程序作為可排程的實體。這個呼叫建立了呼叫程序(calling process)的一個拷貝,這個拷貝與呼叫程序共享相同的地址空間。LinuxThreads 專案使用這個呼叫來完全在使用者空間模擬對執行緒的支援

python整字串的物件複用機制

在python的整型物件中,將-5~256(python原始碼中定義)這些整數物件放於小整數物件池中快取 python中除上述的小整數外其他稱為大整數,建立時為其分配空間 但建立兩個相同的大整數時,會為其分配不同的地址空間 在建立字串時,pyt

變量,數據類轉換

浮點 變量名 類型轉換 浮點型 整型 引號 格式 unicode編碼 十進制 目標:掌握java基礎語法知識 1變量 變量即變化中的量,變量中的值是變化的,在java中,使用變量時需要聲明變量,在聲明變量時需要聲明變量名,變量名必須是一個以字母開頭的由字母或數字構成的序列,

python基礎之態性、綁定方法和非綁定方法

info lib img 感知 animal user save python基礎 assm 多態與多態性 多態 多態並不是一個新的知識 多態是指一類事物有多種形態,在類裏就是指一個抽象類有多個子類,因而多態的概念依賴於繼承 舉個栗子:動物有多種形態,人、狗、貓、豬等,py

Python全棧之路系列----之-----面向對象4接口抽象,繼承態)

統一 dog blog 水果 創建 設計 概念 fly 支付 接口類與抽像類 在python中,並沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應該有一些基本的概念 編程思想 歸一化設計:  1.接口類 不實現具體的方法,並且可以多繼承  2.抽象類 可以做一

編譯解釋、動態語言靜態語言、強類語言弱類語言的區別

動態語言 動態 java語言 不同 編譯型 效率 編譯過程 .exe 檢查 (一)編譯型語言和解釋型語言   首先我們區分這樣一個概念,編譯型語言和解釋型語言。我們編程用的都是高級型語言(寫匯編和機器語言的除外),計算機只理解和運行機器語言,所以必須把高級語言翻譯成機器語言

面向對象之態性

strac writing sof 方式 更改 div read abc UNC 一、多態   1、定義:指的是一類事物的多種形態,如水有液體、氣體和固體三種形態, 動物有人、貓、狗等存在形態。   2、示例: #!/usr/bin/env python3 #-*- co

Day 5-3 態性

直接 文件 class metaclass say iam tex bcm method 多態與多態性 鴨子類型 多態與多態性 多態:一類事物有多種形態.比如,動物有多種形態,人,狗,豬,豹子.水也有多種形態,冰,雪,水蒸氣. 1 #多態:同一類事物的多種形態

態性

bstr imp 擴展性 mod people mode sel elf 執行文件 多態 多態指的是一類事物的多種形態 如:動物有多種形態,狗,豬 文件有多種形態,文本文件,可執行文件 多態性: 多態性:指的是可以在不考慮對象的類型的情況下而直接使用對象

Python 面向對象 組合-態性-封裝-property

階段 架構師 pri ons input level card 類型 ont 面向對象-組合 1.什麽是組合   組合指的是某一個對象擁有一個屬性,該屬性的值是另外一個類的對象 1 class Foo: 2 xxx = 111 3 4 class Bar

【Linux】程序執行緒之間的區別

http://blog.csdn.net/byrsongqq/article/details/6339240 網路程式設計中設計併發伺服器,使用多程序與多執行緒 ,請問有什麼區別?  答案一: 1,程序:子程序是父程序的複製品。子程序獲得父程序資料空間、堆和棧的複製品。 2,執行緒:相

Java開發筆記(三十二)字符相互轉化

傳播 out 字母 href 不但 java 個數 進制數 com 前面提到字符類型是一種新的變量類型,然而編碼實踐的過程中卻發現,某個具體的字符值居然可以賦值給整型變量!就像下面的例子代碼那樣,把字符值賦給整型變量,編譯器不但沒報錯,而且還能正常運行! // 字符允許

Java開發筆記(三十二)字元相互轉化

前面提到字元型別是一種新的變數型別,然而編碼實踐的過程中卻發現,某個具體的字元值居然可以賦值給整型變數!就像下面的例子程式碼那樣,把字元值賦給整型變數,編譯器不但沒報錯,而且還能正常執行! // 字元允許直接賦值給整型變數 private static void charToInt() { i