【 聲明:版權所有,歡迎轉載,請勿用于商業用途。 】
預設函數是C++的一個基本特色。預設函數定義比較簡單,也就是說,對于函數的某一個輸入參數或者幾個輸入參數,如果你沒有特定的數值的話,那我們就會 用預設的資料進行代替。如果你在調用的過程中使用了自己的資料,那麼預設資料将被我們自己定義的資料覆寫。下面就是一個預設函數的示例:
[cpp] view plaincopy
- int add(int m, int n = 10)
- {
- return m + n;
- }
如果調用呢,有什麼差別?
- 262: int p = add(2);
- 00401488 push 0Ah
- 0040148A push 2
- 0040148C call @ILT+15(add) (00401014)
- 00401491 add esp,8
- 00401494 mov dword ptr [ebp-4],eax
- 263: p = add(3, 4);
- 00401497 push 4
- 00401499 push 3
- 0040149B call @ILT+15(add) (00401014)
- 004014A0 add esp,8
- 004014A3 mov dword ptr [ebp-4],eax
可以從上面的代碼看到,如果單獨輸入一個資料2,那麼編譯器幫我們預設輸入了10;如果輸了的資料是3、4呢,那麼編譯器将用4代替預設的資料10。所 以說,編譯器幫我們做了中間的替換和判斷工作。那麼回到我們今天讨論的預設模闆類型上面,那會是什麼樣的情形呢?我們可以編寫一個範例:
- template <typename type1, typename type2 = int>
- class data
- type2 value;
- public:
- data(type2 m): value(m) {}
- ~data() {}
- };
可以看到,我們在第二個參數使用了預設類型int,那麼怎麼證明預設類型可以使用呢?我們設計了下面一個測試用例:
- 239: data<int, int> m(2);
- 004013BD push 2
- 004013BF lea ecx,[ebp-10h]
- 004013C2 call @ILT+5(data<int,int>::data<int,int>) (0040100a)
- 004013C7 mov dword ptr [ebp-4],0
- 240: data<int> n(3);
- 004013CE push 3
- 004013D0 lea ecx,[ebp-14h]
- 004013D3 call @ILT+5(data<int,int>::data<int,int>) (0040100a)
上面的代碼定義了兩個臨時變量,其中第一個是m,輸入類型是int;第二個臨時變量是n,輸入類型是int和int。前面我們說過預設類型是int,那 麼第一個臨時變量m和第二個臨時變量n的構造函數位址應該是一樣的。那麼事實上兩者的構造函數是不是一樣的呢?我們可以檢視兩者的函數位址,發現一個是 0x0040100a,另外一個也是0x0040100a。範例證明我們的判斷是正确的。
明白了上面的預設模闆構造,下面我們談一下特化模闆。特化模闆是什麼意思呢?其實并不複雜。因為模闆類既然是通用模闆,那麼其中的資料類型可以是任意數 據類型,但是難免有一些資料類型(比如說指針),我們需要對其中的一些操作做一些細微的修改,但是這些小的修改在原來的模闆定義上是無法做的。那麼怎麼 辦?我們隻好重新定義一種形式,它和模闆類定義的名稱一緻,但是形式稍有差别。我們可以編寫一個測試看看:
- template <typename type>
- data() {printf("normal!\n");}
- ~data() {printf("~normal!\n");}
- template <>
- class data<int*>
- data() {printf("point!\n");}
- ~data() {printf("point!\n");}
上面的代碼定義了兩個類模闆。但是兩者的名稱是一樣的,說明這兩個類定義的内容其實具有很大的相似性。第一種定義就是标準模闆類的定義,第二中稍微複雜 一點,使用預設的int*,因為沒有使用到特定的type類型,是以此時template後面的内容為空。那麼怎麼判斷這兩個類都是可以正常使用的呢?大 家可以看看下面的範例:
- 249: data<int> p;
- 004013BD lea ecx,[ebp-10h]
- 004013C0 call @ILT+45(data<int>::data<int>) (00401032)
- 004013C5 mov dword ptr [ebp-4],0
- 250: data<int*> q;
- 004013CC lea ecx,[ebp-14h]
- 004013CF call @ILT+35(data<int *>::data<int *>) (00401028)
- 251: }
我們發現,第一個函數的call位址是0x00401032,第二個位址為0x00401028。但是這說明不了什麼,因為第二個位址完全也可能是第一個模闆類引申的。我們應該跟到每一個函數裡面(其實這裡的位址在VC下都是跳轉位址)。
第一個變量的實際進入函數如下所示:
- 234: data() {printf("normal!\n");}
- 00401340 push ebp
- 00401341 mov ebp,esp
- 00401343 sub esp,44h
- 00401346 push ebx
- 00401347 push esi
- 00401348 push edi
- 00401349 push ecx
- 0040134A lea edi,[ebp-44h]
- 0040134D mov ecx,11h
- 00401352 mov eax,0CCCCCCCCh
- 00401357 rep stos dword ptr [edi]
- 00401359 pop ecx
- 0040135A mov dword ptr [ebp-4],ecx
- 0040135D push offset string "normal!\n" (0042607c)
- 00401362 call printf (00401540)
- 00401367 add esp,4
- 0040136A mov eax,dword ptr [ebp-4]
- 0040136D pop edi
- 0040136E pop esi
- 0040136F pop ebx
- 00401370 add esp,44h
- 00401373 cmp ebp,esp
- 00401375 call __chkesp (004023b0)
- 0040137A mov esp,ebp
- 0040137C pop ebp
- 0040137D ret
那麼,第二個變量呢,同樣需要跟入函數:
- 242: data() {printf("point!\n");}
- 00401430 push ebp
- 00401431 mov ebp,esp
- 00401433 sub esp,44h
- 00401436 push ebx
- 00401437 push esi
- 00401438 push edi
- 00401439 push ecx
- 0040143A lea edi,[ebp-44h]
- 0040143D mov ecx,11h
- 00401442 mov eax,0CCCCCCCCh
- 00401447 rep stos dword ptr [edi]
- 00401449 pop ecx
- 0040144A mov dword ptr [ebp-4],ecx
- 0040144D push offset string "point!\n" (00426074)
- 00401452 call printf (00401540)
- 00401457 add esp,4
- 0040145A mov eax,dword ptr [ebp-4]
- 0040145D pop edi
- 0040145E pop esi
- 0040145F pop ebx
- 00401460 add esp,44h
- 00401463 cmp ebp,esp
- 00401465 call __chkesp (004023b0)
- 0040146A mov esp,ebp
- 0040146C pop ebp
- 0040146D ret
看到上面的函數,大家應該明白了兩者調用的構造函數并不一樣。是以說,特化模闆通常就是為了那些特殊的資料類型準備的。這樣我們使用者在使用模闆的時候 就沒有什麼顧慮了,可以忽略各個資料類型處理上的差别了。當然,特化模闆因為考慮了特殊模型資料,使得我們的代碼更加完畢,更加健壯了,建議在設計模闆的 時候适當多使用。
思考題:
(1)模闆類第一個type可以預設嗎?為什麼會這麼考慮?
(2)下面的代碼在vc 6.0和vc 2005上都能編譯過?為什麼呢?對于我們設計代碼有什麼思考呢? (建議從相容性上面考慮)
- class data <type*>
【預告: 下面的兩個部落格非常有意思,介紹遞歸模闆和模闆的模闆内容】