我們首先從一個問題來闡明類的底層機理:
假如有一個類A笆包。里面有一個成員函數get(),比如:
? ? ? ? 定義A a; 那么a.get()表示什么呢抛猖?首先給出答案是get(&a),由于在類的底層機制中谴分,成員函數的第一個參數都是一個指向該類數據結構的指針(靜態(tài)成員函數除外),所以成員函數get()的存在形式為void get(A* this);這也能說明為什么我們在成員函數的定義中總是能夠用this來指代調用對象逞带。
????????我們知道陌兑,要使用一個C++類铡原。必要的條件是在編譯期能得到這個類的頭文件偷厦,并在鏈接期能夠找到相應的符號的鏈接地址(比方成員函數、靜態(tài)數據成員等)燕刻。假設這個C++類與你的使用者在同一個project沪哺。那這個條件非常好滿足:
????????首先。C++類的頭文件非常好獲得酌儒。直接在使用者那里將類的頭文件include就可以辜妓。
????????其次,C++類往往被編譯器作為一個編譯單元,生成一個obj文件籍滴。
????????在最后進行鏈接的過程中酪夷,鏈接器會把project中全部的obj鏈接以生成終于的二進制目標文件。所以鏈接器在遇到一處對類成員函數(或其他形式的符號引用)時孽惰,會在這個類生成的obj文件里找到符號的鏈接地址晚岭。
????????那么,在代碼中使用一個C++類勋功,編譯期和鏈接期須要的究竟是些什么東西呢坦报?換句話說。滿足了什么樣的條件狂鞋。編譯器和鏈接器就不會抱怨了呢片择?依據C++語言的定義。一個C++類實際上是聲明或定義了例如以下幾類內容:
????????1.聲明了一個數據結構骚揍。類中的非靜態(tài)數據成員字管; 和代碼中看不到但假設有虛函數就會生成的虛表入口地址指針等。
????????2.聲明并定義了一堆函數信不,它們第一個參數都是一個指向這個數據結構的指針嘲叔。這些實際上就是類中那些非靜態(tài)成員函數(包含虛函數),它們盡管在類聲明中是寫在類的一對大括號內部抽活。但實際上沒有什么東西被加到前面第1條中所說的內部數據結構中硫戈。實際上。這種聲明僅僅是為這些函數添加了兩個屬性:函數名標識符的作用域被限制在類中下硕;函數第一個參數是this丁逝,被省略不寫了。
????????3.聲明并定義了還有一堆靜態(tài)函數卵牍。它們看上去就是一些普通函數果港,與這個類差點兒沒有關系沦泌。這些實際上就是類中那些靜態(tài)函數糊昙。它們也是一樣,不會在第1條中所說的內部數據結構中添加什么東西谢谦,僅僅是函數名標識符的作用域被限制在類中释牺。
????????4.聲明并定義了一堆全局變量。這些實際上就是類中那些靜態(tài)數據成員回挽。
????????5.聲明并定義了一個全局變量没咙。此全局變量是一個函數指針數組,用來保存此類中全部的虛函數的入口地址千劈。當然祭刚,這個全局變量生成的前提是這個類有虛函數。
看以下的一個樣例:
對于上面列出的這個類MyClass,C++編譯器多數會以例如以下的方式進行編譯:
如今我們再來看一下為什么編譯器須要頭文件和符號地址就能夠編譯鏈接一個使用MyClass的程序了涡驮。
????????首先暗甥,因為編譯器須要在編譯期就知道類的內存布局,以保證能夠生成正確的開辟內存的代碼捉捅。及sizeof(MyClass)的值撤防。有了頭文件,編譯器就知道棒口,一個MyClass占用12字節(jié)的內存空間(見上圖寄月,兩個整數和一個指針)。
????????其次无牵,在調用MyClass的成員函數漾肮、靜態(tài)函數時,鏈接器須要知道這些函數的入口地址合敦,假設無法提供入口地址初橘,鏈接器就會報錯。
????????最后充岛。在引用MyClass的靜態(tài)數據成員時保檐,實際上與引用一個外部全局對象一樣,鏈接器須要知道這些變量的地址崔梗。假設無法提供這些變量的地址夜只,鏈接器也會報錯。
能夠看出:
1.?編譯期:必需要提供的是類的頭文件蒜魄,以使編譯器能夠得知類實例的尺寸和內存布局扔亥。
2.?鏈接期:必需要提供的是程序中引用過的,類的成員函數谈为、靜態(tài)函數旅挤、靜態(tài)數據成員的地址。以使鏈接器能夠正確的生成終于程序伞鲫。
????到這里粘茄,我們能夠猜到。實際上秕脓。導出一個類柒瓣。編譯器實際上僅僅須要將這個類中的:成員函數、靜態(tài)函數吠架、靜態(tài)數據成員當成普通的函數芙贫、全局變量導出就可以。也就是說傍药。我們實際上沒有“導出一個類”磺平。而是把這個類中須要被引用的“有定義的實體”的入口地址像普通函數和變量那樣正常導出就可以魂仍。因為里面的純虛函數VBar沒有定義,所以不會被導出拣挪。