天天看點

C++ 多态

C++程式設計語言是一款應用廣泛,支援多種程式設計的計算機程式設計語言。我們今天就會為大家詳細介紹其中C++多态性的一些基本知識,以友善大家在學習過程中對此能夠有一個充分的掌握。

  多态性可以簡單地概括為“一個接口,多種方法”,程式在運作時才決定調用的函數,它是面向對象程式設計領域的核心概念。多态(polymorphisn),字面意思多種形狀。

  C++多态性是通過虛函數來實作的,虛函數允許子類重新定義成員函數,而子類重新定義父類的做法稱為覆寫(override),或者稱為重寫。(這裡我覺得要補充,重寫的話可以有兩種,直接重寫成員函數和重寫虛函數,隻有重寫了虛函數的才能算作是展現了C++多态性)而重載則是允許有多個同名的函數,而這些函數的參數清單不同,允許參數個數不同,參數類型不同,或者兩者都不同。編譯器會根據這些函數的不同清單,将同名的函數的名稱做修飾,進而生成一些不同名稱的預處理函數,來實作同名函數調用時的重載問題。但這并沒有展現多态性。

  多态與非多态的實質差別就是函數位址是早綁定還是晚綁定。如果函數的調用,在編譯器編譯期間就可以确定函數的調用位址,并生産代碼,是靜态的,就是說位址是早綁定的。而如果函數調用的位址不能在編譯器期間确定,需要在運作時才确定,這就屬于晚綁定。

  那麼多态的作用是什麼呢,封裝可以使得代碼子產品化,繼承可以擴充已存在的代碼,他們的目的都是為了代碼重用。而多态的目的則是為了接口重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數都能夠通過同一個接口調用到适應各自對象的實作方法。

  最常見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實作不同的方法。如果沒有使用虛函數的話,即沒有利用C++多态性,則利用基類指針調用相應的函數的時候,将總被限制在基類函數本身,而無法調用到子類中被重寫過的函數。因為沒有多态性,函數調用的位址将是一定的,而固定的位址将始終調用到同一個函數,這就無法實作一個接口,多種方法的目的了。

#include<iostream>

using namespace std;

class A

{

public:

void foo()

{

printf("1\n");

}

virtual void fun()

printf("2\n");

};

class B : public A

printf("3\n");

void fun()

printf("4\n");

int main(void)

A a;

B b;

A *p = &a;

p->foo();

p->fun();

p = &b;

return 0;

}

  第一個p->foo()和p->fuu()都很好了解,本身是基類指針,指向的又是基類對象,調用的都是基類本身的函數,是以輸出結果就是1、2。

    第二個輸出結果就是1、4。p->foo()和p->fuu()則是基類指針指向子類對象,正式展現多态的用法,p->foo()由于指針是個基類指針,指向是一個固定偏移量的函數,是以此時指向的就隻能是基類的foo()函數的代碼了,是以輸出的結果還是1。而p->fun()指針是基類指針,指向的fun是一個虛函數,由于每個虛函數都有一個虛函數清單,此時p調用fun()并不是直接調用函數,而是通過虛函數清單找到相應的函數的位址,是以根據指向的對象不同,函數位址也将不同,這裡将找到對應的子類的fun()函數的位址,是以輸出的結果也會是子類的結果4。

  筆試的題目中還有一個另類測試方法。即

       B *ptr = (B *)&a;  ptr->foo();  ptr->fun();

  問這兩調用的輸出結果。這是一個用子類的指針去指向一個強制轉換為子類位址的基類對象。結果,這兩句調用的輸出結果是3,2。

  并不是很了解這種用法,從原理上來解釋,由于B是子類指針,雖然被賦予了基類對象位址,但是ptr->foo()在調用的時候,由于位址偏移量固定,偏移量是子類對象的偏移量,于是即使在指向了一個基類對象的情況下,還是調用到了子類的函數,雖然可能從始到終都沒有子類對象的執行個體化出現。

  而ptr->fun()的調用,可能還是因為C++多态性的原因,由于指向的是一個基類對象,通過虛函數清單的引用,找到了基類中fun()函數的位址,是以調用了基類的函數。由此可見多态性的強大,可以适應各種變化,不論指針是基類的還是子類的,都能找到正确的實作方法。

//小結:1、有virtual才可能發生多态現象

// 2、不發生多态(無virtual)調用就按原類型調用

class Base

virtual void f(float x)

cout<<"Base::f(float)"<< x <<endl;

void g(float x)

cout<<"Base::g(float)"<< x <<endl;

void h(float x)

cout<<"Base::h(float)"<< x <<endl;

class Derived : public Base

cout<<"Derived::f(float)"<< x <<endl; //多态、覆寫

void g(int x)

cout<<"Derived::g(int)"<< x <<endl; //隐藏

cout<<"Derived::h(float)"<< x <<endl; //隐藏

Derived d;

Base *pb = &d;

Derived *pd = &d;

// Good : behavior depends solely on type of the object

pb->f(3.14f); // Derived::f(float) 3.14

pd->f(3.14f); // Derived::f(float) 3.14

// Bad : behavior depends on type of the pointer

pb->g(3.14f); // Base::g(float) 3.14

pd->g(3.14f); // Derived::g(int) 3

pb->h(3.14f); // Base::h(float) 3.14

pd->h(3.14f); // Derived::h(float) 3.14

令人迷惑的隐藏規則

本來僅僅差別重載與覆寫并不算困難,但是C++的隐藏規則使問題複雜性陡然增加。

這裡“隐藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:

(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual

關鍵字,基類的函數将被隐藏(注意别與重載混淆)。

(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual

關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)。

上面的程式中:

(1)函數Derived::f(float)覆寫了Base::f(float)。

(2)函數Derived::g(int)隐藏了Base::g(float),而不是重載。

(3)函數Derived::h(float)隐藏了Base::h(float),而不是覆寫。

C++純虛函數

 一、定義

  純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實作方法。在基類中實作純虛函數的方法是在函數原型後加“=0” 

  virtual void funtion()=0 

二、引入原因

   1、為了友善使用多态特性,我們常常需要在基類中定義虛拟函數。 

   2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。 

  為了解決上述問題,引入了純虛函數的概念,将函數定義為純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實作多态性。同時含有純虛拟函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。

三、相似概念

   1、多态性 

  指相同對象收到不同消息或不同對象收到相同消息時産生不同的實作動作。C++支援兩種多态性:編譯時多态性,運作時多态性。

  a、編譯時多态性:通過重載函數實作 

  b、運作時多态性:通過虛函數實作。 

  2、虛函數 

  虛函數是在基類中被聲明為virtual,并在派生類中重新定義的成員函數,可實作成員函數的動态覆寫(Override)

  3、抽象類 

  包含純虛函數的類稱為抽象類。由于抽象類包含了沒有定義的純虛函數,是以不能定義抽象類的對象。