430 likes | 610 Vues
第十五章. 樣板( Template ). 第十五章 樣板( Template ). 樣板是 C++ 為了方便能夠快速生產程式而提供的新功能,在最初期的 C++ 版本中,並不提供樣板機制。所謂樣板( Template ),就是讓某些特定功能的程式如同板模般,當我們需要大量使用該功能時,直接由編譯器代為改變程式細節,以符合輸入的資料型態。
E N D
第十五章 樣板(Template)
第十五章樣板(Template) • 樣板是C++為了方便能夠快速生產程式而提供的新功能,在最初期的C++版本中,並不提供樣板機制。所謂樣板(Template),就是讓某些特定功能的程式如同板模般,當我們需要大量使用該功能時,直接由編譯器代為改變程式細節,以符合輸入的資料型態。 • 因此,在現代化的程式設計中,大多將函式或類別設計為樣板,避免撰寫相同功能但資料型態不同的程式。在正式介紹樣板之前,請讀者注意一點,繼承與樣板都能增進程式碼重複使用率及減少程式開發時程,但樣板的對象是固定是類別和函式,而繼承僅限於類別,兩者在許多方面有根本上的不同。
大綱 • 15.1 什麼是樣板? • 15.2 函式樣板 • 15.2.1 單一保留資訊的函式樣板 • 15.2.2 多保留資訊的函式樣板 • 15.3 類別樣板 • 15.4 樣板的具現 • 15.5 本章回顧
15.1 什麼是樣板? • 在C語言中,程式設計師常常會遇到一個麻煩的問題,就是即使函式需求相同,但必須針對不同資料型態,設計不同的函式,例如我們想要求出絕對值,就必須設計成兩個名稱不同的函式,如下範例。
15.1 什麼是樣板? • 在初期的C++中,上述情況獲得改善,我們可以利用函式覆載(Overload)功能,將函式名稱設定為相同的abs,只要引數內容不同即可,如下範例。 • 雖然C++提供了覆載函式來解決函式名稱的問題,但仍無法解決撰寫重複程式碼的問題(雖然通常只需要複製程式碼並略為修改即可),而後期的C++則提供了樣板來解決上述問題。樣板允許保留一些資料,等到實際使用樣板時,才於編譯時期,由編譯器自行複製程式內容。
15.1 什麼是樣板? • 事實上,樣板可分為兩大類:函式樣板(Function Template)及類別樣板(Class Template),而其目的則分別是為了加強的函式和類別的開發速度。有了樣板,我們只需預先設計一個通用的板模,然後當我們需要設計類似功能的函式或類別時,就套用這個樣板,並設定相關必要的資訊就可以了。
15.1 什麼是樣板? • 舉例來說,假設我們有一個交換函式swap函式如下,它可以作整數資料的交換。 • 在設計上述的swap函式樣板時,我們可以將程式碼內的int資料型態設定為保留資訊,當我們作整數交換時,只要套用這個樣板,並設定資料型態為int;當我們需要作字元交換,也是同樣可以套用這個樣板,只要將資料型態設定為char即可。換句話說,一個經過設計的樣板,可以讓我們節省很多撰寫類似程式碼的時間,因為編譯器將代為處理套用樣板時的函式轉換手續。
15.2 函式樣板 • C++的樣板有函式樣板與類別樣板兩種,我們先從簡單的函式樣板開始介紹,我們將函式樣板的宣告分為兩大類語法來加以介紹,即單一保留資訊的函式樣板與多保留資訊的函式樣板。
15.2.1 單一保留資訊的函式樣板 • 單一保留資訊的函式樣板宣告與定義: • 單一保留資訊的函式樣板宣告語法如下: • 單一保留資訊的函式樣板定義語法如下: • 語法說明: • (1)N是欲保留的資訊,也就是能被替換的資料型態,編譯器將依據呼叫時的資料型態,產生對應的變化。 • (2)class關鍵字可改為typename。 • (3)函式回傳型態也可以宣告為N(保留資訊)。 template <class N> 函式回傳型態 函式名稱(N,N,...); template <class N> 函式回傳型態 函式名稱(N 引數1,N 引數2,...) { ...樣板內容… }
15.2.1 單一保留資訊的函式樣板 • (4)由於C++是自由格式,因此常見到將宣告列改為兩行的格式,如下定義格式: • 【觀念及實用範例15-1】:實作swap函式樣板,並在呼叫函式樣板時,傳入不同的資料型態。 • 範例15-1:ch15_01.cpp(檔案位於隨書光碟 ch15\ch15_01.cpp)。 template <class N> 函式回傳型態 函式名稱(N 引數1,N 引數2,...) { ...樣板內容… }
15.2.1 單一保留資訊的函式樣板 樣板(套用char資料型態): a1 = i , b1 = j Swap: a1 = j , b1 = i ------------------------- 樣板(套用int資料型態): a2 = 25 , b2 = 56 Swap: a2 = 56 , b2 = 25 ------------------------- 樣板(使用double資料型態): a3 = 0.7 , b3 = 9.3 Swap: a3 = 9.3 , b3 = 0.7 • 執行結果 • 範例說明: • (1)第11行,函式樣板宣告。第13~20行,函式樣板定義。對照上述範例,我們可以發現,定義中,僅將原本的int改寫為保留資訊T而已。
15.2.1 單一保留資訊的函式樣板 • (2)當我們宣告swap為函式樣板後,每一次呼叫swap函式時,只要輸入正確的資料時,編譯器會先判斷是否為正確的指標或正確的資料型態,然後依照所判斷的資料型態自動產生相對應的程式碼。例如為了因應第35行的呼叫,編譯器會自動將樣板複製並取代保留資訊,成為下列程式碼:
15.2.1 單一保留資訊的函式樣板 • (3)當我們在呼叫函式樣板的時候,代入的引數會決定這個函式樣板的內部資料型態。因此在第35行,編譯器判斷及處理的流程如下圖。 圖15-1 編譯器判斷及處理函式樣板的流程
15.2.2 多保留資訊的函式樣板 • 有時候,我們想要保留的資訊不只一個的時候,C++仍允許我們建立樣板,只要將欲保留的資訊一一宣告即可,如下語法: • 多保留資訊的函式樣板宣告語法如下: • 多保留資訊的函式樣板定義語法如下: • 語法說明: • (1)N1、N2…是欲保留的資訊,引數型態則可宣告為這些保留資訊的任一種。 • (2)class關鍵字可改為typename。 • (3)函式回傳型態也可以宣告為N1或N2…….(即保留資訊之一)。 template <class N1,class N2,……….> 函式回傳型態 函式名稱(N1或N2或…,N1或N2或…,...); template <class N1,class N2,………> 函式回傳型態 函式名稱(N1或N2或… 引數1, N1或N2或… 引數2,..); { ...樣板內容… }
15.2.2 多保留資訊的函式樣板 • (4)假設我們有兩個保留資訊及兩個引數,則下列是一個合法的宣告範例: • (5)然而我們常常在使用函式樣板時,會不小心使用錯誤的語法,例如下列幾個錯誤範例:
15.2.2 多保留資訊的函式樣板 • 【觀念範例15-2】:函式樣板中包含多個保留資訊。 • 範例15-2:ch15_02.cpp(檔案位於隨書光碟 ch15\ch15_02.cpp)。
15.2.2 多保留資訊的函式樣板 • 執行結果 m= 3,p=5, m的p次方=243 n=1.5,p=5, n的p次方=7.59375
15.2.2 多保留資訊的函式樣板 • 範例說明: • (1)第11行,函式樣板宣告。第13~21行,函式樣板定義。我們在樣板中保留了兩項資訊,T1與T2,其中T2只用在引數宣告與函式定義,而T1則同時用在函式回傳值、引數宣告與函式定義。 • (2)在第27行呼叫power(m,p)時,它將會建構下列的程式碼。
15.2.2 多保留資訊的函式樣板 • (3)在第28行呼叫power(n,p)時,它將會建構下列的程式碼。 • 對於T1被引數及回傳值同時引用的部分,或許讀者會產生疑惑,在上述說明中,似乎編譯器只要確定引數的資料型態後,就決定了保留資訊應該被取代為哪一種資料型態。例如呼叫power(m,p)時,T1被確認為int,在呼叫power(n,p)時,T1被確認為double。而若我們將回傳值使用另一種型態來接收時,例如int y=power(n,p);,會是什麼樣的狀況呢?答案是會產生警告訊息,編譯器仍將T1認為是double,因此回傳double資料型態的資料,當然最終結果y仍舊是被強制轉型的整數,在上例中,y將會是7。
15.3 類別樣板 • 樣板除了可以應用於函式外,也可以應用於類別,這樣的類別稱之為『類別樣板』。經過樣板化的類別,其內的成員函式與成員變數可適用於所有的資料型態,只要將某些資訊宣告為保留資訊即可。類別樣板的宣告及定義與函式樣板大同小異,運作原理也差不多,如下所述。 • 首先讓我們先來看看類別樣板的標準宣告格式如下: • 類別樣板的定義語法如下: template <class N> class 類別名稱 { 保護等級: 函式回傳型態 函式名稱(N 引數1,N 引數2,...); //成員函式 ... N 變數名稱; //成員變數 ... }
15.3 類別樣板 • 類別樣板的成員函式,若定義於類別外部,則語法如下: • 類別樣板產生物件語法: • 語法說明: • (1)N是欲保留的資訊,如果保留資訊不只一個,也可以按照<class N1,class N2,…>等格式擴充。 template <class N> 函式回傳型態 類別樣板名稱 <N>::成員函式名稱( ) { …. 函式定義內容… } 類別樣板名稱 <保留資訊的資料型態> 物件名稱;
15.3 類別樣板 • (2)< >內的class關鍵字可改為typename。 • (3)函式回傳型態也可以宣告為保留資訊之一。 • (4)宣告類別樣板內的成員函式,並在類別外部定義成員函式時,除了必須在前面加上template <class N>,原本的類別名稱,應該改為『類別樣板名稱 <N>』。 • (5)利用類別樣板產生物件時,也同樣必須加上保留資訊的資料型態,例如<int>、<float>。 • (6)常見的類別樣板宣告錯誤範例如下: • 錯誤範例 • 和函式樣板一樣的限制,類別樣板的引數型態定義不可以在類別中重複使用,如下例的Type。
15.3 類別樣板 • 【觀念及實用範例15-3】:使用類別樣板,使得氣泡排序法可以接受各種資料型態。 • 範例15-3:ch15_03.cpp(檔案位於隨書光碟 ch15\ch15_03.cpp)。
15.3 類別樣板 • 執行結果 ----整數陣列已建立---- 請輸入第0個元素:24 請輸入第1個元素:17 請輸入第2個元素:32 請輸入第3個元素:19 24 17 32 19 --------排序後------- 17 19 24 32 ========================= ----浮點數陣列已建立---- 請輸入第0個元素:15.67 請輸入第1個元素:28.43 請輸入第2個元素:17.52 請輸入第3個元素:12.88 15.67 28.43 17.52 12.88 --------排序後------- 12.88 15.67 17.52 28.43
15.3 類別樣板 • 範例說明: • (1)第11行,類別樣板宣告。第13~23行,類別樣板定義。我們在樣板宣告保留資訊為T。 • (2)在第70行,類別樣板建立ObjX物件時,T會被指定為int後,由編譯器產生對應的類別程式碼,然後才產生物件。 • (3)在第78行,類別樣板建立ObjY物件時,T會被指定為double後,由編譯器產生對應的類別程式碼,然後才產生物件。
15.4 樣板的具現 • 在上述的範例說明中,我們不斷地說明,當呼叫函式樣板時,或透過類別樣板宣告物件時,編譯器都會產生一些程式碼。事實上,這個由編譯器自動執行的動作稱之為『樣板的具現(instantiation)』。 • 在編譯時期,編譯器會將樣板依照所給予不同型別的引數,建構出對應的函式或類別。若是經由函式呼叫而造成函式建構的動作,我們稱之為函式樣板的具現;而經過類別的生成物件而造成類別建構的動作,就稱為類別樣板的具現。函式樣板必須在具現後,才能被呼叫,而類別樣板則是必須在具現後,才能夠產生物件。 • 函式樣板的具現與類別樣板的具現可以是獨立的,但也可以有先後關係。這通常發生在類別樣板內又定義了成員函式樣板時。
15.4 樣板的具現 • 雖然類別樣板已經能夠提高非常好的程式開發效率,但如果成員函式的相似性也很高,我們也可以利用函式樣板進一步定義類別樣板的成員函式,使得程式重複性更低。而此時,定義型態的過程將具有先後的關聯性。 • 我們透過範例15-4,說明編譯器對於樣板的定義流程。範例15-4,將修改自範例15-3,將互換資料另外獨立出來成為swap成員函式,並使用函式樣板來宣告swap函式。 • 【觀念及實用範例15-4】:修改範例15-4,結合類別樣板和函式樣板。 • 範例15-4:ch15_04.cpp(檔案位於隨書光碟 ch15\ch15_04.cpp)。
15.4 樣板的具現 • 執行結果:(同範例15-3) • 範例說明: • 在類別樣板內,我們又宣告了一個成員函式樣板swap(在第20~26行)。並且在BubbleSort成員函式的第65行呼叫這個成員函式。整個編譯器在編譯過程如下: • Step1: 在第76行,遇到Sort <int> ObjX;,先決定保留資訊T的資料型態為int。 • Step2: 此時T變成了輸入引數資料型態為int的Sort類別,也就是必須將T替換為int產生類別的程式碼。 • Step3: 但編譯器欲產生類別的程式碼時,發現定義了一個成員函式樣板,因此,編譯器必須保留該位置,等到決定BubbleSort成員函式的內容時,找到swap函式樣板的呼叫時,才產生swap函式的程式碼,完成swap函式的具現後,才能夠完成Sort類別的具現。換句話說,具現的過程有如一個堆疊(先進後出),當遇到另一個更深層的具現時,則必須將更深層具現完成後,才能完成較外圍的具現。
15.5 本章回顧 • 在本章中,我們介紹了C++提供的新功能-『樣板』,善用樣板可以讓我們更有效率地開發中大型專案,減少撰寫相似程式碼的機會,本章重點整理如下: • (1)C++提供了樣板來解決相似程式碼浪費開發時程的問題。樣板允許保留一些資料,等到實際使用樣板時,才於編譯時期,由編譯器自行複製程式內容。 • (2)樣板可分為兩大類:函式樣板(Function Template)及類別樣板(Class Template)。兩者運作的方式和觀念差不多。 • (3)函式樣板的語法如下: • 單一保留資訊的函式樣板宣告語法如下: • 單一保留資訊的函式樣板定義語法如下: template <class N> 函式回傳型態 函式名稱(N,N,...); template <class N> 函式回傳型態 函式名稱(N 引數1,N 引數2,...) { ...樣板內容… }
15.5 本章回顧 • 多保留資訊的函式樣板宣告語法如下: • 多保留資訊的函式樣板定義語法如下: template <class N1,class N2,……….> 函式回傳型態 函式名稱(N1或N2或…,N1或N2或…,...); template <class N1,class N2,………> 函式回傳型態 函式名稱(N1或N2或…. 引數1, N1或N2或…. 引數2,...); { ...樣板內容… }
15.5 本章回顧 • (4)類別樣板的相關語法如下: • 類別樣板的定義語法如下: • 類別樣板的成員函式,若定義於類別外部,則語法如下: template <class N> class 類別名稱 { 保護等級: 函式回傳型態 函式名稱(N 引數1,N 引數2,...); //成員函式 ... N 變數名稱; //成員變數 ... } template <class N> 函式回傳型態 類別樣板名稱 <N>::成員函式名稱( ) { ….函式定義內容… }
15.5 本章回顧 • 類別樣板產生物件語法: • (5)在編譯時期,編譯器會將樣板依照所給予不同型別的引數,建構出對應的函式或類別,這個由編譯器自動執行的動作稱之為『樣板的具現(instantiation)』。發生的時機則發生於函式樣板的呼叫以及類別樣板產生物件時。 • (6)在類別樣板內也可以再定義成員函式的樣板。 類別樣板名稱 <保留資訊的資料型態> 物件名稱;