目錄
一:多態(tài)的原理?
1.虛函數(shù)表
?2.原理分析
3.對于虛表存在哪里的探討
4.對于是不是所有的虛函數(shù)都要存進虛函數(shù)表的探討
二:多繼承中的虛函數(shù)表
三:常見的問答題?
接下來的日子會順順利利,萬事勝意,生活明朗-----------林辭憂?
接上篇的多態(tài)的介紹后,接下來介紹多態(tài)的原理以及虛函數(shù)表的相關(guān)知識
一:多態(tài)的原理?
1.虛函數(shù)表
這里從一道經(jīng)典筆試題引入
對于這道題我們可能想到的是計算類 大小的對齊規(guī)則,結(jié)果為4,但結(jié)果為8,這是因為有虛函數(shù)的類要多考慮一指針
在32位系統(tǒng)下是8
如果這里再添加幾個虛函數(shù)呢?
?
所以在這里不管類里面有多少個虛函數(shù) ,只要是包含虛函數(shù)的類計算大小都要考慮添加一指針,再考慮對齊
但這里的一指針是什么呢?
但這里我們就看到在b1中除了_b還存有 一個_vfptr的指針在對象的前面,這個指針就叫做虛函數(shù)表指針,其中v代表virtual,f代表funcation
每一個含有虛函數(shù)的類都至少有一個虛函數(shù)表指針,他的類型為函數(shù)指針數(shù)組,而虛函數(shù)的地址是存放在虛函數(shù)表中的,虛函數(shù)表也叫虛表
?2.原理分析
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
virtual void Func2()
{
cout << "Func2()" << endl;
}
private:
int _b = 1;
};
class Derived : public Base
{
virtual void Func1()
{
cout << "Func()" << endl;
}
private:
int _a = 0;
};
int main()
{
Base b1;
Derived d1;
return 0;
}
?
解釋多態(tài)調(diào)用的兩個條件
對于條件一:必須是父類的指針或引用來調(diào)用函數(shù)
1.父類的指針指向父類對象時,依據(jù)虛函數(shù)表指針(vfptr),在虛函數(shù)表中找到函數(shù)的地址,再call這個地址來執(zhí)行接下來的操作
2.父類的指針指向子類對象時,先完成切片,找到父類的那一部分,依據(jù)虛函數(shù)表指針(vfptr),在虛函數(shù)表中找到函數(shù)的地址,再call這個地址來執(zhí)行接下來的操作
3.由于經(jīng)過虛函數(shù)的重寫后,虛函數(shù)的地址是不相同的,所以結(jié)果是不相同的,這是就形成了多態(tài)
對于編譯器來說上面的兩個調(diào)用是執(zhí)行的同樣的操作,都只是取對象的頭四個字節(jié),就是虛函數(shù)表指針,然后去虛表中找到對應(yīng)調(diào)用函數(shù)的地址,然后執(zhí)行接下來的操作
4.如果是父類的對象調(diào)用函數(shù)的話這時就要分析可能會總成的結(jié)果
這時尤其是這樣的場景,Person* ptr=new Person,Student s;? ?*ptr=s?,這樣如果支持能拷貝虛函數(shù)表指針的話,這時delete? ptr,就調(diào)用的是 Student類的析構(gòu)函數(shù),導(dǎo)致直接錯誤的
5.對于多態(tài)調(diào)用是在運行時,去虛表里面找到函數(shù)指針,確定函數(shù)指針后,調(diào)用函數(shù);
對于普通調(diào)用是在編譯鏈接時,確定函數(shù)地址
6.派生類中只有一個虛表指針(菱形繼承除外),同一個類的對象共用一張?zhí)摫?/span>
7.虛函數(shù)也是也是和成員函數(shù)一樣存在代碼段的,不同的是虛函數(shù)會將自己的地址存在虛表中
對于條件二:虛函數(shù)的重寫
從上面就可以看出虛函數(shù)的重寫也叫覆蓋,覆蓋了原先虛函數(shù)的地址,重寫是語法層的叫法,而覆蓋是原理層的叫法
三:派生類的虛表生成
1.先將基類中的虛表內(nèi)容拷貝一份到派生類的虛表中
2.如果派生類重寫了基類中的某個虛函數(shù),用派生類自己的虛函數(shù)的地址來覆蓋虛表中基類的虛函數(shù)地址
3.派生類自己新增的虛函數(shù)按其在派生類中的聲明順序增加到派生類虛表的最后
3.對于虛表存在哪里的探討
對于棧和堆是不可能的,只有代碼段或者靜態(tài)區(qū),但我們可以自己驗證是存在哪里的
驗證代碼
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
void func()
{
cout << "void func()" << endl;
}
int main()
{
Base b1;
Base b2;
static int a = 0;
int b = 0;
int* p1 = new int;
const char* p2 = "hello world";
printf("靜態(tài)區(qū):%p\n", &a);
printf("棧:%p\n", &b);
printf("堆:%p\n", p1);
printf("代碼段:%p\n", p2);
printf("虛表:%p\n", *((int*)&b1));
printf("虛函數(shù)地址:%p\n", &Base::func1);
printf("普通函數(shù)地址:%p\n", func);
return 0;
}
對于這里的取虛表地址
?
可以這樣來理解,&b1是整個類的地址,然后強轉(zhuǎn)為(int*),再解引用取得就是頭四個字節(jié),即虛表地址?
?
我們發(fā)現(xiàn) 和虛表地址最接近的為代碼段的地址,所以可以確定虛表是存在代碼段的
4.對于是不是所有的虛函數(shù)都要存進虛函數(shù)表的探討
首先確定答案 一定都是存在虛函數(shù)表的
接下來我們在vs上監(jiān)視窗口來查看
分析代碼
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
void func5() { cout << "Derive::func5" << endl; }
private:
int b;
};
class X :public Derive {
public:
virtual void func3() { cout << "X::func3" << endl; }
};
int main()
{
Base b;
Derive d;
X x;
Derive* p = &d;
p->func3();
p = &x;
p->func3();
return 0;
}
??
對于這里監(jiān)視窗口的顯示,在這里對于b是只有兩個虛函數(shù)都存進了虛函數(shù)表中,但對于d和x都應(yīng)該是四個虛函數(shù)存進虛函數(shù)表的,但在這里都只存了兩個虛函數(shù),但驗證多態(tài)調(diào)用的話,結(jié)果為
結(jié)果是多態(tài)調(diào)用,?這時我們就不得不質(zhì)疑此時監(jiān)視窗口 的結(jié)果了
為了進一步的證明。我們可以調(diào)用內(nèi)存窗口來查看
在內(nèi)存中我們就會發(fā)現(xiàn)后兩個地址與前兩個虛函數(shù)的地址很接近,所以我們暫時可以認為虛函數(shù)是都存在虛函數(shù)表中的,
為了確定結(jié)果,我們可以使用打印虛表來驗證猜想
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
void func5() { cout << "Derive::func5" << endl; }
private:
int b;
};
class X :public Derive {
public:
virtual void func3() { cout << "X::func3" << endl; }
};
typedef void (*VFUNC)();
//void PrintVFT(VFUNC a[])
void PrintVFT(VFUNC* a)
{
for (size_t i = 0; a[i] != 0; i++)
{
printf("[%d]:%p->", i, a[i]);
VFUNC f = a[i];
f();
//(*f)();
}
printf("\n");
}
int main()
{
Base b;
PrintVFT((VFUNC*)(*((long long*)&b)));//32位的話,可以采用int
Derive d;
X x;
// PrintVFT((VFUNC*)&d);
PrintVFT((VFUNC*)(*((long long*)&d)));
PrintVFT((VFUNC*)(*((long long*)&x)));
return 0;
}
?
這樣看,只要是虛函數(shù),都會將地址存到類的虛函數(shù)表里面的
?文章來源地址http://www.zghlxwxcb.cn/news/detail-858471.html
二:多繼承中的虛函數(shù)表
同樣的我們可以采用例子來介紹
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
int main()
{
Derive d;
Base1* p1 = &d;
p1->func1();
Base2* p2 = &d;
p2->func1();
return 0;
}
采用監(jiān)視窗口的話?
就會發(fā)現(xiàn)對于基類的兩張?zhí)摫碇卸紱]有存derived類的fun3() ,但我們可以使用多態(tài)的調(diào)用來驗證下
所以的話,fun3是一定存在基類的兩張?虛表中的其中一個里面,這樣采用內(nèi)存來看
所以最好的方式,我們還是來打印兩個基類的虛函數(shù)表的?
typedef void (*VFUNC)();
//void PrintVFT(VFUNC a[])
void PrintVFT(VFUNC* a)
{
for (size_t i = 0; a[i] != 0; i++)
{
printf("[%d]:%p->", i, a[i]);
VFUNC f = a[i];
f();
//(*f)();
}
printf("\n");
}
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
int main()
{
Derive d;
PrintVFT((VFUNC*)(*(int*)&d));
//PrintVFT((VFUNC*)(*(int*)((char*)&d+sizeof(Base1))));
Base2* ptr = &d;
PrintVFT((VFUNC*)(*(int*)ptr));
/*Base1* p1 = &d;
p1->func1();
Base2* p2 = &d;
p2->func1();*/
return 0;
}
?
所以此時我們就會知道,派生類的虛函數(shù)地址是存在第一個基類的虛函數(shù)表里面的?
三:常見的問答題?
文章來源:http://www.zghlxwxcb.cn/news/detail-858471.html
?
到了這里,關(guān)于C++修煉之路之多態(tài)---多態(tài)的原理(虛函數(shù)表)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!