# 極客班C++OOP(下)第五週筆記

極客班C++OOP(下)第五週筆記


0.關於vptr和vtbl

虛指針vptr和虛表vtbl,通俗的說,二者主要用途就是,在繼承關係中肯定虛函數具體調用哪一個函數時用的。c++

1 繼承關係內存佈局

子類必定含有父類的部分(part)數組

對於函數,繼承的話,是繼承調用權,而非函數的空間。函數

2 靜態綁定

函數初始化的時候就知道成員的地址了,形如佈局

//彙編代碼
call xxxxxx

3 動態綁定 ,以及 this的解釋

C++看到一個函數,有兩個選擇:學習

3.1.靜態綁定

//彙編形式:
//__call_@ 0xABDI398A4@func1

3.2.動態綁定

若是符合某些條件:ui

  • a) 經過指針
  • b) 指針是向上轉型的關係
  • c) 調用的是虛函數

只要符合上面的三個條件,編譯器就把代碼編譯成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())。直到查找虛函數表,編譯器才最終肯定是哪一個調用。因此這種虛函數和對象的綁定關係,就叫作動態綁定。指針

3.3 this指針

其中,n 就是這個虛函數在虛表中的順序索引,須要瞭解的是:code

編譯器在編譯初期就把虛函數定義的順序記錄了下來,並對作了索引關聯

因此當咱們是使用指針p(向上轉型)查找虛函數(p->ptr[n])的時候,虛指針就肯定了對應虛函數的地址,而後再調用該虛函數*(p->ptr[n])(),實參爲p,即*(p->ptr[n])(p)

說了那麼多,其實p就是this

3.4 多態

上面3.3小節中說到的動態綁定(即,調用虛函數的時候才能肯定是哪一個對象來調用),實際上就是繼承關係中的多態。

  • vptr 虛指針 , 虛函數的指針
  • vtbl 虛表 , 虛函數地址表,順序存儲了對象虛函數的地址

其中,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();//虛函數,調用派生類本身的虛函數

4. new && delete

new 和 delete 在C++中分別是建立和回收堆內存的配對操做符。既然是操做符,那麼通常就能夠重載。

可是,下面這段話,並非操做符new真正的調用,此處只是一個關鍵字表達式,C++會將該關鍵字分配到具體的operator new()上。

String *ps = new String("Hello world");
delete ps;
//數組
String *p = new String[3];
delete [] p;

上面的例子,就是表達式,表達式分解以後就是operator newoperator 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);//釋放內存

5. 重載 new和delete

既然new和delete是操做符,那麼就能夠重載(c++規定能夠重載的範圍內)。 重載操做符,分兩大類

  • 全域重載
    • ::operator new , ::operator delete
    • ::operator new[], ::operator delete[]
  • 成員重載
    • A::operator new(), A::operator delete()
    • A::operator new[], A::operator delete[]

對於全域範圍內重載,有一個條件是,不能在某個特定的namespace重載,必須是全域的。

同時,new操做符返回的必須是void *類型,第一個參數必須是size_t類型。

inline void * ::operator new(size_t size);

delete操做符則相似

inline void ::operator delete(void * ptr);

6. Basic_String 擴充申請量

在這個示例中,咱們學習了new的主要用途,就是用於自定義的特殊的內存分配。好比 basic_string 類型的內存分配,並非一般咱們看到的new String()對象就完了,該類還作了擴展,即在基本類型佔用空間的基礎上,還增長了extra大小的擴展空間,以做特殊用途。

這類特性,得以給咱們極大的內存管理上的便利。

另外,new 和delete在內存分配上是配對的,可是並不意味着,new以後必然會調用delete來釋放空間。這一點,在ppt中,咱們已經足夠了解,這裏就很少作說明了。

相關文章
相關標籤/搜索