轉(zhuǎn)自:http://www.vckbase.com/document/viewdoc/?id=950
C++中的虛函數(shù)(一)
作者:aber
雖然很難找到一本不討論多態(tài)性的C++書籍或雜志,但是,大多數(shù)這類討論使多態(tài)性和C++虛函數(shù)的使用看起來很難。我打算在這篇文章中通過從幾個(gè)方面和結(jié)合一些例子使讀者理解在C++中的虛函數(shù)實(shí)現(xiàn)技術(shù)。說明一點(diǎn),寫這篇文章只是想和大家交流學(xué)習(xí)經(jīng)驗(yàn)因?yàn)楸救藢W(xué)識(shí)淺薄,難免有一些錯(cuò)誤和不足,希望大家批評(píng)和指正,在此深表感謝!
一、 基本概念
首先,C++通過虛函數(shù)實(shí)現(xiàn)多態(tài)."無論發(fā)送消息的對(duì)象屬于什么類,它們均發(fā)送具有同一形式的消息,對(duì)消息的處理方式可能隨接手消息的對(duì)象而變"的處理方式被稱為多態(tài)性。"在某個(gè)基類上建立起來的類的層次構(gòu)造中,可以對(duì)任何一個(gè)派生類的對(duì)象中的同名過程進(jìn)行調(diào)用,而被調(diào)用的過程提供的處理可以隨其所屬的類而變。"虛函數(shù)首先是一種成員函數(shù),它可以在該類的派生類中被重新定義并被賦予另外一種處理功能。
二、 虛函數(shù)的定義與派生類中的重定義
class 類名{
public:
virtual 成員函數(shù)說明;
}
class 類名:基類名{
public:
virtual 成員函數(shù)說明;
}
三、 虛函數(shù)在內(nèi)存中的結(jié)構(gòu)
1.我們先看一個(gè)例子:
#include "iostream.h"
#include "string.h"
class A {
public:
virtual void fun0() { cout << "A::fun0" << endl; }
};
int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return 0;
}
結(jié)果如下:Size of A = 4
2.如果再添加一個(gè)虛函數(shù):virtual void fun1() { cout << "A::fun" << endl;}
得到相同的結(jié)果。如果去掉函數(shù)前面的virtual修飾符
class A {
public:
void fun0() { cout << "A::fun0" << endl; }
};
int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return 0;
}
結(jié)果如下:Size of A = 1
3.在看下面的結(jié)果:
class A {
public:
virtual void fun0() { cout << "A::fun0" << endl; }
int a;
int b;
};
int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return 0;
}
結(jié)果如下:Size of A = 12
其實(shí)虛函數(shù)在內(nèi)存中結(jié)構(gòu)是這樣的:

圖一
在window2000下指針在內(nèi)存中占4個(gè)字節(jié),虛函數(shù)在一個(gè)虛函數(shù)表(VTABLE)中保存函數(shù)地址。在看下面例子。
class A {
public:
virtual void fun0() { cout << "A::fun0" << endl; }
virtual void fun1() { cout << "A::fun1" << endl; }
int a;
int b;
};
int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return 0;
}
結(jié)果如下:結(jié)果如下:
Size of A = 4
虛函數(shù)的內(nèi)存結(jié)構(gòu)如下,你也可以通過函數(shù)指針,先找到虛函數(shù)表(VTABLE),然后訪問每個(gè)函數(shù)地址來驗(yàn)證這種結(jié)構(gòu),在國外網(wǎng)站作者是:Zeeshan Amjad寫的"ATL on the Hood中有詳細(xì)介紹"

圖二
4.我們?cè)賮砜纯蠢^承中虛函數(shù)的內(nèi)存結(jié)構(gòu),先看下面的例子
class A {
public:
virtual void f() { }
};
class B {
public:
virtual void f() { }
};
class C {
public:
virtual void f() { }
};
class Drive : public A, public B, public C {
};
int main() {
Drive d;
cout << "Size is = " << sizeof(d) << endl;
return 0;
}
結(jié)果如下:Size is = 12 ,相信大家一看下面的結(jié)構(gòu)圖就會(huì)很清楚,

