C++中的虛函數(shù)(virtualfunction)

字號(hào):

虛函數(shù)是C++中用于實(shí)現(xiàn)多態(tài)(polymorphism)的機(jī)制。核心理念就是通過(guò)基類訪問(wèn)派生類定義的函數(shù)。假設(shè)我們有下面的類層次:
    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;}
    };
    那么,在使用的時(shí)候,我們可以:
    A * a = new B();
    a->foo(); // 在這里,a雖然是指向A的指針,但是被調(diào)用的函數(shù)(foo)卻是B的!
    這個(gè)例子是虛函數(shù)的一個(gè)典型應(yīng)用,通過(guò)這個(gè)例子,也許你就對(duì)虛函數(shù)有了一些概念。它虛就虛在所謂“推遲聯(lián)編”或者“動(dòng)態(tài)聯(lián)編”上,一個(gè)類函數(shù)的調(diào)用并不是在編譯時(shí)刻被確定的,而是在運(yùn)行時(shí)刻被確定的。由于編寫代碼的時(shí)候并不能確定被調(diào)用的是基類的函數(shù)還是哪個(gè)派生類的函數(shù),所以被成為“虛”函數(shù)。
    虛函數(shù)只能借助于指針或者引用來(lái)達(dá)到多態(tài)的效果,如果是下面這樣的代碼,則雖然是虛函數(shù),但它不是多態(tài)的:
    class A
    {
    public:
    virtual void foo();
    };
    class B: public A
    {
    virtual void foo();
    };
    void bar()
    {
    A a;
    a.foo(); // A::foo()被調(diào)用
    }
    1.1 多態(tài)
    在了解了虛函數(shù)的意思之后,再考慮什么是多態(tài)就很容易了。仍然針對(duì)上面的類層次,但是使用的方法變的復(fù)雜了一些:
    void bar(A * a)
    {
    a->foo(); // 被調(diào)用的是A::foo() 還是B::foo()?
    }
    因?yàn)閒oo()是個(gè)虛函數(shù),所以在bar這個(gè)函數(shù)中,只根據(jù)這段代碼,無(wú)從確定這里被調(diào)用的是A::foo()還是B::foo(),但是可以肯定的說(shuō):如果a指向的是A類的實(shí)例,則A::foo()被調(diào)用,如果a指向的是B類的實(shí)例,則B::foo()被調(diào)用。
    這種同一代碼可以產(chǎn)生不同效果的特點(diǎn),被稱為“多態(tài)”。
    1.2 多態(tài)有什么用?
    多態(tài)這么神奇,但是能用來(lái)做什么呢?這個(gè)命題我難以用一兩句話概括,一般的C++教程(或者其它面向?qū)ο笳Z(yǔ)言的教程)都用一個(gè)畫圖的例子來(lái)展示多態(tài)的用途,我就不再重復(fù)這個(gè)例子了,如果你不知道這個(gè)例子,隨便找本書應(yīng)該都有介紹。我試圖從一個(gè)抽象的角度描述一下,回頭再結(jié)合那個(gè)畫圖的例子,也許你就更容易理解。
    在面向?qū)ο蟮木幊讨?,首先?huì)針對(duì)數(shù)據(jù)進(jìn)行抽象(確定基類)和繼承(確定派生類),構(gòu)成類層次。這個(gè)類層次的使用者在使用它們的時(shí)候,如果仍然在需要基類的時(shí)候?qū)戓槍?duì)基類的代碼,在需要派生類的時(shí)候?qū)戓槍?duì)派生類的代碼,就等于類層次完全暴露在使用者面前。如果這個(gè)類層次有任何的改變(增加了新類),都需要使用者“知道”(針對(duì)新類寫代碼)。這樣就增加了類層次與其使用者之間的耦合,有人把這種情況列為程序中的“bad smell”之一。
    多態(tài)可以使程序員脫離這種窘境。再回頭看看1.1中的例子,bar()作為A-B這個(gè)類層次的使用者,它并不知道這個(gè)類層次中有多少個(gè)類,每個(gè)類都叫什么,但是一樣可以很好的工作,當(dāng)有一個(gè)C類從A類派生出來(lái)后,bar()也不需要“知道”(修改)。這完全歸功于多態(tài)--編譯器針對(duì)虛函數(shù)產(chǎn)生了可以在運(yùn)行時(shí)刻確定被調(diào)用函數(shù)的代碼。