<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 第31章 類 ## 31.1 **簡單的例子** 在程序內部,C++類的表示基本和結構體一樣。 讓我們試試這個有2個變量,2個構造函數和1個方法的類。 ``` #include <stdio.h> class c { private: int v1; int v2; public: c() // default ctor { v1=667; v2=999; }; c(int a, int b) // ctor { v1=a; v2=b; }; void dump() { printf ("%d; %d ", v1, v2); }; }; int main() { class c c1; class c c2(5,6); c1.dump(); c2.dump(); return 0; }; ``` **31.1.1 MSVC-X86** 這里可以看到main()函數是如何被翻譯成匯編代碼的: ``` _c2$ = -16 ; size = 8 _c1$ = -8 ; size = 8 _main PROC push ebp mov ebp, esp sub esp, 16 ; 00000010H lea ecx, DWORD PTR _c1$[ebp] call ??0c@@QAE@XZ ; c::c push 6 push 5 lea ecx, DWORD PTR _c2$[ebp] call ??0c@@QAE@HH@Z ; c::c lea ecx, DWORD PTR _c1$[ebp] call ?dump@c@@QAEXXZ ; c::dump lea ecx, DWORD PTR _c2$[ebp] call ?dump@c@@QAEXXZ ; c::dump xor eax, eax mov esp, ebp pop ebp ret 0 _main ENDP ``` 所以,發生什么了。對每個對象來說(而不是類c),會分配8個字節。這正好是2個變量存儲所需的大小。 對c1來說一個默認的無參數構造函數??0c@@QAE@XZ會被調用。對c2來說另一個??0c@@QAE@HH@Z會被調用,有兩個數字會被作為參數傳遞。 指向對象的指針(c++術語的“this”)會被通過ECX寄存器傳遞。這被叫做thiscall(31.1.1)--這是一個指向對象的指針傳遞方式。 MSVC使用ECX來傳遞它。無需說明的是,它并不是一個標準化的方法,其他編譯器可能用其他方法,例如通過第一個函數參數,比如GCC就是這么做的。 為什么函數的名字這么奇怪?這是因為名字打碎方式的緣故。 C++類可能有多個同名的重載函數,因此,不同的類也可能有相同的函數名。 名字打碎可以把類的類名+函數名+參數類型編碼到一個字符串里面,然后它就會被用作內部名稱。這完全是因為編譯器和DLL OS加載器都 不知道C++或者面向對象的緣故。 Dump()函數在之后被調用了2次。 讓我們看看構造函數的代碼。 ``` _this$ = -4 ; size = 4 ??0c@@QAE@XZ PROC ; c::c, COMDAT ; _this$ = ecx push ebp mov ebp, esp push ecx mov DWORD PTR _this$[ebp], ecx mov eax, DWORD PTR _this$[ebp] mov DWORD PTR [eax], 667 ; 0000029bH mov ecx, DWORD PTR _this$[ebp] mov DWORD PTR [ecx+4], 999 ; 000003e7H mov eax, DWORD PTR _this$[ebp] mov esp, ebp pop ebp ret 0 ??0c@@QAE@XZ ENDP ; c::c _this$ = -4 ; size = 4 _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 ??0c@@QAE@HH@Z PROC ; c::c, COMDAT ; _this$ = ecx push ebp mov ebp, esp push ecx mov DWORD PTR _this$[ebp], ecx mov eax, DWORD PTR _this$[ebp] mov ecx, DWORD PTR _a$[ebp] mov DWORD PTR [eax], ecx mov edx, DWORD PTR _this$[ebp] mov eax, DWORD PTR _b$[ebp] mov DWORD PTR [edx+4], eax mov eax, DWORD PTR _this$[ebp] mov esp, ebp pop ebp ret 8 ??0c@@QAE@HH@Z ENDP ; c::c ``` 構造函數只是函數,它們會使用ECX中存儲的指向結構體的指針,然后把指針指向自己的本地變量,但是,這個操作并不是必須的。 對C++標準來說我們知道構造函數不應該返回任何值。事實上,構造函數會返回指向新創建對象的指針,比如“this”。 現在看看dump()函數: ``` _this$ = -4 ; size = 4 ?dump@c@@QAEXXZ PROC ; c::dump, COMDAT ; _this$ = ecx push ebp mov ebp, esp push ecx mov DWORD PTR _this$[ebp], ecx mov eax, DWORD PTR _this$[ebp] mov ecx, DWORD PTR [eax+4] push ecx mov edx, DWORD PTR _this$[ebp] mov eax, DWORD PTR [edx] push eax push OFFSET ??_C@_07NJBDCIEC@?$CFd?$DL?5?$CFd?6?$AA@ call _printf add esp, 12 ; 0000000cH mov esp, ebp pop ebp ret 0 ?dump@c@@QAEXXZ ENDP ; c::dump ``` 簡單的可以:dump()會把帶有2個int的結構體傳給ecx,然后從他里面取出2個值,然后傳給printf()。 如果使用/Ox優化,代碼會更短。 ``` ??0c@@QAE@XZ PROC ; c::c, COMDAT ; _this$ = ecx mov eax, ecx mov DWORD PTR [eax], 667 ; 0000029bH mov DWORD PTR [eax+4], 999 ; 000003e7H ret 0 ??0c@@QAE@XZ ENDP ; c::c _a$ = 8 ; size = 4 _b$ = 12 ; size = 4 ??0c@@QAE@HH@Z PROC ; c::c, COMDAT ; _this$ = ecx mov edx, DWORD PTR _b$[esp-4] mov eax, ecx mov ecx, DWORD PTR _a$[esp-4] mov DWORD PTR [eax], ecx mov DWORD PTR [eax+4], edx ret 8 ??0c@@QAE@HH@Z ENDP ; c::c ?dump@c@@QAEXXZ PROC ; c::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+4] mov ecx, DWORD PTR [ecx] push eax push ecx push OFFSET ??_C@_07NJBDCIEC@?$CFd?$DL?5?$CFd?6?$AA@ call _printf add esp, 12 ; 0000000cH ret 0 ?dump@c@@QAEXXZ ENDP ; c::dump ``` 還要說的就是棧指針在調用add esp ,x之后并不正確。所以構造函數還需要ret 8來返回,而不是ret。 這是因為這兒調用方式是thiscall(31.1.1),這個方法會使用棧來傳遞參數,和stdcall對比(47.2)來看,他將為被調用者維護正確的棧,而不是調用者。Ret x指令會額外的給esp加上x,然后會把控制流交還給調用者函數。 調用轉換見47章。 還有需要注意的是,編譯器會決定什么時候調用構造函數什么時候調用析構函數,但是我們從c++語言基礎里面已經知道調用時機了。 **31.1.2 MSVC-x86-64** 像我們已經知道的那樣,x86-64中前4個函數參數是通過RCX/RDX/R8/R9寄存器傳遞的,剩余的通過棧傳遞。但是this是用RCX傳遞的 ,而第一個函數參數是從RDX開始傳遞的。我們可以通過c(int a, int b)這個函數看出來。 ``` ; void dump() ?dump@c@@QEAAXXZ PROC ; c::dump mov r8d, DWORD PTR [rcx+4] mov edx, DWORD PTR [rcx] lea rcx, OFFSET FLAT:??_C@_07NJBDCIEC@?$CFd?$DL?5?$CFd?6?$AA@ ; ’%d; %d’ jmp printf ?dump@c@@QEAAXXZ ENDP ; c::dump ; c(int a, int b) ??0c@@QEAA@HH@Z PROC ; c::c mov DWORD PTR [rcx], edx ; 1st argument: a mov DWORD PTR [rcx+4], r8d ; 2nd argument: b mov rax, rcx ret 0 ??0c@@QEAA@HH@Z ENDP ; c::c ; default ctor ??0c@@QEAA@XZ PROC ; c::c mov DWORD PTR [rcx], 667 ; 0000029bH mov DWORD PTR [rcx+4], 999 ; 000003e7H mov rax, rcx ret 0 ??0c@@QEAA@XZ ENDP ; c::c ``` X64中,Int數據類型依然是32位的。所以這里也使用了32位寄存器部分。 我們還可以看到dump()里的JMP printf,而不是RET,這個技巧我們已經在11.1.1里面見過了。 **31.1.3 GCC-x86** 幾乎和GCC4.4.1一樣的結果,除了幾個例外。 ``` public main main proc near ; DATA XREF: _start+17 var_20 = dword ptr -20h var_1C = dword ptr -1Ch var_18 = dword ptr -18h var_10 = dword ptr -10h var_8 = dword ptr -8 push ebp mov ebp, esp and esp, 0FFFFFFF0h sub esp, 20h lea eax, [esp+20h+var_8] mov [esp+20h+var_20], eax call _ZN1cC1Ev mov [esp+20h+var_18], 6 mov [esp+20h+var_1C], 5 lea eax, [esp+20h+var_10] mov [esp+20h+var_20], eax call _ZN1cC1Eii lea eax, [esp+20h+var_8] mov [esp+20h+var_20], eax call _ZN1c4dumpEv lea eax, [esp+20h+var_10] mov [esp+20h+var_20], eax call _ZN1c4dumpEv mov eax, 0 leave retn main endp ``` 我們可以看到另一個命名破碎模式,這個GNU特殊的模式可以看到指向對象的this時針其實是作為函數的第一個參數被傳入的,當然,這個對程序員來說是透明的。 第一個構造函數: ``` public _ZN1cC1Ev ; weak _ZN1cC1Ev proc near ; CODE XREF: main+10 arg_0 = dword ptr 8 push ebp mov ebp, esp mov eax, [ebp+arg_0] mov dword ptr [eax], 667 mov eax, [ebp+arg_0] mov dword ptr [eax+4], 999 pop ebp retn _ZN1cC1Ev endp ``` 他所做的無非就是使用第一個傳來的參數寫入兩個數字。 第二個構造函數: ``` public _ZN1cC1Eii _ZN1cC1Eii proc near arg_0 = dword ptr 8 arg_4 = dword ptr 0Ch arg_8 = dword ptr 10h push ebp mov ebp, esp mov eax, [ebp+arg_0] mov edx, [ebp+arg_4] mov [eax], edx mov eax, [ebp+arg_0] mov edx, [ebp+arg_8] mov [eax+4], edx pop ebp retn _ZN1cC1Eii endp ``` 這是個函數,原型類似于: ``` void ZN1cC1Eii (int *obj, int a, int b) { *obj=a; *(obj+1)=b; }; ``` 這是完全可以預測到的,現在看看dump(): ``` public _ZN1c4dumpEv _ZN1c4dumpEv proc near var_18 = dword ptr -18h var_14 = dword ptr -14h var_10 = dword ptr -10h arg_0 = dword ptr 8 push ebp mov ebp, esp sub esp, 18h mov eax, [ebp+arg_0] mov edx, [eax+4] mov eax, [ebp+arg_0] mov eax, [eax] mov [esp+18h+var_10], edx mov [esp+18h+var_14], eax mov [esp+18h+var_18], offset aDD ; "%d; %d " call _printf leave retn _ZN1c4dumpEv endp ``` 在這個函數的內部表達中有一個單獨的參數,被用作指向當前對象,也即this。 因此,如果從這些簡單的例子來看,MSVC和GCC的區別也就只有函數名編碼的區別和傳入this指針的區別(ECX寄存器或通過第一個參數)。 **31.1.14 GCC-X86-64** 前6個參數,會通過RDI/RSI/RDX/RCX/R8/R9[21章]的順序傳遞,this指針會通過第一個RDI來傳遞,我們可以接著看到。 Int數據類型也是一個32位的數據,JMP替換RET的技巧這里也用到了。 ``` ; default ctor _ZN1cC2Ev: mov DWORD PTR [rdi], 667 mov DWORD PTR [rdi+4], 999 ret ; c(int a, int b) _ZN1cC2Eii: mov DWORD PTR [rdi], esi mov DWORD PTR [rdi+4], edx ret ; dump() _ZN1c4dumpEv: mov edx, DWORD PTR [rdi+4] mov esi, DWORD PTR [rdi] xor eax, eax mov edi, OFFSET FLAT:.LC0 ; "%d; %d " jmp printf ``` ## 31.2 類繼承 可以說關于類繼承就是我們已經研究了的這個結構體,但是它現在擴展成類了。 讓我們看個簡單的例子: ``` #include <stdio.h> class object { public: int color; object() { }; object (int color) { this->color=color; }; void print_color() { printf ("color=%d ", color); }; }; class box : public object { private: int width, height, depth; public: box(int color, int width, int height, int depth) { this->color=color; this->width=width; this->height=height; this->depth=depth; }; void dump() { printf ("this is box. color=%d, width=%d, height=%d, depth=%d ", color, width, height, depth); }; }; class sphere : public object { private: int radius; public: sphere(int color, int radius) { this->color=color; this->radius=radius; }; void dump() { printf ("this is sphere. color=%d, radius=%d ", color, radius); }; }; int main() { box b(1, 10, 20, 30); sphere s(2, 40); b.print_color(); s.print_color(); b.dump(); s.dump(); return 0; }; ``` 讓我們觀察一下生成的dump()的代碼和object::print_color(),讓我們看看結構體對象的內存輸出(作為32位代碼) 所以,dump()方法其實是對應了好幾個類,下面代碼由MSVC 2008生成(/Ox+/Ob0) 優化的MSVC 2008 /Ob0 ``` ??_C@_09GCEDOLPA@color?$DN?$CFd?6?$AA@ DB ’color=%d’, 0aH, 00H ; ‘string’ ?print_color@object@@QAEXXZ PROC ; object::print_color, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx] push eax ; ’color=%d’, 0aH, 00H push OFFSET ??_C@_09GCEDOLPA@color?$DN?$CFd?6?$AA@ call _printf add esp, 8 ret 0 ?print_color@object@@QAEXXZ ENDP ; object::print_color 優化的MSVC2008 /Ob0 ?dump@box@@QAEXXZ PROC ; box::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+12] mov edx, DWORD PTR [ecx+8] push eax mov eax, DWORD PTR [ecx+4] mov ecx, DWORD PTR [ecx] push edx push eax push ecx ; ’this is box. color=%d, width=%d, height=%d, depth=%d’, 0aH, 00H ; ‘string’ push OFFSET ??_C@_0DG@NCNGAADL@this?5is?5box?4?5color?$DN?$CFd?0?5width?$DN?$CFd?0@ call _printf add esp, 20 ; 00000014H ret 0 ?dump@box@@QAEXXZ ENDP ; box::dump ?dump@sphere@@QAEXXZ PROC ; sphere::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+4] mov ecx, DWORD PTR [ecx] push eax push ecx ; ’this is sphere. color=%d, radius=%d’, 0aH, 00H push OFFSET ??_C@_0CF@EFEDJLDC@this?5is?5sphere?4?5color?$DN?$CFd?0?5radius@ call _printf add esp, 12 ; 0000000cH ret 0 ?dump@sphere@@QAEXXZ ENDP ; sphere::dump ``` 所以,這就是他的內存暑促后: (基類對象) ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec5260abb.png) 繼承的對象 Box: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec526f7ef.png) Sphere: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec527a9d4.png) 讓我們看看main()函數體: ``` PUBLIC _main _TEXT SEGMENT _s$ = -24 ; size = 8 _b$ = -16 ; size = 16 _main PROC sub esp, 24 ; 00000018H push 30 ; 0000001eH push 20 ; 00000014H push 10 ; 0000000aH push 1 lea ecx, DWORD PTR _b$[esp+40] call ??0box@@QAE@HHHH@Z ; box::box push 40 ; 00000028H push 2 lea ecx, DWORD PTR _s$[esp+32] call ??0sphere@@QAE@HH@Z ; sphere::sphere lea ecx, DWORD PTR _b$[esp+24] call ?print_color@object@@QAEXXZ ; object::print_color lea ecx, DWORD PTR _s$[esp+24] call ?print_color@object@@QAEXXZ ; object::print_color lea ecx, DWORD PTR _b$[esp+24] call ?dump@box@@QAEXXZ ; box::dump lea ecx, DWORD PTR _s$[esp+24] call ?dump@sphere@@QAEXXZ ; sphere::dump xor eax, eax add esp, 24 ; 00000018H ret 0 _main ENDP ``` 繼承的類必須永遠將它們的范圍添加到基類的范圍中,所以這樣可以讓基類的方法對其范圍生效。 當object::print_color()方法被調用時,會有一個指針指向box對象和sphere對象會被傳遞進去,它就是“this”。它可以和這些對象簡單的互動,因為color域指向的永遠是固定的地址(+0x00偏移)。 可以說,object::print_color()方法對于輸入對象類型來說是不可知的,如果你創建一個繼承類,例如繼承了box類編譯器會自動在depth域之后加上新域,而把box的類域固定在一個固定的位置。 因此,box::dump()方法會在訪問color/width/height/depths的時候順利工作,因為地址的固定,它會很容易的知道偏移。 GCC生成的代碼基本一樣,只有一個不一樣的就是this的傳遞,就像之前說的一樣,它是作為第一個參數傳遞的,而不是通過ECX傳遞的。 ## 31.3 封裝 封裝是一個把數據裝在類的private域里面的動作,這樣會讓它們只能從類的內部被訪問到,而從外面訪問不到。 但是,生成的代碼里面是否有什么東西指示一個變量是private呢? 沒有,讓我們看看簡單的例子: ``` #include <stdio.h> class box { private: int color, width, height, depth; public: box(int color, int width, int height, int depth) { this->color=color; this->width=width; this->height=height; this->depth=depth; }; void dump() { printf ("this is box. color=%d, width=%d, height=%d, depth=%d ", color, width, height, depth); }; }; ``` 在MSVC 2008+/Ox和/Ob0選項,然后看看box::dump()代碼: ``` ?dump@box@@QAEXXZ PROC ; box::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+12] mov edx, DWORD PTR [ecx+8] push eax mov eax, DWORD PTR [ecx+4] mov ecx, DWORD PTR [ecx] push edx push eax push ecx ; ’this is box. color=%d, width=%d, height=%d, depth=%d’, 0aH, 00H push OFFSET ??_C@_0DG@NCNGAADL@this?5is?5box?4?5color?$DN?$CFd?0?5width?$DN?$CFd?0@ call _printf add esp, 20 ; 00000014H ret 0 ?dump@box@@QAEXXZ ENDP ; box::dump ``` 這就是類的內存分布: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec528d8a2.png) 所有域都不允許其他類的訪問,但是,我們知道這個存放方式之后是否可以修改這些域? 所以我加了hack_oop_encapsulation()函數,假設他有這個代碼,當然我們沒有編譯: ``` void hack_oop_encapsulation(class box * o) { o->width=1; // that code can’t be compiled: "error C2248: ’box::width’ : cannot access private member declared in class ’box’" }; ``` 還有,如果要轉換box的類型,把它從指針轉為int數組,然后如果我們能修改這些數字,那么我們就成功了。 ``` void hack_oop_encapsulation(class box * o) { unsigned int *ptr_to_object=reinterpret_cast<unsigned int*>(o); ptr_to_object[1]=123; }; ``` 這個函數的代碼非常簡單,剋說函數指示把指針指向這些int,然后把123寫入第二個int: ``` ?hack_oop_encapsulation@@YAXPAVbox@@@Z PROC ; hack_oop_encapsulation mov eax, DWORD PTR _o$[esp-4] mov DWORD PTR [eax+4], 123 ; 0000007bH ret 0 ?hack_oop_encapsulation@@YAXPAVbox@@@Z ENDP ; hack_oop_encapsulation ``` 看看它是怎么工作的: ``` int main() { box b(1, 10, 20, 30); b.dump(); hack_oop_encapsulation(&b); b.dump(); return 0; }; ``` 運行后: ``` this is box. color=1, width=10, height=20, depth=30 this is box. color=1, width=123, height=20, depth=30 ``` 可以看到,private只是在編譯階段被保護了,c++編譯器不會允許其他代碼修改private域下的內容,但是如果用一些技巧,就可以修改private的值。 ## 31.4 多重繼承 多重繼承是一個類的創建,這個類會從2個或多個類里面繼承函數和成員。 看一個簡單的例子: ``` #include <stdio.h> class box { public: int width, height, depth; box() { }; box(int width, int height, int depth) { this->width=width; this->height=height; this->depth=depth; }; void dump() { printf ("this is box. width=%d, height=%d, depth=%d ", width, height, depth); }; int get_volume() { return width * height * depth; }; }; class solid_object { public: int density; solid_object() { }; solid_object(int density) { this->density=density; }; int get_density() { return density; }; void dump() { printf ("this is solid_object. density=%d ", density); }; }; class solid_box: box, solid_object { public: solid_box (int width, int height, int depth, int density) { this->width=width; this->height=height; this->depth=depth; this->density=density; }; void dump() { printf ("this is solid_box. width=%d, height=%d, depth=%d, density=%d ", width, height, depth, density); }; int get_weight() { return get_volume() * get_density(); }; }; int main() { box b(10, 20, 30); solid_object so(100); solid_box sb(10, 20, 30, 3); b.dump(); so.dump(); sb.dump(); printf ("%d ", sb.get_weight()); return 0; }; ``` 讓我們在MSVC 2008中用/Ox和/Ob0選項來編譯,然后看看box::dump()、solid_object::dump()和solid_box::dump()的函數代碼: ``` ?dump@box@@QAEXXZ PROC ; box::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+8] mov edx, DWORD PTR [ecx+4] push eax mov eax, DWORD PTR [ecx] push edx push eax ; ’this is box. width=%d, height=%d, depth=%d’, 0aH, 00H push OFFSET ??_C@_0CM@DIKPHDFI@this?5is?5box?4?5width?$DN?$CFd?0?5height?$DN?$CFd@ call _printf add esp, 16 ; 00000010H ret 0 ?dump@box@@QAEXXZ ENDP ; box::dump ?dump@solid_object@@QAEXXZ PROC ; solid_object::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx] push eax ; ’this is solid_object. density=%d’, 0aH push OFFSET ??_C@_0CC@KICFJINL@this?5is?5solid_object?4?5density?$DN?$CFd@ call _printf add esp, 8 ret 0 ?dump@solid_object@@QAEXXZ ENDP ; solid_object::dump ?dump@solid_box@@QAEXXZ PROC ; solid_box::dump, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+12] mov edx, DWORD PTR [ecx+8] push eax mov eax, DWORD PTR [ecx+4] mov ecx, DWORD PTR [ecx] push edx push eax push ecx ; ’this is solid_box. width=%d, height=%d, depth=%d, density=%d’, 0aH push OFFSET ??_C@_0DO@HNCNIHNN@this?5is?5solid_box?4?5width?$DN?$CFd?0?5hei@ call _printf add esp, 20 ; 00000014H ret 0 ?dump@solid_box@@QAEXXZ ENDP ; solid_box::dump ``` 所以,這三個類的內存分布是: Box: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec529a9c8.png) Solid_object: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec52a642c.png) 可以說,solid_box的類內存空間就是它們的組合: ![enter image description here](https://box.kancloud.cn/2015-12-28_5680ec52b251c.png) Box::get_volume()和solid_object::get_density()函數的代碼如下: ``` ?get_volume@box@@QAEHXZ PROC ; box::get_volume, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx+8] imul eax, DWORD PTR [ecx+4] imul eax, DWORD PTR [ecx] ret 0 ?get_volume@box@@QAEHXZ ENDP ; box::get_volume ?get_density@solid_object@@QAEHXZ PROC ; solid_object::get_density, COMDAT ; _this$ = ecx mov eax, DWORD PTR [ecx] ret 0 ?get_density@solid_object@@QAEHXZ ENDP ; solid_object::get_density ``` 但是solid_box::get_weight()的代碼更有趣: ``` ?get_weight@solid_box@@QAEHXZ PROC ; solid_box::get_weight, COMDAT ; _this$ = ecx push esi mov esi, ecx push edi lea ecx, DWORD PTR [esi+12] call ?get_density@solid_object@@QAEHXZ ; solid_object::get_density mov ecx, esi mov edi, eax call ?get_volume@box@@QAEHXZ ; box::get_volume imul eax, edi pop edi pop esi ret 0 ?get_weight@solid_box@@QAEHXZ ENDP ; solid_box::get_weight ``` Get_weight()函數只會調用2個函數,但是對于get_volume()來說,他只是傳遞指針給this,對get_density()來說,他指示傳遞指針給this,同時移位12(0xC)字節,然后在solid_box類的內存空間理,solid_object類開始了。 因此,solid_object::get_density()方法相信它正在處理普通的solid_object類,而且box::get_volume類將對它的3個域生效,而且相信這是普通的box類對象。 因此,我們可以說,類的一個對象,是從多個其他類繼承阿日來,在內存中代表著組合起來的類,因為它有所有繼承來的域。每個繼承的方法都會又一個指向對應結構部分的指針來處理。 ## 31.5 虛函數 還有一個簡單的例子: ``` #include <stdio.h> class object { public: int color; object() { }; object (int color) { this->color=color; }; virtual void dump() { printf ("color=%d ", color); }; }; class box : public object { private: int width, height, depth; public: box(int color, int width, int height, int depth) { this->color=color; this->width=width; this->height=height; this->depth=depth; }; void dump() { printf ("this is box. color=%d, width=%d, height=%d, depth=%d ", color, width, height, depth); }; }; class sphere : public object { private: int radius; public: sphere(int color, int radius) { this->color=color; this->radius=radius; }; void dump() { printf ("this is sphere. color=%d, radius=%d ", color, radius); }; }; int main() { box b(1, 10, 20, 30); sphere s(2, 40); object *o1=&b; object *o2=&s; o1->dump(); o2->dump(); return 0; }; ``` 類object有一個虛函數dump(),被box和sphere類繼承者替換。 如果在一個并不知道什么類型是什么對象的環境下,就像在main()這個函數里面一樣,當一個虛函數dump()被調用的時候,我們還是需要知道它的返回類型的。 讓我們在MSVC2008用/Ox 、 /Ob0編譯看看main()的函數代碼: ``` _s$ = -32 ; size = 12 _b$ = -20 ; size = 20 _main PROC sub esp, 32 ; 00000020H push 30 ; 0000001eH push 20 ; 00000014H push 10 ; 0000000aH push 1 lea ecx, DWORD PTR _b$[esp+48] call ??0box@@QAE@HHHH@Z ; box::box push 40 ; 00000028H push 2 lea ecx, DWORD PTR _s$[esp+40] call ??0sphere@@QAE@HH@Z ; sphere::sphere mov eax, DWORD PTR _b$[esp+32] mov edx, DWORD PTR [eax] lea ecx, DWORD PTR _b$[esp+32] call edx mov eax, DWORD PTR _s$[esp+32] mov edx, DWORD PTR [eax] lea ecx, DWORD PTR _s$[esp+32] call edx xor eax, eax add esp, 32 ; 00000020H ret 0 _main ENDP ``` 指向dump()函數的指針在這個對象的某處被使用了,那么新函數的地址寫到了哪里呢?只有在構造函數中有可能:其他地方都不會被main()調用。 看看類構造函數的代碼: ``` ??_R0?AVbox@@@8 DD FLAT:??_7type_info@@6B@ ; box ‘RTTI Type Descriptor’ DD 00H DB ’.?AVbox@@’, 00H ??_R1A@?0A@EA@box@@8 DD FLAT:??_R0?AVbox@@@8 ; box::‘RTTI Base Class Descriptor at (0,-1,0,64)’ DD 01H DD 00H DD 0ffffffffH DD 00H DD 040H DD FLAT:??_R3box@@8 ??_R2box@@8 DD FLAT:??_R1A@?0A@EA@box@@8 ; box::‘RTTI Base Class Array’ DD FLAT:??_R1A@?0A@EA@object@@8 ??_R3box@@8 DD 00H ; box::‘RTTI Class Hierarchy Descriptor’ DD 00H DD 02H DD FLAT:??_R2box@@8 ??_R4box@@6B@ DD 00H ; box::‘RTTI Complete Object Locator’ DD 00H DD 00H DD FLAT:??_R0?AVbox@@@8 DD FLAT:??_R3box@@8 ??_7box@@6B@ DD FLAT:??_R4box@@6B@ ; box::‘vftable’ DD FLAT:?dump@box@@UAEXXZ _color$ = 8 ; size = 4 _width$ = 12 ; size = 4 _height$ = 16 ; size = 4 _depth$ = 20 ; size = 4 ??0box@@QAE@HHHH@Z PROC ; box::box, COMDAT ; _this$ = ecx push esi mov esi, ecx call ??0object@@QAE@XZ ; object::object mov eax, DWORD PTR _color$[esp] mov ecx, DWORD PTR _width$[esp] mov edx, DWORD PTR _height$[esp] mov DWORD PTR [esi+4], eax mov eax, DWORD PTR _depth$[esp] mov DWORD PTR [esi+16], eax mov DWORD PTR [esi], OFFSET ??_7box@@6B@ mov DWORD PTR [esi+8], ecx mov DWORD PTR [esi+12], edx mov eax, esi pop esi ret 16 ; 00000010H ??0box@@QAE@HHHH@Z ENDP ; box::box ``` 我們可以看到一些輕微的內存布局的變化:第一個域是一個指向box::`vftable`(這個名字由MSVC編譯器生成)的指針。 在這個函數表里我們看到了一個指向box::`RTTI Complete Object Locator`的連接,而且還有一個指向box::dump()函數的。所以這就是被命名的虛函數表和RTTI。虛函數表可以包含所有虛函數體的地址,RTTI表包含類型的信息。另外一提,RTTI表是c++調用dynamic_cast和typeid的結果的枚舉表。你可以看到這里函數名是用明文表記的。因此,一個基對象可以調用虛函數object::dump(),然后,會從這個對象的結構里調用這個繼承類的函數。 枚舉這些函數表需要消耗額外的CPU時間,所以可以認為虛函數比普通調用要慢一些。 在GCC生成的代碼里,RTTI表的構造有些輕微的不同。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看