虛指針vptr
和虛表vtbl
,通俗的說,二者主要用途就是,在繼承關係中肯定虛函數具體調用哪一個函數時用的。c++
子類必定含有父類的部分(part)數組
對於函數,繼承的話,是繼承調用權,而非函數的空間。函數
函數初始化的時候就知道成員的地址了,形如佈局
//彙編代碼 call xxxxxx
C++看到一個函數,有兩個選擇:學習
//彙編形式: //__call_@ 0xABDI398A4@func1
若是符合某些條件:ui
只要符合上面的三個條件,編譯器就把代碼編譯成this
class A{ public: virtual void vfun1(); void func2(); }; class B:public A{ public: virtual void vfun1(); void fun2(); }; //調用 A p=new B();//B是A的派生類,而且調用了虛函數 p->vfun1();//虛函數,覆蓋了父類的虛函數,動態綁定 p->fun2(); //普通函數,靜態綁定,無需動態肯定,畢竟B裏面原本就有了
編譯器看來,p->vfun1() 的形式以下:spa
(*(p->vptr)[n])(p); //或者 (* p->vptr[n])(p);
即,調用虛函數以前,咱們仍然不知道該函數對應的是哪一個調用(究竟是調用A::vfun1()
呢,仍是B::vfun1()
)。直到查找虛函數表,編譯器才最終肯定是哪一個調用。因此這種虛函數和對象的綁定關係,就叫作動態綁定。指針
其中,n
就是這個虛函數在虛表中的順序索引,須要瞭解的是:code
編譯器在編譯初期就把虛函數定義的順序記錄了下來,並對作了索引關聯
因此當咱們是使用指針p
(向上轉型)查找虛函數(p->ptr[n])
的時候,虛指針就肯定了對應虛函數的地址,而後再調用該虛函數*(p->ptr[n])()
,實參爲p
,即*(p->ptr[n])(p)
說了那麼多,其實p就是this
上面3.3
小節中說到的動態綁定(即,調用虛函數的時候才能肯定是哪一個對象來調用),實際上就是繼承關係中的多態。
其中,vptr
是當基類定義了虛函數的時候,若是子類繼承了基類
,那麼子類在實例化的時候,虛指針就能夠指出調用的是哪一個函數了。
舉個例子:
有時候咱們須要在容器中存放不少不一樣的水果,香蕉、蘋果、梨,可是容器只能存放一種東西,因此只好存指針(指針纔是無差異的)。那麼,存儲什麼類型的指針呢?咱們這麼多種類的東西,都是水果,因此,應該放水果的指針進去,根據以前的向上轉型的例子,能夠看出,具體水果能夠轉型爲基類水果類型。
std::list<Fruit*> myList;
咱們定義了一個myList,該容器存放的是 Fruit*
這種類型的指針。
class Fruit{ public: virtual print(){ std::cout << "I'am Fruit!";} }; class Apple:public Fruit{ public: virtual print(){ std::cout << "I'am Apple!";} }; class Banana:public Fruit{ public: virtual print(){ std::cout << "I'am Banana!";} }; class Pear:public Fruit{ public: virtual print(){ std::cout << "I'am Pear!";} }; //那咱們就能夠在裏面放各類派生類型了 myList.push_back(new Apple()); myList.push_back(new Banana()); myList.push_back(new Pear()); Fruit pApple = new Apple(); pApple->print();//虛函數,調用派生類本身的虛函數
new 和 delete 在C++中分別是建立和回收堆內存的配對操做符。既然是操做符,那麼通常就能夠重載。
可是,下面這段話,並非操做符new真正的調用,此處只是一個關鍵字表達式,C++會將該關鍵字分配到具體的operator new()
上。
String *ps = new String("Hello world"); delete ps; //數組 String *p = new String[3]; delete [] p;
上面的例子,就是表達式,表達式分解以後就是operator new
和operator delete
兩個函數調用,以及相應內存管理過程。 ``
其中,new String("Hello world")
,這是表達式,能夠分解爲如下幾個動做。
try { void *mem =operator new (sizeof(String));//操做符函數,能夠重載 String *ps = static_cast<String*>(mem);//轉型 ps->String::String("Hello world");//構造 }
而,delete
操做符,扮演的是
p->~String(); //調用析構函數 operator delete(p);//釋放內存
既然new和delete是操做符,那麼就能夠重載(c++規定能夠重載的範圍內)。 重載操做符,分兩大類
對於全域範圍內重載,有一個條件是,不能在某個特定的namespace重載,必須是全域的。
同時,new操做符返回的必須是void *
類型,第一個參數必須是size_t
類型。
inline void * ::operator new(size_t size);
delete操做符則相似
inline void ::operator delete(void * ptr);
在這個示例中,咱們學習了new的主要用途,就是用於自定義的特殊的內存分配。好比 basic_string 類型的內存分配,並非一般咱們看到的new String()對象就完了,該類還作了擴展,即在基本類型佔用空間的基礎上,還增長了extra大小的擴展空間,以做特殊用途。
這類特性,得以給咱們極大的內存管理上的便利。
另外,new 和delete在內存分配上是配對的,可是並不意味着,new以後必然會調用delete來釋放空間。這一點,在ppt中,咱們已經足夠了解,這裏就很少作說明了。