《深入理解c++對象內存模型》

《c++對象內存模型》讀書筆記


這本書大二第一次接觸,剛開始由於功力不夠不能很好的消化這本書的內功,多讀幾遍就會對自己的語言思想有很大的提升。這本書出版很久了,但一直沒被淘汰。記錄自己的學習筆記,溫故知新。


C語言不是面向對象的計算機編程語言,它主要由基本數據類型,struct結構體,和函數,數據塊和函數是沒有關聯的,函數就相當於服務指令,你想加工什麼數據,就通過調用函數傳人數據參數,從而獲得該函數的服務。這樣的話當工程很大的時候就就會定義很多的函數很多的結構體,這些結構體和函數根據去功能通過.h  .m文件進行劃分,成百上千的函數結構體會讓頭文件的引用變得非常的複雜,從而降低開發效率,和程序設計的難度。


C++是面向對象編程的計算機高級語言,封裝,繼承,多態。數據塊和處理數據塊的功能函數就關聯起來,以前用文件的形式來劃分數據塊和函數變成的用類的形式。提高開發效率,用面向對象的程序設計思想來設計程序使得更加的清晰。


對於計算機語言,與其說高級語言牛叉到不如說是編譯器牛叉,從本質上來講,計算機高級語言寫出來的源代碼都逃不過編譯器的掌心,它們都會被編譯成對應的CPU指令,對於動態語言無非就是加上一個runtime,既然語言都最後會變成CPU指令,那麼計算機語言之間都可以一一對應的關係,那麼C++語言編寫的程序也無非就是函數的調用,參數的傳遞,但在語法特性上又存在各自的差異。這就說明了編譯器對C++語言做了很多的事。


計算機語言是標準,對應的編譯器是標準的實現,編譯器越是智能越是強大,它爲你做的東西就多,這就爲什麼java,c#,swift不需要頭文件,因爲開發環境編譯器能動態的檢測到你設計的變量和函數,swift甚至不需要main函數,swift是面向對象的語言,如果編譯器檢測到了函數調用,它就會用這個函數作爲main函數作爲程序的入口點。


對於不同的書,不同的讀者有不同的看法,以下爲個人學習記錄,不足之處還望見諒。


問題:

1.問什麼會有self變量?

2.成員函數和虛函數的區別?

3.爲什麼虛函數能實現動態調用和多態?


1.c和c++對比

c語言的數據塊和功能函數分開
  1. typedef struct point3d {  
  2.     float a;  
  3.     float b;  
  4.     float c;  
  5.   
  6. }point3d;  
  7.   
  8.   
  9. void printPoint3d(const point3d *pd){  
  10.     printf("a= %g,b= %g,c = %g",pd->a,pd->b,pd->c);  
  11. }  

使用時:新建point3d,調用打印函數,這兩部分可以天各一方的聲明定義,在使用時應用就行了,編譯之後有函數指令地址和數據塊地址。
  1. point3d p;  
  2.     p.a=4;  
  3.     p.b=5;  
  4.     p.c=6;  
  5.       
  6.     printPoint3d(&p);  

在c語言中我們要完成一個功能函數的調用首先要創建需要的參數,把參數傳人到函數中。

c++語言來實現:
  1. class point3d{  
  2. private:  
  3.     float a;  
  4.     float b;  
  5.     float c;  
  6. public:  
  7.     void printPoint3d(){  
  8.         printf("a= %g,b= %g,c = %g",a,b,c);  
  9.     }  
  10. };  

使用時:非虛函數的成員函數在編譯時就已經確定它的調用地址,而虛函數的調用是在運行時綁定。
  1. point3d *p=new point3d();  
  2.     p->printPoint3d();  

在c++中我們想要對數據塊進行操作只需要調用和它包裝工能函數就行了,成員變量(data member)可以映射爲struct數據塊,成員函數 (data function)可以映射爲打印功能函數。

通過這兩部分使用的比較我們知道編譯器肯定爲我門做了些什麼?爲什麼我們通過一個指針就能達到打印的效果了。

結合一些編譯的思想,簡單對比一下編譯運行之後的內存狀態:




通過該例子主要是想說明面向對象變成中編譯器爲我們做了很多東西,它能夠確定的它就幫我們完成,c到c++的封裝,如果沒有虛函數的話是不會增加數據的負擔的,c++經過編譯之後就是c的數據結構和函數而已,c++爲了實現多態,會增加數據成本。


2.this的由來

下面還會講到編譯器對this的處理,給我們添加虛函數列表,虛函數列表指針,爲我們生成默認構造函數。

在此先說一下靜態和非靜態的成員變量和函數:
類的靜態變量和靜態函數不屬於類的實例,它們屬於類,也就是它們的作用域是類而不是實例變量,不能通過this指針使用。
類的成員函數和成員變量屬於類的實例變量,可以通過this指針指定。


對於靜態和非靜態而言,編譯器主要就是添加this指針的出來,通過this指針調用成員函數時,編譯去會把this指針作爲參數傳遞到調用的函數,成員函數就通過this指針就知道它要處理的this的數據塊。


