模板:從類型參數(shù)化到泛型編程的實(shí)戰(zhàn)指南)
1. 項(xiàng)目概述從“重復(fù)造輪子”到“一勞永逸”的思維躍遷干了這么多年C我敢說每個(gè)從C語言轉(zhuǎn)過來的老手或者剛?cè)胄械男氯硕冀?jīng)歷過一段“復(fù)制粘貼”的黑暗時(shí)期。就拿最經(jīng)典的swap函數(shù)來說你想交換兩個(gè)int寫一個(gè)過兩天要交換兩個(gè)double得再復(fù)制一份改個(gè)類型后來要交換兩個(gè)自定義的Student對象又得再來一遍。代碼庫里堆滿了功能相同、僅僅類型不同的函數(shù)維護(hù)起來簡直是噩夢——改一個(gè)邏輯得把所有副本都找出來改一遍稍不留神就漏了bug就這么埋下了。這就是“函數(shù)模板”要解決的核心痛點(diǎn)。它不是什么高深莫測的黑魔法而是一種讓編譯器幫你“自動寫代碼”的編程范式官方名稱叫“泛型編程”。它的核心思想就一句話將數(shù)據(jù)類型參數(shù)化。你不再需要為int、double、string各寫一套swap只需要寫一套“模板”告訴編譯器“我這里有個(gè)操作邏輯但具體是什么類型等我用的時(shí)候再告訴你”。編譯器會在你調(diào)用的時(shí)候根據(jù)你提供的具體類型現(xiàn)場“實(shí)例化”出一個(gè)針對該類型的、實(shí)實(shí)在在的函數(shù)。這帶來的好處是顛覆性的。首先是代碼復(fù)用率飆升一份模板無限類型適用。其次是類型安全模板是在編譯期進(jìn)行類型檢查和代碼生成的遠(yuǎn)比運(yùn)行時(shí)靠void*強(qiáng)轉(zhuǎn)要安全可靠。最后是性能零開銷因?yàn)閷?shí)例化出來的就是普通函數(shù)沒有任何運(yùn)行時(shí)額外的判斷或跳轉(zhuǎn)。理解并熟練運(yùn)用函數(shù)模板是從“C with Class”的面向過程思維邁向真正現(xiàn)代C泛型思維的關(guān)鍵一步。無論你是想寫出更優(yōu)雅、更通用的庫代碼還是僅僅為了讓自己日常開發(fā)更高效函數(shù)模板都是你必須掌握的利器。2. 函數(shù)模板的核心語法與工作機(jī)制拆解2.1 模板聲明與定義template關(guān)鍵字的門道函數(shù)模板的語法骨架非常簡潔但細(xì)節(jié)里藏著魔鬼。我們從一個(gè)最基礎(chǔ)的模板開始template typename T T max(T a, T b) { return (a b) ? a : b; }第一行template typename T就是模板的“開工聲明”。template是關(guān)鍵字尖括號里放的是“模板參數(shù)列表”。這里的typename T聲明了一個(gè)類型模板參數(shù)T。你可以把T理解為一個(gè)占位符代表“某種尚未確定的類型”。typename也可以用class關(guān)鍵字替代在類型參數(shù)這里兩者完全等價(jià)但typename語義更清晰表示一個(gè)類型名我個(gè)人更推薦使用。注意template這一行必須緊挨著它要修飾的函數(shù)或類聲明或定義之前中間不能插入其他代碼。并且一個(gè)模板聲明只對其緊接著的那個(gè)實(shí)體有效。這個(gè)max模板的意思是我定義了一個(gè)函數(shù)它接受兩個(gè)類型相同的參數(shù)都是T類型并返回其中較大的那個(gè)。至于T具體是int、double還是MyClass調(diào)用的時(shí)候再說。2.2 模板實(shí)例化編譯器在背后做了什么當(dāng)你寫下int result max(10, 20);這行代碼時(shí)魔法就開始了。這個(gè)過程叫做模板實(shí)例化。編譯器會進(jìn)行以下幾步操作類型推導(dǎo)編譯器看到實(shí)參10和20都是int類型于是推導(dǎo)出模板參數(shù)T應(yīng)該是int。生成代碼編譯器拿著T int這個(gè)信息回到模板定義處將所有的T替換為int生成一個(gè)實(shí)實(shí)在在的函數(shù)就像你親手寫的一樣int max(int a, int b) { return (a b) ? a : b; }編譯生成函數(shù)這個(gè)新生成的函數(shù)會被編譯并加入到你的程序中。如果你再調(diào)用double d max(3.14, 2.71);編譯器會為你再實(shí)例化一個(gè)double版本的max函數(shù)。這就是“一份模板多種實(shí)現(xiàn)”。2.3 多參數(shù)與默認(rèn)參數(shù)讓模板更靈活模板參數(shù)可以不止一個(gè)也可以有默認(rèn)值這大大增強(qiáng)了其靈活性。多類型參數(shù)比如你想寫一個(gè)能打印任意兩個(gè)類型值的函數(shù)。template typename T1, typename T2 void printPair(const T1 first, const T2 second) { std::cout ( first , second )\n; } // 使用printPair(42, Hello); // T1int, T2const char*非類型模板參數(shù)除了類型模板參數(shù)還可以是整型、枚舉、指針或引用C20后范圍更廣等“值”。這在編譯期計(jì)算和固定大小容器中非常有用。template typename T, int N class FixedArray { public: T arr[N]; // 數(shù)組大小N在編譯期就確定了 int size() const { return N; } }; // 使用FixedArraydouble, 100 bigArray; // 一個(gè)編譯期就知道是100個(gè)double的數(shù)組默認(rèn)模板參數(shù)和函數(shù)參數(shù)一樣模板參數(shù)也可以有默認(rèn)值。template typename T int, typename Container std::vectorT class MyAdapter { // ... 默認(rèn)使用int和vectorint }; // 使用MyAdapter adapter1; // 使用默認(rèn)的int和vectorint // MyAdapterdouble adapter2; // Tdouble, Containervectordouble實(shí)操心得非類型模板參數(shù)是進(jìn)行編譯期優(yōu)化的利器。例如你可以用一個(gè)模板參數(shù)來指定算法策略的“展開層級”編譯器會根據(jù)這個(gè)常量進(jìn)行循環(huán)展開等優(yōu)化。但要注意非類型參數(shù)的值必須是編譯期常量。3. 類型推導(dǎo)與顯式指定的藝術(shù)3.1 自動類型推導(dǎo)的規(guī)則與陷阱我們調(diào)用max(10, 20)時(shí)并沒有告訴編譯器T是什么編譯器自己推導(dǎo)出來了。這套推導(dǎo)規(guī)則是模板使用的核心。對于函數(shù)模板template typename T void func(T param);當(dāng)你調(diào)用func(expr)時(shí)編譯器通過expr的類型來推導(dǎo)T。規(guī)則大致類似于auto的推導(dǎo)規(guī)則傳遞int→T被推導(dǎo)為int傳遞const int→T被推導(dǎo)為int引用和頂層const被剝離傳遞int[10]→T被推導(dǎo)為int*數(shù)組退化為指針常見陷阱1類型不匹配。template typename T T add(T a, T b) { return a b; } int a 5; double b 3.14; auto c add(a, b); // 錯(cuò)誤編譯器困惑T該推導(dǎo)成int還是double這里兩個(gè)參數(shù)類型不同編譯器無法為單個(gè)模板參數(shù)T確定一個(gè)唯一的類型因此推導(dǎo)失敗。常見陷阱2期望推導(dǎo)出引用類型。template typename T void modify(T arg) { arg 100; } int value 42; modify(value); std::cout value; // 輸出仍然是42為什么因?yàn)閙odify(value)推導(dǎo)時(shí)T被推導(dǎo)為int而不是int。函數(shù)內(nèi)部修改的是局部副本。如果你希望修改實(shí)參模板應(yīng)該寫成template typename T void modify(T arg)。3.2 何時(shí)需要顯式指定模板參數(shù)有些時(shí)候編譯器無法推導(dǎo)或者我們不想使用推導(dǎo)結(jié)果就需要在函數(shù)名后使用尖括號顯式指定模板參數(shù)。場景1返回值類型無法從參數(shù)推導(dǎo)。template typename To, typename From To convert(From f) { return static_castTo(f); } // 編譯器無法從‘3.14’推導(dǎo)出To是什么類型 auto i convertint(3.14); // 正確顯式指定Toint, From由3.14推導(dǎo)為double場景2希望引導(dǎo)編譯器使用特定類型。template typename T void process(T* ptr) { /*...*/ } int* p nullptr; process(p); // 推導(dǎo)T為int processint(p); // 效果相同但顯式指明了 // 更常見于有重載或特化時(shí)強(qiáng)制選擇某個(gè)版本場景3函數(shù)參數(shù)與模板類型無關(guān)。template typename T T getDefaultValue() { return T{}; // 返回T類型的默認(rèn)構(gòu)造值 } // 調(diào)用時(shí)必須顯式指定因?yàn)闆]有任何函數(shù)參數(shù)可供推導(dǎo) int zero getDefaultValueint();避坑技巧當(dāng)模板函數(shù)有多個(gè)參數(shù)且你只想顯式指定前面幾個(gè)讓編譯器推導(dǎo)后面的時(shí)C17之前這是做不到的必須全部指定或全部推導(dǎo)。從C17開始你可以利用“類模板參數(shù)推導(dǎo)”(CTAD)的類似思想或設(shè)計(jì)不同的參數(shù)順序來規(guī)避但最直接的方法通常是重載或使用默認(rèn)參數(shù)。4. 模板特化與重載處理特殊情況模板是通用的但總有通用邏輯處理不了的“刺頭”類型。這時(shí)就需要特化和重載出場了。4.1 全特化為特定類型定制實(shí)現(xiàn)全特化意為“完全特化”即為模板參數(shù)列表中的所有參數(shù)都指定了具體類型提供了一個(gè)完全不同的實(shí)現(xiàn)。它像一個(gè)專門為某種類型開的“后門”。語法上全特化時(shí)不再需要template 但尖括號不能少并且函數(shù)參數(shù)類型和返回值類型通常都變?yōu)榫唧w的類型。// 通用模板 template typename T bool isEqual(T a, T b) { return a b; } // 為const char* 類型的全特化版本比較字符串內(nèi)容而非指針地址 template bool isEqualconst char*(const char* a, const char* b) { return strcmp(a, b) 0; } // 使用 int main() { std::cout isEqual(1, 1) std::endl; // 調(diào)用通用版本true const char* s1 hello; const char* s2 hello; std::cout isEqual(s1, s2) std::endl; // 調(diào)用特化版本true (如果是指針比較則為false) }全特化就像一個(gè)完全獨(dú)立的函數(shù)只是借用了原模板的名字。編譯器在調(diào)用時(shí)會優(yōu)先選擇最匹配的特化版本。4.2 偏特化函數(shù)模板不支持這里有一個(gè)非常重要的知識點(diǎn)函數(shù)模板不支持偏特化部分特化。偏特化是指只特化一部分模板參數(shù)或者對模板參數(shù)加上一些修飾如T*,T。這是類模板的“特權(quán)”。如果你需要對函數(shù)模板實(shí)現(xiàn)類似“針對指針類型”的特殊處理不能使用偏特化而應(yīng)該使用重載。4.3 函數(shù)重載更自然的替代方案當(dāng)通用模板對某些類型處理不佳時(shí)直接提供一個(gè)參數(shù)類型不同的重載函數(shù)往往是更清晰、更符合直覺的做法。// 通用模板 template typename T void log(T value) { std::cout Value: value std::endl; } // 重載版本針對指針類型的特殊處理 template typename T void log(T* ptr) { if (ptr) { std::cout Pointer points to: *ptr std::endl; } else { std::cout Null pointer std::endl; } } // 重載版本針對C風(fēng)格字符串 void log(const char* str) { std::cout C-string: \ str \ std::endl; }調(diào)用log(42)調(diào)用模板log(x)調(diào)用指針版本的重載模板log(hello)調(diào)用非模板的const char*重載。編譯器會選擇最匹配的那個(gè)版本。注意事項(xiàng)重載決議的規(guī)則非常復(fù)雜涉及到模板和非模板的優(yōu)先級、類型轉(zhuǎn)換成本等。一個(gè)基本原則是非模板函數(shù)優(yōu)先于模板函數(shù)更特化的模板優(yōu)先于更通用的模板。在設(shè)計(jì)重載和特化時(shí)務(wù)必寫測試用例確保調(diào)用的是你期望的那個(gè)版本避免掉入重載決議的陷阱。5. 實(shí)戰(zhàn)構(gòu)建一個(gè)通用的“數(shù)組工具”模板庫光說不練假把式。我們現(xiàn)在來實(shí)戰(zhàn)構(gòu)建一個(gè)小型的、通用的數(shù)組操作工具庫把前面講的知識點(diǎn)串起來。假設(shè)我們要處理內(nèi)置數(shù)組C-style array和std::array。5.1 核心工具函數(shù)實(shí)現(xiàn)首先我們實(shí)現(xiàn)幾個(gè)最常用的函數(shù)。1. 安全獲取數(shù)組長度對于內(nèi)置數(shù)組我們常用sizeof(arr)/sizeof(arr[0])但容易出錯(cuò)比如數(shù)組退化為指針后失效。我們可以用模板封裝一個(gè)安全的版本。// 針對內(nèi)置數(shù)組的版本 template typename T, std::size_t N constexpr std::size_t arraySize(T ()[N]) noexcept { return N; // 通過引用傳遞數(shù)組可以推導(dǎo)出大小N } // 針對std::array的版本其實(shí)std::array有size()成員這里僅為演示 template typename T, std::size_t N constexpr std::size_t arraySize(const std::arrayT, N) noexcept { return N; }這里用了兩個(gè)技巧1) 使用數(shù)組的引用T ()[N]作為參數(shù)確保模板只匹配真正的數(shù)組防止指針誤入。2) 使用constexpr和noexcept表明這個(gè)函數(shù)可以在編譯期求值且不會拋出異常符合工具函數(shù)的定位。2. 泛型打印數(shù)組template typename Container void printContainer(const Container cont) { for (const auto elem : cont) { // 范圍for循環(huán)要求容器支持begin/end std::cout elem ; } std::cout \n; } // 針對不支持范圍for的舊式數(shù)組可以單獨(dú)重載或使用上面的arraySize遍歷 template typename T, std::size_t N void printContainer(T (arr)[N]) { for (std::size_t i 0; i N; i) { std::cout arr[i] ; } std::cout \n; }3. 查找數(shù)組中的最大值泛型版本template typename ForwardIt // 使用迭代器更通用 ForwardIt findMax(ForwardIt first, ForwardIt last) { if (first last) return last; // 空范圍 ForwardIt largest first; first; for (; first ! last; first) { if (*largest *first) { // 依賴元素類型的 操作符 largest first; } } return largest; }這個(gè)版本不關(guān)心容器是數(shù)組、vector還是list只關(guān)心迭代器通用性極強(qiáng)。使用時(shí)int arr[] {5, 2, 9, 1, 5}; auto maxIt findMax(std::begin(arr), std::end(arr)); if (maxIt ! std::end(arr)) { std::cout Max value: *maxIt std::endl; }5.2 引入比較器讓查找更靈活上面的findMax使用運(yùn)算符比較。如果我們想找“絕對值最大”的或者針對自定義類型按某個(gè)成員比較呢我們需要引入一個(gè)“比較器”參數(shù)。template typename ForwardIt, typename Compare ForwardIt findExtreme(ForwardIt first, ForwardIt last, Compare comp) { if (first last) return last; ForwardIt extreme first; first; for (; first ! last; first) { if (comp(*extreme, *first)) { // 使用傳入的比較器 extreme first; } } return extreme; }現(xiàn)在我們可以傳入任何可調(diào)用對象函數(shù)、函數(shù)指針、lambda、仿函數(shù)作為比較器。// 找絕對值最大的數(shù) int arr[] {-10, 2, -30, 4}; auto absCompare [](int a, int b) { return std::abs(a) std::abs(b); }; auto absMaxIt findExtreme(std::begin(arr), std::end(arr), absCompare); // 假設(shè)有Student結(jié)構(gòu)體按年齡找最小的 struct Student { std::string name; int age; }; std::vectorStudent students {{Alice, 20}, {Bob, 18}}; auto ageCompare [](const Student a, const Student b) { return a.age b.age; }; auto youngestIt findExtreme(students.begin(), students.end(), ageCompare);這種“將算法和策略比較方式分離”的設(shè)計(jì)是STL算法的精髓極大地提升了代碼的復(fù)用性。5.3 編譯期數(shù)組操作初探利用非類型模板參數(shù)和constexpr我們甚至可以在編譯期進(jìn)行一些數(shù)組操作。這在性能要求極高的場景如游戲、嵌入式很有用。// 編譯期計(jì)算數(shù)組所有元素的和 template typename T, std::size_t N constexpr T sumArray(const T (arr)[N]) { T sum{}; for (std::size_t i 0; i N; i) { sum arr[i]; } return sum; } // 在編譯期驗(yàn)證 constexpr int nums[] {1, 2, 3, 4, 5}; constexpr int total sumArray(nums); // 編譯期計(jì)算total15 static_assert(total 15, Compile-time sum error!);constexpr函數(shù)在C14/17后能力大大增強(qiáng)可以在函數(shù)內(nèi)使用循環(huán)、局部變量等。這使得編寫編譯期計(jì)算的泛型算法成為可能。6. 進(jìn)階話題SFINAE與概念Concepts簡介當(dāng)模板變得復(fù)雜你會遇到需要“根據(jù)類型條件選擇不同實(shí)現(xiàn)”或“限制模板只接受某些類型”的需求。傳統(tǒng)的解決方案是SFINAE而C20帶來了更優(yōu)雅的“概念”。6.1 SFINAE替換失敗并非錯(cuò)誤SFINAE 是“Substitution Failure Is Not An Error”的縮寫。它是模板元編程的基石之一。簡單說當(dāng)編譯器在重載決議時(shí)嘗試用實(shí)參推導(dǎo)模板參數(shù)如果推導(dǎo)導(dǎo)致出現(xiàn)無效的代碼如某個(gè)類型沒有某個(gè)成員這個(gè)模板候選并不會導(dǎo)致編譯錯(cuò)誤而是被靜默地從重載集中移除。編譯器會繼續(xù)嘗試其他候選。一個(gè)經(jīng)典的例子是檢查一個(gè)類型是否有某個(gè)成員函數(shù)serialize// 輔助工具檢測serialize成員函數(shù) template typename T class has_serialize { private: template typename U static auto test(int) - decltype(std::declvalU().serialize(), std::true_type{}); template typename static std::false_type test(...); public: static constexpr bool value decltype(testT(0))::value; }; // 使用SFINAE的模板函數(shù) template typename T typename std::enable_ifhas_serializeT::value, std::string::type serialize(const T obj) { return obj.serialize(); // 版本1有serialize成員 } template typename T typename std::enable_if!has_serializeT::value, std::string::type serialize(const T obj) { return std::to_string(obj); // 版本2沒有則用to_string }std::enable_if條件, 返回類型是一個(gè)工具如果條件為真它才有type成員即指定的返回類型如果條件為假它沒有type成員根據(jù)SFINAE原則這個(gè)函數(shù)模板就會被移除。這樣就實(shí)現(xiàn)了條件選擇。SFINAE功能強(qiáng)大但語法晦澀容易寫出難以理解的代碼。6.2 C20概念Concepts讓約束變得清晰C20引入的“概念”特性正是為了解決SFINAE的復(fù)雜性。它允許你以直觀的方式聲明對模板參數(shù)的約束。// 定義一個(gè)概念要求類型T必須有serialize()成員函數(shù)且返回std::string template typename T concept Serializable requires(T t) { { t.serialize() } - std::convertible_tostd::string; }; // 使用概念約束的模板 template Serializable T // 簡潔明了 std::string serialize(const T obj) { return obj.serialize(); } // 對于不滿足Serializable的類型提供一個(gè)通用后備版本如果需要 template typename T std::string serialize(const T obj) { return std::to_string(obj); }使用concept關(guān)鍵字定義約束然后在模板參數(shù)列表中使用ConceptName T的語法。代碼意圖一目了然第一個(gè)serialize函數(shù)只接受滿足Serializable概念的類型。編譯器會給出更清晰的錯(cuò)誤信息。概念還可以用于更復(fù)雜的約束組合與、或、非。它是現(xiàn)代C泛型編程的未來如果你的編譯器支持C20強(qiáng)烈建議開始使用概念來替代復(fù)雜的SFINAE技巧。7. 常見問題、調(diào)試技巧與性能考量7.1 鏈接錯(cuò)誤模板定義的位置這是新手最常見的坑。記住一個(gè)黃金法則函數(shù)模板的定義而不僅僅是聲明通常必須放在頭文件里。為什么因?yàn)槟0迨蔷幾g期的“藍(lán)圖”。當(dāng)你在main.cpp里調(diào)用max(10, 20)時(shí)編譯器需要看到max模板的完整定義才能為int實(shí)例化出代碼。如果定義在另一個(gè).cpp文件里main.cpp只看到了聲明編譯器就無法實(shí)例化只會假設(shè)其他編譯單元那個(gè).cpp會提供實(shí)例化好的函數(shù)。到了鏈接階段鏈接器找不到maxint的函數(shù)實(shí)體就會報(bào)“未定義的引用”錯(cuò)誤。解決方案將所有模板的定義函數(shù)體直接寫在頭文件.h或.hpp中。如果模板實(shí)現(xiàn)很長可以在頭文件末尾#include impl.ipp而impl.ipp里包含實(shí)現(xiàn)但這本質(zhì)上還是把頭文件內(nèi)容分開放置而已。7.2 晦澀的錯(cuò)誤信息模板相關(guān)的編譯錯(cuò)誤信息往往又長又晦澀充斥著各種內(nèi)部類型名。比如一個(gè)簡單的類型不匹配錯(cuò)誤可能輸出幾十行。調(diào)試技巧從最后一行看起編譯器錯(cuò)誤信息通常像棧一樣層層展開最后一行往往是最根本的原因。尋找你熟悉的代碼行號在錯(cuò)誤信息中定位到你源代碼文件的行號從那里開始分析上下文。簡化問題如果錯(cuò)誤復(fù)雜嘗試將調(diào)用簡化到最小可復(fù)現(xiàn)例子。比如用具體的int替換自定義類型看是否還出錯(cuò)。使用static_assert提前檢查在模板代碼中可以用static_assert對類型進(jìn)行編譯期斷言給出清晰的錯(cuò)誤信息。template typename T void process(T val) { static_assert(std::is_arithmetic_vT, T must be an arithmetic type (int, float, etc.)); // ... 處理邏輯 }7.3 代碼膨脹問題模板會導(dǎo)致代碼膨脹嗎會也不會。會產(chǎn)生膨脹的情況如果你為許多不同的類型實(shí)例化了同一個(gè)模板編譯器確實(shí)會生成多份函數(shù)二進(jìn)制代碼。例如std::vectorint,std::vectordouble,std::vectorMyClass在最終的程序里就是三個(gè)不同的類有各自的成員函數(shù)。不會產(chǎn)生膨脹的情況對于函數(shù)模板如果不同類型實(shí)例化出的函數(shù)體機(jī)器碼完全相同例如void*特化或某些簡單的內(nèi)聯(lián)函數(shù)聰明的鏈接器可能會進(jìn)行“重復(fù)代碼消除”。而且很多模板函數(shù)是內(nèi)聯(lián)的不存在獨(dú)立的函數(shù)調(diào)用開銷。優(yōu)化建議將模板的通用部分抽取到非模板的輔助函數(shù)中。對于指針類型考慮使用類型擦除技術(shù)如void*加函數(shù)指針但會損失類型安全。相信現(xiàn)代編譯器和鏈接器的優(yōu)化能力不要過早優(yōu)化。在性能分析工具指出模板實(shí)例化是瓶頸之前優(yōu)先保證代碼的清晰和通用性。7.4 與其他特性的結(jié)合constexpr,auto,decltype現(xiàn)代C中模板經(jīng)常與這些特性強(qiáng)強(qiáng)聯(lián)合。constexpr 模板如前所述可以實(shí)現(xiàn)編譯期計(jì)算。auto作為返回類型C14 允許函數(shù)使用auto返回值結(jié)合模板可以推導(dǎo)返回類型非常方便。template typename T1, typename T2 auto add(const T1 a, const T2 b) - decltype(a b) { // C11 風(fēng)格 return a b; } // C14 可以簡化為 template typename T1, typename T2 auto add(const T1 a, const T2 b) { return a b; // 編譯器自動推導(dǎo)返回類型 }decltype用于查詢表達(dá)式的類型在模板中常用于聲明與某個(gè)參數(shù)表達(dá)式類型相關(guān)的變量或返回類型。掌握函數(shù)模板就像是拿到了C泛型編程大門的鑰匙。它要求你從“具體類型”的思維跳脫出來思考“類型范疇”上的通用算法。開始時(shí)可能會被復(fù)雜的語法和錯(cuò)誤信息嚇到但一旦熟悉你將能寫出極其靈活、強(qiáng)大且類型安全的代碼。從簡單的swap、max開始練習(xí)逐步嘗試編寫自己的容器適配器、算法工具你會深刻體會到“一次編寫處處適用”的魅力。模板的深水區(qū)還有類模板、變參模板、模板元編程等但打好函數(shù)模板這個(gè)基礎(chǔ)后續(xù)的學(xué)習(xí)將會順暢得多。記住好的模板代碼不僅是給機(jī)器用的更是給人讀的清晰的接口和適當(dāng)?shù)募s束是關(guān)鍵。