在度娘那裏找到的一個牛人的回答,就複製了過來http://zhidao.baidu.com/link?url=AhjH4Os7ixE7gzWxChCLyd13yQGdb47dAbSm_p1BsyKmh4__QlCTWoRnKwIG5x9kSDg4tYmAsp1sYKnrF47Odq
C++中的虛函數
雖然很難找到一本不討論多態性的C++書籍或雜誌,可是,大多數這類討論使多態性和C++虛函數的使用看起來很難。我打算在這篇文章中經過從幾個方面和結合一些例子使讀者理解在C++中的虛函數實現技術。說明一點,寫這篇文章只是想和你們交流學習經驗由於本人學識淺薄,不免有一些錯誤和不足,但願你們批評和指正,在此深表感謝!
1、 基本概念
首先,C++經過虛函數實現多態."不管發送消息的對象屬於什麼類,它們均發送具備同一形式的消息,對消息的處理方式可能隨接手消息的對象而變"的處理方式被稱爲多態性。"在某個基類上創建起來的類的層次構造中,能夠對任何一個派生類的對象中的同名過程進行調用,而被調用的過程提供的處理能夠隨其所屬的類而變。"虛函數首先是一種成員函數,它能夠在該類的派生類中被從新定義並被賦予另一種處理功能。
2、 虛函數的定義與派生類中的重定義
class 類名{
public:
virtual 成員函數說明;
}
class 類名:基類名{
public:
virtual 成員函數說明;
}
3、 虛函數在內存中的結構
1.咱們先看一個例子:#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;
}
結果以下:Size of A = 4
2.若是再添加一個虛函數:virtual void fun1() { cout << "A::fun" << endl;}
獲得相同的結果。若是去掉函數前面的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;
}
結果以下:Size of A = 1
3.在看下面的結果: 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;
}
結果以下:Size of A = 12
其實虛函數在內存中結構是這樣的:
圖一
在window2000下指針在內存中佔4個字節,虛函數在一個虛函數表(VTABLE)中保存函數地址。在看下面例子。 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;
}
結果以下:結果以下:
Size of A = 4
虛函數的內存結構以下,你也能夠經過函數指針,先找到虛函數表(VTABLE),而後訪問每一個函數地址來驗證這種結構,在國外網站做者是:Zeeshan Amjad寫的"ATL on the Hood中有詳細介紹"
圖二
4.咱們再來看看繼承中虛函數的內存結構,先看下面的例子 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;
}
結果以下:Size is = 12 ,相信你們一看下面的結構圖就會很清楚,
圖三
5.咱們再來看看用虛函數實現多態性,先看個例子: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;
}
結果:A::f
B::f
C::f
D::f
不用解釋,相信你們一看就明白什麼道理!注意:多態不是函數重載
6.用虛函數實現動態鏈接在編譯期間,C++編譯器根據程序傳遞給函數的參數或者函數返回類型來決定程序使用那個函數,而後編譯器用正確的的函數替換每次啓動。這種基於編譯器的替換被稱爲靜態鏈接,他們在程序運行以前執行。另外一方面,當程序執行多態性時,替換是在程序執行期進行的,這種運行期間替換被稱爲動態鏈接。以下例子: 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修飾符去掉,看看打印的結果,而後再看下面一個例子想一想二者的聯繫。若是把B和C中的virtual修飾符去掉,又會怎樣,結果和沒有去掉同樣。 7.在基類中調用繼承類的函數(若是此函數是虛函數才能如此)仍是先看例子: 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;} 打印結果:A::fun 在6中的例子中,test(A *a)其實有一個繼承類指針向基類指針隱式轉化的過程。能夠看出利用虛函數咱們能夠在基類調用繼承類函數。但若是不是虛函數,繼承類指針轉化爲基類指針後只能夠調用基類函數。反之,若是基類指針向繼承類指針轉化的狀況怎樣,這隻能進行顯示轉化,轉化後的繼承類指針能夠調用基類和繼承類指針。以下例子: 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); //基類指針向繼承類指針進行顯示轉化 pb->fun0(); pb->fun(); return 0;}