靜態函數和非靜態函數編譯之後都是函數,只是它的調用在編寫源代碼是被編譯器不同的對待,對它們的作用域進行了限制,從而達到封裝的效果,實現安全調用。

下面來看一個簡單類:
頭文件:
[objc] view plain copy
  1. #include <stdio.h>  
  2.   
  3. class point3d{  
  4. private:  
  5.     float a;  
  6.     float b;  
  7.     float c;  
  8. public:  
  9.     void printPoint3d();  
  10.       
  11. };  

實現文件:
  1. #include "Point3d.h"  
  2.   
  3. void point3d::printPoint3d(){  
  4.     this->a =1;  
  5.     this->b =2;  
  6.     this->c =3;  
  7.     printf("a= %g,b= %g,c = %g\n",this->a,this->b,this->c);  
  8. }  

使用:
  1. #include <iostream>  
  2. #include "Point3d.h"  
  3. int main(int argc, const char * argv[]) {  
  4.      
  5.     point3d *point = new point3d();  
  6.     point->printPoint3d();  
  7.       
  8.     return 0;  
  9. }  




運行結果:

a= 1,b= 2,c = 3

Program ended with exit code: 0


從上面的例子中我們可以看到成員變量中可以通過this指針使用成員變量。

下面我們來一步一步的分解改例子:

該類經過編譯之後就得到:



上面的類是沒有虛函數的所以它的對象的成員佈局爲:



3.虛函數

如果我們把printPoint3d聲明爲虛函數:
  1. class point3d{  
  2. private:  
  3.     float a;  
  4.     float b;  
  5.     float c;  
  6. public:  
  7.    virtual void printPoint3d();  
  8.       
  9. };  

這樣的話編譯器就會爲我們添加一個成員變量,而這個成員變量的初始化並不是我們給它初始化值的,而是編譯器在默認構造函數中給它初始化值的。


類中有虛函數的類編譯器又會把它編譯成什麼樣了:




那我們調用虛函數編譯器又會對我們進行編譯了:

  1. point->printPoint3d();  
當編譯器知道我們調用的是虛函數的時候,它會通過它添加的虛函數列表指針找到虛函數列表(編譯時確定的),在虛函數列表中找到調用的虛函數的入口地址,把point當作參數傳遞給虛函數。它和非虛函數的調用的不同是一個直接,一個間接調用,非虛函數的調用在編譯時就已經直接使用,虛函數的掉用是在動態的綁定調用,和爲動態,如果現在的point指針指向的是它的子類,那麼虛函數列表就是它的子類,這是調用的就不是point的printPoint3d函數了,而是它子類虛函數列表中的printPoint3d函數。這就找到爲什麼父類指針指向不同的子類對象,調用它的虛函數會去調用不同子類的虛函數,這也是多態的本質所在了。


4.多態

下面我們通過繼承point3d來實現這一案例:


class point3d.h
  1. class point3d{  
  2. public:  
  3.     float a;  
  4.     float b;  
  5.     float c;  
  6. public:  
  7.    virtual void printPoint3d();  
  8. };  

class point3d.m
  1. void point3d::printPoint3d(){  
  2.     this->a =1;  
  3.     this->b =2;  
  4.     this->c =3;  
  5.     printf("a= %g,b= %g,c = %g\n",this->a,this->b,this->c);  
  6. }  

class point4d.h
  1. #include <stdio.h>  
  2. #include "Point3d.h"  
  3.   
  4. class Point4d:public point3d{  
  5. private:  
  6.     float d;  
  7. public:  
  8.     void printPoint3d();  
  9. };  

class point4d.m
  1. void Point4d::printPoint3d(){  
  2.     this->a=1;  
  3.     this->b=2;  
  4.     this->c=3;  
  5.     this->d=4;  
  6.     printf("point 4d print :a= %g,b= %g,c = %g ,d= %g\n",this->a,this->b,this->c,this->d);  
  7. }  

使用:

  1. #include <iostream>  
  2. #include "Point3d.h"  
  3.   
  4. #include "Point4d.h"  
  5. int main(int argc, const char * argv[]) {  
  6.      
  7.     point3d *point = new point3d();  
  8.     point->printPoint3d();  
  9.       
  10.     point = new Point4d();  
  11.     point->printPoint3d();  
  12.     return 0;  
  13.       
  14.       
  15. }  

運行結果:

  1. a= 1,b= 2,c = 3  
  2. point 4d print :a= 1,b= 2,c = 3 ,d= 4  
  3. Program ended with exit code: 0  


同一個point指針,指向不同的對象是,調用同一個函數,會得到不同的結果,其中到底發生了什麼了,下圖分解:




小結:
這本說博大精深,還有很多細節沒有說出來,感興趣的可以去閱讀本書,這本書給我最大的就是對語言的理解能力有很大的提升,從c語言到面向對象的語言的過度的理解有很大的幫助,不足之處還忘見諒,謝謝。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章