圖三
5.我們?cè)賮砜纯从锰摵瘮?shù)實(shí)現(xiàn)多態(tài)性,先看個(gè)例子:
class A {
public:
virtual void f() { cout << "A::f" << endl; }
};
class B :public A{
public:
virtual void f() { cout << "B::f" << endl;}
};
class C :public A {
public:
virtual void f() { cout << "C::f" << endl;}
};
class Drive : public C {
public:
virtual void f() { cout << "D::f" << endl;}
};
int main(int argc, char* argv[])
{
A a;
B b;
C c;
Drive d;
a.f();
b.f();
c.f();
d.f();
return 0;
}
結(jié)果:A::f
B::f
C::f
D::f
不用解釋,相信大家一看就明白什么道理!注意:多態(tài)不是函數(shù)重載
6.用虛函數(shù)實(shí)現(xiàn)動(dòng)態(tài)連接在編譯期間,C++編譯器根據(jù)程序傳遞給函數(shù)的參數(shù)或者函數(shù)返回類型來決定程序使用那個(gè)函數(shù),然后編譯器用正確的的函數(shù)替換每次啟動(dòng)。這種基于編譯器的替換被稱為靜態(tài)連接,他們?cè)诔绦蜻\(yùn)行之前執(zhí)行。另一方面,當(dāng)程序執(zhí)行多態(tài)性時(shí),替換是在程序執(zhí)行期進(jìn)行的,這種運(yùn)行期間替換被稱為動(dòng)態(tài)連接。如下例子:
class A{
public:
virtual void f(){cout << "A::f" << endl;};
};
class B:public A{
public:
virtual void f(){cout << "B::f" << endl;};
};
class C:public A{
public:
virtual void f(){cout << "C::f" << endl;};
};
void test(A *a){
a->f();
};
int main(int argc, char* argv[])
{
B *b=new B;
C *c=new C;
char choice;
do{
cout<<"type B for class B,C for class C:"<<endl;
cin>>choice;
if(choice==''b'')
test(b);
else if(choice==''c'')
test(c);
}while(1);
cout<<endl<<endl;
return 0;
}
在上面的例子中,如果把類A,B,C中的virtual修飾符去掉,看看打印的結(jié)果,然后再看下面一個(gè)例子想想兩者的聯(lián)系。如果把B和C中的virtual修飾符去掉,又會(huì)怎樣,結(jié)果和沒有去掉一樣。
7.在基類中調(diào)用繼承類的函數(shù)(如果此函數(shù)是虛函數(shù)才能如此)還是先看例子:
class A {
public:
virtual void fun() {
cout << "A::fun" << endl;
}
void show() {
fun();
}
};
class B : public A {
public:
virtual void fun() {
cout << "B::fun" << endl;
}
};
int main() {
A a;
a.show();
return 0;
}
打印結(jié)果:A::fun
在6中的例子中,test(A *a)其實(shí)有一個(gè)繼承類指針向基類指針隱式轉(zhuǎn)化的過程。可以看出利用虛函數(shù)我們可以在基類調(diào)用繼承類函數(shù)。但如果不是虛函數(shù),繼承類指針轉(zhuǎn)化為基類指針后只可以調(diào)用基類函數(shù)。反之,如果基類指針向繼承類指針轉(zhuǎn)化的情況怎樣,這只能進(jìn)行顯示轉(zhuǎn)化,轉(zhuǎn)化后的繼承類指針可以調(diào)用基類和繼承類指針。如下例子:
class A {
public:
void fun() {
cout << "A::fun" << endl;
}
};
class B : public A {
public:
void fun() {
cout << "B::fun" << endl;
}
void fun0() {
cout << "B::fun0" << endl;
}
};
int main() {
A *a=new A;
B *b=new B;
A *pa;
B *pb;
pb=static_cast<B *>(a); //基類指針向繼承類指針進(jìn)行顯示轉(zhuǎn)化
pb->fun0();
pb->fun();
return 0;
}

參考資料:
1.科學(xué)出版社 《C++程序設(shè)計(jì)》
2.Zeeshan Amjad 《ATL on the Hood》