網上各種關於這個的文章,但是我卻找了很久才找到我想要的東西,小小的總結一下。因為我心中一直有個疑問:虛表存在什麼地方,是類還是對象裡面?虛指針又存在什麼地方,是類還是對象?锲而不捨終於在第一篇文章找到我想要的東西。
1.c++類中的重載
看看下面的代碼:
[html]
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(float speed,int total)
{
Vehicle::speed=speed;
Vehicle::total=total;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<endl;
}
protected:
float speed;
int total;
};
class Car:public Vehicle
{
public:
Car(int aird,float speed,inttotal):Vehicle(speed,total)
{
Car::aird=aird;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<"|"<<aird<<endl;
}
protected:
int aird;
};
void main()
{
Vehicle a(120,4);
a.ShowMember();
Car b(180,110,4);
b.ShowMember();
cin.get();
}
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(float speed,int total)
{
Vehicle::speed=speed;
Vehicle::total=total;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<endl;
}
protected:
float speed;
int total;
};
class Car:public Vehicle
{
public:
Car(int aird,float speed,inttotal):Vehicle(speed,total)
{
Car::aird=aird;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<"|"<<aird<<endl;
}
protected:
int aird;
};
void main()
{
Vehicle a(120,4);
a.ShowMember();
Car b(180,110,4);
b.ShowMember();
cin.get();
} 在c++中是允許派生類重載基類成員函數的,對於不同類的對象,調用其類的成員函數的時候,系統是知道如何找到其類的同名成員。上面代碼中的a.ShowMember()調用的是Vehicle::ShowMember(),而b.ShowMember()調用的是
[html]
Car::ShowMemeber()。
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(float speed,int total)
{
Vehicle::speed=speed;
Vehicle::total=total;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<endl;
}
protected:
float speed;
int total;
};
class Car:public Vehicle
{
public:
Car(int aird,float speed,inttotal):Vehicle(speed,total)
{
Car::aird=aird;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<"|"<<aird<<endl;
}
protected:
int aird;
};
void test(Vehicle &temp)
{
temp.ShowMember();
}
void main()
{
Vehicle a(120,4);
Car b(180,110,4);
test(a);
test(b);
cin.get();
}
Car::ShowMemeber()。
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(float speed,int total)
{
Vehicle::speed=speed;
Vehicle::total=total;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<endl;
}
protected:
float speed;
int total;
};
class Car:public Vehicle
{
public:
Car(int aird,float speed,inttotal):Vehicle(speed,total)
{
Car::aird=aird;
}
void ShowMember()
{
cout<<speed<<"|"<<total<<"|"<<aird<<endl;
}
protected:
int aird;
};
void test(Vehicle &temp)
{
temp.ShowMember();
}
void main()
{
Vehicle a(120,4);
Car b(180,110,4);
test(a);
test(b);
cin.get();
}
對象a與b分辨是基類和派生類的對象,而函數test的形參卻只是Vehicle類的引用,按照類繼承的特點,系統把Car類對象看做是一個 Vehicle類對象,因為Car類的覆蓋范圍包含Vehicle類,所以test函數的定義並沒有錯誤,我們想利用test函數達到的目的是,傳遞不同類對象的引用,分別調用不同類的,重載了的,ShowMember成員函數,但是程序的運行結果卻出乎人們的意料,系統分不清楚傳遞過來的基類對象還是派生類對象,無論是基類對象還是派生類對象調用的都是基類的ShowMember成員函數。
解決上面這個問題就是利用多態了。
2.多態中的虛表與虛地址
對C++ 了解的人都應該知道虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱為V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。
[html]
class A
{
protected:
virtual voidtest(){cout<<"aaa"<<endl;}
virtual voidtest1(){cout<<"df"<<endl;}
};
class A
{
protected:
virtual voidtest(){cout<<"aaa"<<endl;}
virtual voidtest1(){cout<<"df"<<endl;}
};
(1)sizeof(A)=4,這個4應該是個指針大小,代表虛指針,虛指針是個函數指針,指向虛函數表裡面的位置,虛表裡面存放的是虛函數的地址。
(2)A a;sizeof(a)=4,虛指針存在於每個對象中,因為類不是一個可以存儲的地方。
(3)C++的編譯器應該是保證虛函數表的指針存在於對象實例中最前面的位置(這是為了保證取到虛函數表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味著我們通過對象實例的地址得到這張虛函數表,然後就可以遍歷其中函數指針,並調用相應的函數。
(4)虛表是整個類共用的,他的大小取決於你定義的虛函數的個數,以及編譯器的策略.一般存在於內存的某個地方,你不需要去管他。
這裡我還有一個問題,為什麼下面這個程序等於1:
[html]
class a
{
protected:
voidtest(){cout<<"aaa"<<endl;}
voidtest1(){cout<<"df"<<endl;}
};
int main()
{
cout<<sizeof(a)<<endl;
getchar();
}
class a
{
protected:
voidtest(){cout<<"aaa"<<endl;}
voidtest1(){cout<<"df"<<endl;}
};
int main()
{
cout<<sizeof(a)<<endl;
getchar();
}
3.虛函數表
(1)如何獲取虛函數表的地址
假設我們有這樣的一個類:
[html]
class Base {
public:
virtual void f() { cout <<"Base::f" << endl; }
virtual void g() { cout <<"Base::g" << endl; }
virtual void h() { cout <<"Base::h" << endl; }
};
class Base {
public:
virtual void f() { cout <<"Base::f" << endl; }
virtual void g() { cout <<"Base::g" << endl; }
virtual void h() { cout <<"Base::h" << endl; }
};
按照上面的說法,我們可以通過Base的實例來得到虛函數表。 下面是實際例程:
[html]
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虛函數表地址:" << (int*)(&b) << endl;
cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b)<< endl;
// Invoke the first virtualfunction
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虛函數表地址:" << (int*)(&b) << endl;
cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b)<< endl;
// Invoke the first virtualfunction
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
實際運行經果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虛函數表地址:0012FED4
虛函數表 — 第一個函數地址:0044F148
Base::f
通過這個示例,我們可以看到,我們可以通過強行把&b轉成int *,取得虛函數表的地址,然後,再次取址就可以得到第一個虛函數的地址了,也就是Base::f(),這在上面的程序中得到了驗證(把int*強制轉成了函數指針)。通過這個示例,我們就可以知道如果要調用Base::g()和Base::h(),其代碼如下:
(Fun)*((int*)*(int*)(&b)+0); //Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()