虛函數的使用:
目的:為了讓類的指針和類的對象可以調用到想調用到函數;
原理:
1 虛函數:virtual func();
2 有一個以上虛函數的類都會隱藏的有一個成員變量:ptr_vtable(指向vtable的指針)
3 vtable用來存儲虛函數的指針的表;
4 在構造函數的時候會初始化ptr_vtable;(所以不會有虛構造函數)
代碼解析:
#include <iostream>
class A
{
public:
void func1()
{
std::cout << "func1 in A" << std::endl;
}
virtual void func2()
{
std::cout << "func2 in A" << std::endl;
}
virtual ~A()
{
std::cout << " ~ A" << std::endl;
}
};
class B : public A
{
public:
void func1()
{
std::cout << "func1 in B" << std::endl;
}
void func2()
{
std::cout << "func2 in B" << std::endl;
}
~B()
{
std::cout << " ~ B" << std::endl;
}
};
class C final:public B
{
public:
void func1 ()
{
std::cout << "func1 in C" << std::endl;
}
void func2 () override
{
std::cout << "func2 in C" << std::endl;
}
~C()
{
std::cout << " ~ C" << std::endl;
}
};
int main(int argc, char *argv[]) {
A* ptr = new C();
ptr->func1(); // func1 in A
ptr->func2(); // func2 in B
delete ptr;
return 0;
}
用類A的變量調用到B到對象時,只有基類中的虛函數才會對應到想調用的函數。
分析:在B的對象構造的時候,會先構造A,這樣就會有vtable,然後構造B的時候,發現有新的func2,
就會更新vtable中func2的地址,所以調用func2的時候,是通過vtable調用的。
深入提問:那麼為什麼func1調用的是A的,而不是B的呢?
進一步:為什麼析構函數要聲明為虛函數?
當A的析構函數被聲明為虛函數的時候,那麼B繼承A後,虛解構函數會指向B的解構函數,這樣就可以
解構到B(真正指向的對象類型)。
深入提問:為什麼A的virtual func2 不會被調用,而A的virtual ~A會被調用?
編譯原理:
1 靜態編譯:編譯時綁定,即是函數調用的地方會替換為響應的函數的定義。(運行更快)
2 動態編譯:運行時綁定,即是該函數調用的時候是在運行的時候,會調用相應的函數。(更加動態)
所以,當動態編譯出問題的時候,我們是無法在編譯階段看出來的,這樣就需要確保動態編譯是我們
想要的,這也是下面這個關鍵字的原因。
進一步:override
Override:專門為子類設計的,要覆蓋父的virtual函數用的,確保會替換父類的虛函數。
Final關鍵字:聲明這個類無法被繼承。