虛函數,看看吧

來源:互聯網
上載者:User

虛 函 數

    說一個人後入為主,通常是說耳根子軟,人家說什麼就把原先的忘掉了,別人說向東走,他就向東走,一會兒有人說向西了,他立馬趕回來。不過好處顯而易見,這個人特別聽話,如果你有時候不知道自己要幹什麼,或者不確定一會兒要幹什麼,那麼就叫他在旁邊等著,到你拿定主意的時候,再告訴他你的要求,讓他照辦。現實中這樣的事情並不少見,秘書需要作記錄、寫報告、安排行程、聯絡客戶等等,而這些工作都是在你的指示下去做的。如果編程的時候也有這麼聽話的代碼就好了,當然這不是做夢,這完全可以實現。

    求解方程時有一個未知數的概念,用一個可以為任意數的符號代表一個暫時不知道值的數,然後通過運算規則得到符合要求的值,用編程的角度來看,就是加上了一個間接層,我們暫時不處理數值,而是處理一個與數值有關的符號,如果變化的是數值也就是對應的屬性,我們可以用成員變數來處理;如果變化的是運算規則呢?y=f(x),這個f(x)是可變的,要讓我們的解決方案通用於各種可能的f(x),那就要提供一個間接層來串連不同的函數,在C++中用函數指標就可以辦到。

    指標的作用就是可以儲存某種類型的對象在記憶體中的位置,改變指標的值就可以讓他指向不同對象。(指標本身也是一個類型)

    編譯器是如何?對虛函數在運行時刻確定被調用函數的代碼呢?也就是說,虛函數實際上是如何被編譯器處理的呢?這裡把“標準的”方式簡單介紹一下。

    “標準”方式,也就是所謂的“VTABLE”機制。編譯器發現一個類中有被聲明為virtual的函數,就會為其建立一個虛函數表,也就是VTABLE。VTABLE實際上是一個函數指標的數組,每個虛函數佔用這個數組的一個位置。一個類只有一個VTABLE,不管它有多少個執行個體。衍生類別有自己的VTABLE,但是衍生類別的VTABLE與基類的VTABLE有相同的函數排列順序,同名的虛函數被放在兩個數組的相同位置上。在建立類執行個體(對象)的時候,編譯器還會在每個執行個體的記憶體中增加一個vptr欄位(虛函數表指標),該欄位指向本類的VTABLE。通過這些手段,編譯器在看到一個虛函數調用的時候,就會將這個調用改寫,如下面的例子:

class A
{
public:
    virtual void foo() { cout << "A::foo() is called" << endl;}
};

class B: public A
{
public:
    virtual void foo() { cout << "B::foo() is called" << endl;}
};

那麼,在使用的時候,我們可以:

A * a = new B();
a->foo();       // 在這裡,a雖然是指向A的指標,但是被調用的函數(foo)卻是B的!

void bar(A * a)
{
    a->foo();  // 被調用的是A::foo() 還是B::foo()?
}
因為foo()是個虛函數,所以在bar這個函數中,只根據這段代碼,無法確定這裡被調用的是A::foo()還是B::foo(),但是可以肯定的說:如果a指向的是A類的執行個體,則A::foo()被調用,如果a指向的是B類的執行個體,則B::foo()被調用。

編譯時間bar這個函數會被改寫為:

void bar(A * a)
{
    (a->vptr[1])();
}

    因為衍生類別和基類的foo()函數具有相同的VTABLE索引,而他們的vptr又指向不同的VTABLE,因此通過這樣的方法可以在運行時刻決定調用哪個foo()函數。

    雖然實際情況遠非這麼簡單,但是基本原理大致如此。如類X的物件模型如所示:

   虛函數的多態性只能通過對象指標或對象的引用調用來實現,如下的調用:
 

X obj;X* ptr = &obj; X& ref = obj;ptr->VirtualFunc();ref.VirtualFunc();

  將被C++編譯器轉換為如下的形式。
 

( *ptr->vptr[2] )(ptr);( *ptr->vptr[2] )(&ref);

  其中的2表示VirtualFunc在類的虛函數表的第2個槽位。因此,在C++中用函數指標實現虛函數,編譯器在類中隱藏了一個函數指標數組(因為成員函數可能有很多,叫做虛函數表)。顯然這裡增加了一個尋找函數位置的步驟,所以,使用虛函數將降低執行效率,需要在效率和靈活性之間的取捨。虛函數的調用相當於一個C的函數指標調用,其效率也並未降低多少。

我們用C++來看一下虛函數的效果

#include <iostream>

#include <stdlib.h>

class NormalA{

public:

void DoSomething( void ){ cout << "This is class NormalA." << endl; }

};

class NormalB : public NormalA{

public:

void DoSomething( void ){ cout << "This is class NormalB." << endl; }

};

class NormalC : public NormalB{

public:

void DoSomething( void ){ cout << "This is class NormalC." << endl; }

};

 

class VirtualA{

public:

virtual void DoSomething( void ){ cout << "This is class VirtualA." << endl; }

};

class VirtualB : public VirtualA{

public:

void DoSomething( void ){ cout << "This is class VirtualB." << endl; }

};

class VirtualC : public VirtualB{

public:

void DoSomething( void ){ cout << "This is class VirtualC." << endl; }

};

int main()

{

NormalA na;

NormalB nb;

NormalC nc;

VirtualA va;

VirtualB vb;

VirtualC vc;

NormalA * pna;

VirtualA * pva;

na.DoSomething();

nb.DoSomething();

nc.DoSomething();

 

va.DoSomething();

vb.DoSomething();

vc.DoSomething();

 

pna = &na;

pna->DoSomething();

pna = &nb;

pna->DoSomething();

pna = &nc;

pna->DoSomething();

 

pva = &va;

pva->DoSomething();

pva = &vb;

pva->DoSomething();

pva = &vc;

pva->DoSomething();

cin.get();

return 0;

}

結果是

This is class NormalA. //調用的是NormalA::DoSomething();

This is class NormalB. //調用的是NormalB::DoSomething();

This is class NormalC. //調用的是NormalC::DoSomething();

This is class VirtualA. //調用的是VirtualA::DoSomething();

This is class VirtualB. //調用的是VirtualB::DoSomething();

This is class VirtualC. //調用的是VirtualC::DoSomething();

This is class NormalA. //調用的是NormalA::DoSomething(); 沒有虛函數表,NormalA *指定了類型

This is class NormalA. //調用的是NormalA::DoSomething(); 沒有虛函數表,NormalA *指定了類型

This is class NormalA. //調用的是NormalA::DoSomething(); 沒有虛函數表,NormalA *指定了類型

This is class VirtualA. //調用的是VirtualA::DoSomething(); 虛函數表指標指向的是VirtualA

This is class VirtualB. //調用的是VirtualB::DoSomething(); 虛函數表指標指向的是VirtualB

This is class VirtualC. //調用的是VirtualC::DoSomething(); 虛函數表指標指向的是VirtualC

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.