• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            C++分析研究  
            C++
            日歷
            <2013年10月>
            293012345
            6789101112
            13141516171819
            20212223242526
            272829303112
            3456789
            統計
            • 隨筆 - 92
            • 文章 - 4
            • 評論 - 4
            • 引用 - 0

            導航

            常用鏈接

            留言簿

            隨筆檔案

            文章檔案

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

             
              我曾經自學過C++,現在回想起來,當時是什么都不懂。說不上能使用C++,倒是被C++牽著鼻子走了。高中搞NOIP并不允許使用STL庫,比賽中C++面向對象的機制基本沒有什么用武之地,所以高中搞NOIP名為用C++,其實就是c加上了cout和cin。
             
               前幾天看韓老師的《老碼識途》,里面記錄了一些C++面向對象機制的探索,又勾起了我的興趣。而這個學期自學了匯編,又給了我自己動手探索提供了能力基礎,自己上手以后,從一個更加底層的視角看C++機制的實現,讓我在黑暗中摸到了馴服C++的韁繩。
             
               引用:
             
               本質上是指針,這一點即使大家沒有看反匯編應該也是猜到了。
             
               對象在內存上的布局:
             
               1: class Father
             
               2: {
             
               3: int iA_;
             
               4: int iB_;
             
               5:
             
               6: void FuncA();
             
               7: void FuncB();
             
               8: };
             
               9:
             
               10: class Child : Father
             
               11: {
             
               12: int iC_;
             
               13: void FuncC();
             
               14: };
             
               一個Father對象里只包含 (低地址 –> 高地址) : iA_,iB_。也就是一個Father對象的大小是8個字節,函數并不會占用內存空間托福答案
             
               為什么不會?
             
               其實類的成員函數可以看做本質上與普通函數相同。
             
               編譯器在編譯的時候就知道函數的位置,所以調用普通函數的時候會直接 call 函數地址(偏移)。也就是被硬編碼了,函數的地址是固定的( 不考慮重定位之類的情況 )。
             
               而成員函數的調用也是如此,只是編譯器還多做了一件事情,就是判斷這個對象有沒有調用這個函數的“權限”(函數不是你聲明的,當然無權調用),“權限”不夠就會報錯,告訴那個對象類型沒有這個方法。
             
               所以,類對象的大小與這個類的方法數多少是沒關系的。成員函數和普通函數本質上一樣,實現這個機制,要靠編譯器來做工作雅思答案
             
               this指針:
             
               成員函數與普通函數不同之處之一就是訪問對象的數據。
             
               要訪問一個對象的元素,說白了就是要找到這個元素所在的內存位置,也就是要有指針。
             
               我們沒有看到傳遞this指針,因為這件事又是編譯器幫我們做了。
             
               反匯編會看到對象調用一個方法的時候,會將這個對象的首部地址賦值給ecx寄存器,通過寄存器來傳遞this指針。
             
               我們在成員函數里可以不需明寫this指針地調用對象元素,還是因為編譯器幫我們多做了一步“翻譯”。
             
               私有化:
             
               不多說,就是編譯器在編譯階段通過源碼來判斷某個元素是不是能夠被訪問,某個方法是不是能夠被調用,運行的時候并不會有訪問限制。看代碼:
             
               1: #include <stdio.h>
             
               2:
             
               3: class Exp
             
               4: {
             
               5: int iA_;
             
               6: int iB_;
             
               7:
             
               8: public:
             
               9: Exp()
             
               10: {
             
               11: iA_ = iB_ = 0;
             
               12: }
             
               13: void Out()
             
               14: {
             
               15: printf("%d \t %d \n",iA_,iB_);
             
               16: }
             
               17: };
             
               18:
             
               19: int main()
             
               20: {
             
               21: Exp oA;
             
               22: void *pC = &oA;
             
               23:
             
               24: oA.Out();
             
               25: *(int*)pC = 1;
             
               26: *(int*)((int)pC+4) = 2;
             
               27: oA.Out();
             
               28:
             
               29: return 0;
             
               30: }
             
               結果是: 0 0
             
               1 2
             
               雖然 iA_,iB_是私有的,但是還是被外界修改了。因為編譯器無法知道我干了這事(顯式的 oA.iA_ = 1 就被發現了哈)
             
               構造與析構:
             
               說道底還是編譯器幫我們在多做了一些工作,生成了一些額外代碼。
             
               需要注意的是:
             
               1: void Test( Father oP )
             
               2: {
             
               3: }
             
               4:
             
               5: int main()
             
               6: {
             
               7: Father oA;
             
               8: Test(oA);
             
               9: return 0;
             
               10: }
             
               會調用拷貝構造函數。
             
               重載:
             
               一樣還是編譯器的功勞,C++最后生成的函數名是與參數有關的,所以又不同參數的函數最后生成的函數名不同,看似同名,實則不同。在函數調用的時候,編譯器會判斷參數的類型,相應的可以生成一個函數名進行“匹配”。( 當然不止這么簡單,還會考慮發生類型轉換的情況 )
             
               繼承:
             
               從內存布局的角度上看
             
               1: struct Child : Father
             
               和
             
               1: struct Child
             
               2: {
             
               3: Father o;
             
               4: //other
             
               5: };
             
               相同(虛函數情況后面討論)。子類的前面部分和父類是一樣的。
             
               所以一個接受 Father * 參數的函數可以接受 Child *參數,而且轉換是安全的北美托福答案
             
               有 Father & 類型參數的函數可以接受 Child &,但是繼承方式要public。But , why ?
             
               protected和private繼承模式,子類繼承的父類的接口對外都是隱藏的,所以以一個Father &傳入的參數所有的方法元素原則上是不可用的,用了肯定是違反規則的,編譯器判定這一點,所以報錯。
             
               虛函數:
             
               比較特別的是這個。
             
               Question:為什么需要虛函數?
             
               網上看到的答案:基類可以通過虛函數對子類的相識功能進行管理。(我的C++primer被借走以后就此失蹤,所以只能網上找了)。
             
               虛函數具體怎么回事就不細說了,討論一下背后的機制。
             
               為了能夠實現虛函數,每個有虛函數的類有一張對應的虛表。這個虛表儲存在只讀內存區,記錄了對應函數的地址。(PS:一個類就只有一個虛表)
             
               每個類對象都要保存一個虛表指針,保存本類的虛表地址。所以你使用 Father *指針指向一個Child對象,調用的虛函數是Child的。
             
               虛表指針保存在每個對象的首部。
             
               1: class Child : Father
             
               2: {
             
               3: int iC_;
             
               4: void FuncC();
             
               5: virtual void VF();
             
               6: };
             
               現在這個Child對象較前面的多了四個字節。內存布局(從低地址到高地址)是:虛表指針__vfptr,iA_,iB_,iC_。
             
               好。問題來了,Child繼承了Father,但是Father的函數并沒有為Child再量身定做一次,也就是說無論是Father對象還是Child對象,他們調用FuncA()都是同一個函數。但是Father并沒有__vfptr,Child對象在頭部多了這個,FuncA()中用this指針定位iA_和iB_不是都不正確嗎?
             
               現象告訴我們FuncA()是可以正確訪問iA_和iB_,所以推測Child對象在調用FuncA的時候,傳的不是真正的首部地址,而是往后偏移了四個字節托福改分
             
               反匯編,確實如此。這么說Father類里不能調用虛函數了?當然,Father都還不知道虛函數這回事,怎么在FuncA中調用。
             
               還有一個有趣的現象:
             
               1: #include <stdio.h>
             
               2:
             
               3: class Base
             
               4: {
             
               5: public:
             
               6: virtual void ShowID()
             
               7: {
             
               8: printf("Base\n");
             
               9: }
             
               10: };
             
               11:
             
               12: class CB : public Base
             
               13: {
             
               14: public:
             
               15: virtual void ShowID()
             
               16: {
             
               17: printf("CB\n");
             
               18: }
             
               19: };
             
               20:
             
               21: class CC : public Base
             
               22: {
             
               23: public:
             
               24: virtual void ShowID()
             
               25: {
             
               26: printf("CC\n");
             
               27: }
             
               28: };
             
               29:
             
               30: void Test( CB& oB )
             
               31: {
             
               32: oB.ShowID();
             
               33: }
             
               34:
             
               35: int main()
             
               36: {
             
               37: Base oBase;
             
               38: CB oB;
             
               39: CC oC;
             
               40:
             
               41: CB* pCB = &oB;
             
               42:
             
               43: *(int*)(&oB) = *(int*)(&oC); //修改虛表指針
             
               44: oB.ShowID();
             
               45: ((CB*)(&oB))->ShowID();
             
               46: pCB->ShowID();
             
               47: Test(oB);
             
               48:
             
               49: return 0;
             
               50: }
             
               猜猜結果啊,買定離手。
             
               結果是:CB CB CC CC
             
               在43行的地方,修改了oB的虛表指針,讓其指向CC類的虛表。
             
               但是oB.ShowID()沒理會我們的修改,還是調用CB類的ShowID。反匯編,發現他沒走“獲取虛表指針,在虛表中得到相應的函數地址”這一套,直接調用了。因為一般人不會閑著蛋疼去改對象的虛表指針的,對象的類型是明確的,編譯器可以通過這些信息確定調用的函數地址,所以沒必要走他一套,這樣效率還更高托福答案
             
               而pCB->ShowID()就不同了,他很乖地地走了流程,因為一個父類指針可以指向一個子類對象,編譯器無法找信息,所以走流程托福改分
             
               那現在糾結了,為神馬 ((CB*)(&oB))->ShowID() 輸出CB。
             
               反匯編看,發現編譯器又擅自做主,沒有走指針的流程。
             
               那你猜猜((Base*)(&oB))->ShowID();輸出的是什么?CC。
             
               比較二者的差異,可以大概發現一些端倪,什么時候走流程,什么時候不走。
             
               最后是Test(oB)了,前面說過引用的本質是指針,所以這個結果很好理解。
             
               還有,想過
             
               1: void Test2( Base oP )
             
               2: {
             
               3: oP.ShowID();
             
               4: }
             
               拷貝的時候有沒有拷貝虛表指針嗎?試試就知道,厄…發現沒有。
             
               前面說過這樣會調用拷貝構造函數,但是你在這個函數你沒有寫虛表指針的賦值。但是邪惡的編譯器已經幫你悄悄加上去了哈哈哈哈~。(唉?節操呢)
             
               RTTI
             
               每個類有特定的虛表地址,每個對象會保存這個虛表地址,應該想到了吧,偷懶,不寫了。
             
               綜上。可以看到,面向對象機制在底層并不特別,機制的實現主要靠的是編譯器。
             
            posted on 2013-04-16 11:42 HAOSOLA 閱讀(307) 評論(0)  編輯 收藏 引用
             
            Copyright © HAOSOLA Powered by: 博客園 模板提供:滬江博客
            PK10開獎 PK10開獎
            国产ww久久久久久久久久| 久久精品无码一区二区三区| 久久人妻少妇嫩草AV蜜桃| 欧美大战日韩91综合一区婷婷久久青草| 区亚洲欧美一级久久精品亚洲精品成人网久久久久 | 久久久久国产亚洲AV麻豆| 天天影视色香欲综合久久| 久久国产精品99国产精| 久久久久这里只有精品| AV无码久久久久不卡蜜桃| 无码人妻久久一区二区三区蜜桃| 久久婷婷国产综合精品| 亚洲精品无码久久毛片| 国产精品久久久久天天影视| 色欲综合久久躁天天躁| 欧美精品一本久久男人的天堂| 久久强奷乱码老熟女网站| 亚洲国产精品久久久久网站| 亚洲第一极品精品无码久久| 国产精品日韩欧美久久综合| 久久精品午夜一区二区福利| 久久久久99这里有精品10 | 精品久久久久久中文字幕人妻最新 | 久久人妻少妇嫩草AV无码专区| 久久99国产一区二区三区| 国产午夜精品理论片久久影视| 亚洲中文久久精品无码| 久久久久青草线蕉综合超碰| 久久本道久久综合伊人| 精品国产VA久久久久久久冰| 欧美成a人片免费看久久| 99久久国产精品免费一区二区| 久久久久亚洲AV无码去区首| 2021国产成人精品久久| 嫩草影院久久99| 亚洲乱亚洲乱淫久久| 国产叼嘿久久精品久久| 久久精品不卡| 2021国产精品久久精品| 亚洲色大成网站www久久九| 浪潮AV色综合久久天堂|