C++ 函數(shù)模板完全指南(最新整理)
??從泛型思想、模板特化到排序算法實戰(zhàn) —— 基于現(xiàn)代C++深度剖析, 函數(shù)模板讓算法脫離數(shù)據(jù)類型 —— 真正實現(xiàn) “Write once, use for any type”.
? 一、為什么需要函數(shù)模板?
在C++中,如果需要實現(xiàn)功能相同但數(shù)據(jù)類型不同的函數(shù)(例如交換兩個整數(shù)、兩個浮點數(shù),或者對不同類型數(shù)組排序),傳統(tǒng)做法需要為每種類型重載函數(shù),導致大量重復代碼。函數(shù)模板允許編寫通用的函數(shù),編譯器會根據(jù)實參類型自動生成對應的函數(shù)版本,實現(xiàn)了“一次編寫,多類型使用”,極大提高代碼復用性與可維護性。
?? 核心思想:將數(shù)據(jù)類型參數(shù)化,使用 template <typename T> 或 template <class T> 定義類型占位符。
?? 二、函數(shù)模板定義與基本語法
語法格式:
template <typename T>
T 函數(shù)名(T 參數(shù)1, T 參數(shù)2) {
// 函數(shù)體
}基于示例代碼中的交換模板和加法模板:
// 交換模板
template<typename T>
void swap_T(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
// 加法模板
template<class T>
T add_T(T a, T b) {
return a + b;
}?? 調用方式
- 自動類型推導:swap_T(a, b); 編譯器根據(jù)實參推斷 T 的類型。
- 顯式指定類型:swap_T<int>(a, b); 或 add_T<int>(a, d); 適用于需要類型轉換或避免二義性。
?? 關鍵限制:自動推導時,所有模板參數(shù)必須保持一致的類型。例如 swap_T(a, d)(一個int一個double)會導致推導失敗。但顯式指定類型時允許隱式轉換(值傳遞場景)。
?? 三、無參數(shù)的模板函數(shù) & 值初始化安全性
當模板參數(shù)無法從函數(shù)參數(shù)推導時,必須顯式指定類型。示例中 test_T<int>() 使用了 C++11 的 統(tǒng)一初始化 {} ,防止未初始化變量和窄化轉換。
template<typename T>
void test_T() {
T temp{}; // 值初始化,int為0,double為0.0,自定義類調用默認構造
T temp1{0}; // 安全
// T temp2{0.5}; // 編譯錯誤:窄化轉換(若T為int)
temp = 1.5; // 隱式轉換,int會截斷
}? 最佳實踐:在模板中盡量使用 T var{} 保證變量總是被初始化,提高泛型代碼的魯棒性。
?? 四、函數(shù)模板特化 (Explicit Specialization)
通用模板無法滿足某些特殊類型的需求時(例如自定義類 MyPoint 無法直接用 << 輸出,或者需要特殊格式),可以為特定類型提供特化版本。編譯器會優(yōu)先匹配特化版本。
// 通用模板
template<typename T>
void printArr(T arr[], int len) { ... }
// 針對 int 類型的特化 (分隔符變?yōu)?" / ")
template<>
void printArr<int>(int arr[], int len) {
for (int i = 0; i < len; i++) cout << arr[i] << " / ";
}
// 針對 MyPoint 類型的特化 (優(yōu)雅輸出坐標)
template<>
void printArr(MyPoint arr[], int len) {
for (int i = 0; i < len; i++) {
cout << "(" << arr[i].getX() << "," << arr[i].getY() << ")";
}
}調用時,若傳入 MyPoint 數(shù)組,自動選擇特化版本,實現(xiàn)定制行為。
?? 五、普通函數(shù) vs 函數(shù)模板 vs 模板特化 —— 調用優(yōu)先級
C++ 編譯器選擇調用哪個函數(shù)有一套明確的規(guī)則(優(yōu)先級從高到低):
| 優(yōu)先級 | 候選類型 | 示例 |
|---|---|---|
| 1?? 最高 | 完全匹配的普通函數(shù) | void printArr(int arr[], int len) |
| 2?? | 模板特化 | template<> void printArr<int>(int arr[], int len) |
| 3?? | 函數(shù)模板實例化 | template<typename T> void printArr(T arr[], int len) |
| 4?? 最低 | 經(jīng)過隱式轉換的普通函數(shù) | 例如 printArr(charArr, len) 不存在時可能會嘗試轉換 |
// 普通函數(shù) (打印int數(shù)組使用逗號分隔)
void printArr(int arr[], int len) { ... }
// 調用時:
printArr(int_arr, num); // 匹配普通函數(shù) (優(yōu)先級最高)
printArr<>(int_arr, num); // 空模板列表強制調用函數(shù)模板版本 (跳過普通函數(shù))?? 注意:普通函數(shù)和函數(shù)模板最好不要提供完全相同的重載,容易引發(fā)二義性。若希望總是使用模板,使用空尖括號 <> 強制指定。
?? 六、類型轉換 & 引用的特殊規(guī)則
基于代碼中的 test1 可總結:
- 值傳遞:允許隱式類型轉換(如 addInt(a, c) 將 char 提升為 int)。
- 引用傳遞:swapInt(a, c) 錯誤!引用不能綁定到不同類型轉換后的臨時變量。
但是 const 引用 可以綁定到臨時量(安全,只讀)。 - 模板引用參數(shù):同樣不支持隱式轉換,保持類型嚴格一致。例如 swap_T(a, d) 編譯失敗。
?? 解決方案:需要處理不同類型時,可顯式指定模板參數(shù),或利用重載/函數(shù)重載決議。
?? 七、算法模板實戰(zhàn):選擇排序 & 冒泡排序 (泛型排序)
以下兩個排序模板可以處理 任意支持比較運算符 < 的類型數(shù)組(int, double, char, 甚至自定義類若重載了運算符)。
?? 1.選擇排序模板 (不穩(wěn)定,每輪選擇最大/最小交換)
template<typename T>
void mySelectionSort_T(T arr[], int len) {
for (int i = 0; i < len-1; i++) {
int max = i;
for (int j = i+1; j < len; j++)
if (arr[max] < arr[j]) max = j; // 降序
if (max != i) swap_T(arr[max], arr[i]);
}
}?? 2.冒泡排序模板 (穩(wěn)定,提前退出優(yōu)化)
template<typename T>
void myBubbleSort_T(T arr[], int len) {
for (int i = 0; i < len-1; i++) {
bool swapped = false;
for (int j = 0; j < len-1-i; j++) {
if (arr[j] < arr[j+1]) { // 降序排列
swap_T(arr[j], arr[j+1]);
swapped = true;
}
}
if (!swapped) break;
}
}在 test2 中對 "hello world" 字符數(shù)組進行降序排序,test3/test4 演示整型數(shù)組的排序過程,充分體現(xiàn)了模板對 char 和 int 的通用性。
?? 八、完整測試模塊說明
程序中的 test1~test5 覆蓋了模板的所有關鍵特性,下面逐一說明:
- test1():演示普通函數(shù) vs 模板交換、加法、自動推導失敗案例、顯式指定類型 + 隱式轉換、無參模板必須顯式實例化。
- test2():字符數(shù)組選擇排序 +
printArr重載測試,展示默認模板和自定義連接符模板的重載。 - test3():整型數(shù)組選擇排序,并演示
printArr<>(int_arr, num)強制調用模板版本。 - test4():冒泡排序模板測試,驗證提前退出優(yōu)化。
- test5():函數(shù)調用優(yōu)先級測試 —— 普通函數(shù) printArr(int[],len) 被優(yōu)先調用,特化版本處理 MyPoint,以及空模板參數(shù)強制使用模板。
?? 注意:test5 中通過 MyPoint point_arr[] 調用 printArr 會自動匹配模板特化版本,輸出格式化為 (x,y)。
?? 九、易錯點 & 最佳實踐總結
?? 模板定義通常放在頭文件中,因為編譯器需要在使用時看到完整定義才能實例化。
?? 函數(shù)模板不支持默認模板參數(shù)(C++11 后支持,但函數(shù)模板常用默認類型參數(shù)較少見,類模板更常見)。
?? 模板特化時 template<> 必須前置,且參數(shù)列表必須匹配原模板。
?? 避免過度特化,優(yōu)先使用重載或 if constexpr (C++17)。
?? 如果希望模板只適用于具有某種特性的類型,可以結合 SFINAE 或 C++20 Concept。
?? 十、關鍵代碼與模板
// 1. 泛型交換
template<typename T> void mySwap(T& a, T& b) { T t = a; a = b; b = t; }
// 2. 泛型打印數(shù)組(通用)
template<typename T>
void printArray(const T arr[], size_t len) {
for (size_t i = 0; i < len; ++i) cout << arr[i] << " ";
}
// 3. 針對int的特化
template<> void printArray(const int arr[], size_t len) {
for (size_t i = 0; i < len; ++i) cout << arr[i] << " | ";
}
// 4. 泛型排序算法框架(選擇排序)
template<typename T>
void genericSelectionSort(T arr[], int n) {
for (int i = 0; i < n-1; ++i) {
int minIdx = i;
for (int j = i+1; j < n; ++j)
if (arr[j] < arr[minIdx]) minIdx = j;
if (minIdx != i) mySwap(arr[i], arr[minIdx]);
}
}基于以上模板,可以輕松排序整數(shù)、浮點數(shù)、字符甚至自定義對象(需實現(xiàn)比較運算符或提供仿函數(shù))。
Tips:仿函數(shù)(也稱為函數(shù)對象)是C++中一個行為類似函數(shù)的對象。它通過重載函數(shù)調用運算符operator()來實現(xiàn),使得對象可以像函數(shù)一樣被調用,這里就不多展開了。
// 傳統(tǒng)比較函數(shù)
bool compareInts(int a, int b) {
return a < b; // 從小到大
}
// 仿函數(shù)比較器
class CompareInts {
public:
bool operator()(int a, int b) const {
return a < b; // 從小到大
}
};?? 十一、模板的優(yōu)勢與運行時開銷
函數(shù)模板在編譯期進行實例化,為每個類型生成獨立函數(shù),因此沒有運行時額外開銷,代碼效率和手寫特定類型函數(shù)相同。同時又能保證類型安全,堪稱C++泛型編程的基石。
?? 函數(shù)模板使得代碼極度精簡,同時又保留了極高的靈活性。在項目中,盡量將通用算法抽象為模板,減少重復勞動。
?? 十二、思考與擴展
1.如何修改選擇排序模板使其支持升序/降序通過函數(shù)參數(shù)控制?
可以從幾個層面來修改,使其功能更靈活:
方案一:增加布爾參數(shù) 這是最直接的修改。在函數(shù)參數(shù)列表中添加一個bool ascending(默認為true表示升序),然后在內部比較時根據(jù)這個參數(shù)決定是使用<(找最小值,用于升序)還是>(找最大值,用于降序)。
template<typename T>
void mySelectionSort_T(T arr[], int len, bool ascending = true) {
for (int i = 0; i < len-1; i++) {
int targetIdx = i; // 目標位置應存放的元素索引
for (int j = i + 1; j < len; j++) {
bool condition = ascending ? (arr[j] < arr[targetIdx]) : (arr[j] > arr[targetIdx]);
if (condition) {
targetIdx = j;
}
}
if (targetIdx != i) {
swap_T(arr[targetIdx], arr[i]);
}
}
}
使用示例: mySelectionSort_T(arr, len); // 默認升序 mySelectionSort_T(arr, len, false); // 降序
方案二:增加枚舉參數(shù)(增強可讀性) 定義一個enum SortOrder { ASCENDING, DESCENDING };,用枚舉值代替布爾值,使調用意圖更明確。
mySelectionSort_T(arr, len, SortOrder::DESCENDING);
方案三:增加仿函數(shù)參數(shù)(最靈活、最符合C++標準庫風格,推薦) 這是最強大和通用的方法。在模板中增加一個比較器參數(shù)Compare comp,默認值可以設為std::less<T>()(表示升序)。在內部比較時,不再直接使用<或>,而是調用比較器comp(arr[j], arr[targetIdx])。如果此調用返回true,則意味著arr[j]應該排在arr[targetIdx]之前,因此更新targetIdx。
template<typename T, typename Compare = std::less<T>>
void mySelectionSort_T(T arr[], int len, Compare comp = Compare()) {
for (int i = 0; i < len-1; i++) {
int targetIdx = i;
for (int j = i + 1; j < len; j++) {
if (comp(arr[j], arr[targetIdx])) { // 使用比較器
targetIdx = j;
}
}
if (targetIdx != i) {
swap_T(arr[targetIdx], arr[i]);
}
}
}
使用示例:
mySelectionSort_T(arr, len); // 默認使用std::less,升序
mySelectionSort_T(arr, len, std::greater<int>()); // 使用std::greater,降序
mySelectionSort_T(arr, len, [](int a, int b){ return a % 10 < b % 10; }); // 使用lambda表達式作為比較器自定義比較規(guī)則(按個位數(shù)升序排序)
mySelectionSort_T(arr, len, [](int a, int b){ return a % 10 > b % 10; }); // 使用lambda表達式作為比較器自定義比較規(guī)則(按個位數(shù)降序排序)
tips:無論是 std::less、std::greater還是自定義的函數(shù),在傳遞給像 std::sort或mySelectionSort_T這樣的排序算法時,都遵循同一個嚴格的語義約定:
比較函數(shù)
comp(a, b)返回true的唯一含義是:在算法所構建的最終有序序列中,元素a必須排在元素b的前面。
這個約定是排序算法能夠工作的基礎。算法并不關心 comp內部是實現(xiàn)了“小于”還是“大于”,它只關心這個“前后順序”的規(guī)則。
比較器 | 比較操作 | 返回 true的含義 | 在排序中產(chǎn)生的效果 |
|---|---|---|---|
std::less<T>? | a < b | a(較小的) 應排在 b(較大的) 前面? | 升序? (小的在前,大的在后) |
std::greater<T>? | a > b | a(較大的) 應排在 b(較小的) 前面? | 降序? (大的在前,小的在后) |
簡單記憶:std::less-> “小者在前” -> 升序。std::greater-> “大者在前” -> 降序。
2.如果希望模板只接受具有operator<的類型,應該怎么做?
(C++20 Concept:template<typename T> requires std::totally_ordered<T>)
基于C++20的現(xiàn)代特性,我們可以這樣實現(xiàn):
std::totally_ordered<T> 是一個概念(Concept):它要求類型T必須支持<, <=, >, >=這四個完整的比較運算符。如果只需要operator<,使用std::totally_ordered是足夠的,但可能約束“過強”。一個更精確的自定義概念可以是:
template<typename T>
concept HasLessThan = requires(const T& a, const T& b) {
{ a < b } -> std::convertible_to<bool>;
};
- template<typename T>?:聲明一個模板參數(shù) T
- concept HasLessThan =? :定義一個名為 HasLessThan的概念
- requires(const T& a, const T& b)? :要求部分,定義需要滿足的條件
- { a < b } -> std::convertible_to<bool>;? :具體的要求:a < b必須是一個有效的表達式,表達式的結果必須能轉換為 bool類型
這個概念的語義是:類型 T必須支持 <運算符,并且這個運算符的結果可以被用作布爾值。
概念(Concept)與模板函數(shù)的區(qū)別
特性 | 概念(Concept) | 模板函數(shù) |
|---|---|---|
本質? | 編譯時類型約束,是謂詞(返回true/false) | 編譯時代碼生成,是可執(zhí)行代碼? |
作用? | 檢查類型是否滿足條件,不產(chǎn)生可執(zhí)行代碼 | 為特定類型生成函數(shù)實例,產(chǎn)生可執(zhí)行代碼 |
返回值? | 編譯時布爾值(類型是否滿足條件) | 運行時返回值(函數(shù)執(zhí)行結果) |
然后,在函數(shù)模板聲明中使用它進行約束:
template <HasLessThan T> // 或者 template <std::totally_ordered T>
void mySelectionSort_T(T arr[], int len) {
// ... 函數(shù)體內部可以安全地使用 arr[j] < arr[targetIdx] ...
}
這樣,當用戶嘗試用不支持operator<的類型實例化此模板時,編譯器會在調用處給出清晰易懂的錯誤信息,而不是在函數(shù)體內部遇到<符號時才報出令人困惑的錯誤。這是C++20引入Concepts的主要優(yōu)勢之一:將接口約束檢查提前,并改善錯誤信息。
3.嘗試實現(xiàn)一個模板函數(shù)findMax,返回數(shù)組中最大元素的索引。
這個函數(shù)可以看作是mySelectionSort_T函數(shù)中“尋找最大值”步驟的獨立版本,實現(xiàn)起來非常直觀:
/// 查找數(shù)組中最大元素的索引
/// @param arr 數(shù)組指針
/// @param len 數(shù)組長度
/// @return 最大元素的索引,如果數(shù)組為空(len<=0)則返回-1
template<typename T>
int findMax(T arr[], int len) {
if (len <= 0) return -1; // 處理邊界情況
int maxIdx = 0; // 假設第一個元素最大
for (int i = 1; i < len; ++i) {
if (arr[maxIdx] < arr[i]) { // 如果發(fā)現(xiàn)更大的元素
maxIdx = i; // 更新最大元素索引
}
}
return maxIdx;
}
增強版本:可以像排序函數(shù)一樣,為其增加一個比較器參數(shù),使其更加通用,可以用于尋找“在某種比較規(guī)則下的極值”。
template<typename T, typename Compare = std::less<T>>
int findMax(T arr[], int len, Compare comp = Compare()) {
if (len <= 0) return -1;
int extremeIdx = 0;
for (int i = 1; i < len; ++i) {
if (comp(arr[extremeIdx], arr[i])) { // 如果當前“極值”排在 arr[i] 之前
extremeIdx = i; // 更新為新的“極值”
}
}
return extremeIdx;
}
// 使用:findMax(arr, len) 找最大, findMax(arr, len, std::greater<int>()) 找最小。
4.模板特化和函數(shù)重載有何區(qū)別?何時優(yōu)先使用特化?
這是一個核心概念,代碼已經(jīng)同時包含了兩種用法(printArr的特化和重載),正好可以用來對比。
核心區(qū)別:
| 特性 | 模板特化 | 函數(shù)重載 |
|---|---|---|
| 定義 | 為一個已有的主模板的特定類型參數(shù)提供一個完全特殊的實現(xiàn)。 | 定義多個同名但參數(shù)列表不同的獨立函數(shù)。 |
| 關系 | 是主模板的“特例”,語法上緊密綁定(需要template<>)。 | 函數(shù)之間是平等的“重載”關系,彼此獨立。 |
| 匹配時機 | 在模板參數(shù)推導/確定之后發(fā)生。編譯器先決定調用哪個模板,再檢查是否有特化。 | 在重載決議階段發(fā)生。編譯器在所有候選函數(shù)(包括普通函數(shù)和可能推導出的模板)中選擇最匹配的一個。 |
| 靈活性 | 主要是為特定類型定制行為,通常保持相同的函數(shù)簽名和語義。 | 可以改變參數(shù)數(shù)量、類型,甚至語義(但通常不推薦改變語義)。 |
代碼中的例子:
template<> void printArr<int>(int arr[], int len)是模板特化。它是對主模板template<typename T> void printArr(T arr[], int len)在T=int時的特殊實現(xiàn)。void printArr(int arr[], int len)是一個普通函數(shù)重載。它是一個獨立的函數(shù),與模板無關。當調用printArr(int_arr, num1)時,根據(jù)您提到的優(yōu)先級規(guī)則(完全匹配的普通函數(shù) > 模板),它會優(yōu)先于模板被調用。
何時優(yōu)先使用特化?
- 定制類模板行為時:這是最常見場景。函數(shù)重載不能用于類,只能對類模板進行(全/偏)特化。例如,為
std::vector<bool>做特化優(yōu)化。 - 希望行為變化對用戶“透明”時:當你想為某種類型提供特殊實現(xiàn),但希望函數(shù)簽名、名稱和核心語義保持不變,對所有調用者來說,調用方式完全一樣,只是內部效率或細節(jié)不同。例如,為
std::swap特化自定義類,使其交換更高效。 - 針對一類類型(通過偏特化,僅限類模板):類模板支持偏特化(如
template<typename T> class Widget<T*>),可以為某一類類型(如所有指針)提供特殊實現(xiàn),這是函數(shù)重載無法直接做到的。
總結建議: 代碼注釋中“提供了函數(shù)模板,最好就不要提供普通函數(shù),否則容易出現(xiàn)二義性”的觀點非常重要。在函數(shù)層面,一個更通用的建議是:如果主模板的邏輯對大多數(shù)類型都有效,僅為少數(shù)類型需要特殊處理,且處理邏輯是“優(yōu)化”或“微小調整”,考慮使用特化。如果針對不同類型需要的是邏輯完全不同、甚至簽名都不同的操作,那么應該使用函數(shù)重載。 在實際中,對于函數(shù)模板,使用“帶約束的模板”或“標簽分發(fā)”等技巧有時比特化更清晰。C++20的Concepts進一步增強了這種能力。
?? 附1:示例來自如下實際可運行的 .cpp 文件
#include <iostream>
/// <summary>
/// 表示平面坐標系中的點的類
/// </summary>
class MyPoint {
double x;
double y;
public:
MyPoint(double x, double y) { this->x = x; this->y = y; }
double getX()const { return x; }
double getY()const { return y; }
void show()const { std::cout << '(' << x << ',' << y << ')'; }
};
/// <summary>
/// 打印數(shù)組函數(shù)(模板函數(shù))
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
template<typename T>
void printArr(T arr[], int len) {
for (int i = 0; i < len; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
/// <summary>
/// 打印整型數(shù)組函數(shù)(模板特化)
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
template<>
void printArr<int>(int arr[], int len) {
for (int i = 0; i < len; i++) {
std::cout << arr[i] << " / ";
}
std::cout << std::endl;
}
/// <summary>
/// 打印坐標點數(shù)組函數(shù)(模板特化)
/// 模板的通用性并不是萬能的,比如兩個數(shù)組直接賦值或者自定義數(shù)據(jù)類型操作,所以需要特化
/// 有參數(shù)可以自動推導為MyPoint類型,調用這個特化版本來打印坐標點數(shù)組
/// 也可以顯式指定模板參數(shù)為MyPoint,void printArr<MyPoint>(MyPoint arr[], int len)
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
template<>
void printArr(MyPoint arr[], int len) {
for (int i = 0; i < len; i++) {
std::cout << "(" << arr[i].getX() << "," << arr[i].getY() << ")";
}
std::cout << std::endl;
}
/// <summary>
/// 打印數(shù)組函數(shù),連接符自定義(模板函數(shù))
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
/// <param name="hyphen"></param>
template<typename T>
void printArr(T arr[], int len, char hyphen) {
for (int i = 0; i < len; i++) {
std::cout << arr[i] << hyphen;
}
std::cout << std::endl;
}
/// <summary>
/// 打印數(shù)組函數(shù),連接符固定為逗號(普通函數(shù))
/// 如果函數(shù)模板和普通函數(shù)都可以實現(xiàn),優(yōu)先調用普通函數(shù)
/// 但是提供了函數(shù)模板,最好就不要提供普通函數(shù),否則容易出現(xiàn)二義性
/// </summary>
/// <param name="arr"></param>
/// <param name="len"></param>
void printArr(int arr[], int len) {
for (int i = 0; i < len; i++) {
std::cout << arr[i] << ",";
}
std::cout << std::endl;
}
/// <summary>
/// 交換整型函數(shù)
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
/// <summary>
/// 整型加函數(shù)
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
int addInt(int a, int b) {
return a + b;
}
/// <summary>
/// 交換浮點型函數(shù)
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
/// <summary>
/// 交換函數(shù)(模板函數(shù))
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
template<typename T>
void swap_T(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
/// <summary>
/// 加函數(shù)(模板函數(shù))
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
template<class T>
T add_T(T a, T b)
{
return a + b;
}
/// <summary>
/// 無參數(shù)模板函數(shù)測試
/// </summary>
/// <typeparam name="T"></typeparam>
template<typename T>
void test_T() {
//初始化T類型的變量,使用{}值初始化
//1. 安全性第一:防止未初始化變量
//2. 防止窄化轉換(編譯時錯誤)
//3. 統(tǒng)一性:一套語法適用于所有類型
//4. 區(qū)分函數(shù)聲明和對象構造
//5. 清晰的語義表達
//6. 模板的通用性:適用更多類型(例如自定義類、指針、僅有 explicit構造函數(shù)的類型等)
T temp{};
std::cout << "測試:" << temp << std::endl;
T temp1{0};
std::cout << "測試:" << temp << std::endl;
//T temp2{0.5};//從“double”轉換到“int”需要收縮轉換
//T的類型由顯示指定,而不是自動類型推導,所以T的類型不是浮點型,而是int類型
temp = 1.5;
std::cout << "測試:" << temp << std::endl;
}
/// <summary>
/// 測試函數(shù)
/// </summary>
void test1() {
std::cout << "==============================================================" << std::endl;
std::cout << "函數(shù)模板封裝交換函數(shù)測試:" << std::endl;
int a = 10;
int b = 20;
char c = 'c';//'c' 對應 ASCII碼 99
double d = 0.5;
std::cout << "交換前:a = " << a << ", b = " << b << ", c = " << c << ", d = " << d << std::endl;
std::cout << "=== 普通函數(shù) ===" << std::endl;
//普通函數(shù)交換
swapInt(a, b);
std::cout << "交換后:a = " << a << ", b = " << b << std::endl;
//隱式轉換允許不同類型的變量交換
//swapInt(a, c);// 引用傳遞 - 不允許隱式類型轉換,引用應該對原變量的直接操作,而轉換會產(chǎn)生臨時變量
//但是const引用允許隱式轉換,const引用是只讀的,綁定到臨時變量是安全的,因為不會修改它。
std::cout << a << "+ " <<c << "= " << addInt(a, c) << std::endl;// 值傳遞 - 允許隱式類型轉換
std::cout << "=== 有參數(shù)版本(可以自動推導)===" << std::endl;
//模板函數(shù)交換
swap_T(a, b);
std::cout << "交換后:a = " << a << ", b = " << b << std::endl;
//顯示指定類型
swap_T<int>(a, b);
std::cout << "交換后:a = " << a << ", b = " << b << std::endl;
//swap_T<int>(a, d);//引用傳遞同樣不允許隱式類型轉換
//隱式轉換
std::cout << "相加:a = " << a << ", d = " << d << std::endl;
std::cout << a << "+ " << d << "= " << add_T<int>(a, d) << std::endl;//顯式指定,可以隱式轉換
//std::cout << a << "+ " << d << "= " << add_T(a, d) << std::endl;//自動推導,不可以隱式轉換
//自動類型推導不允許不同類型的變量
// 推導過程:
//1. 看第一個參數(shù)a → T必須與a類型匹配 → T = int
//2. 看第二個參數(shù)d → T必須與d類型匹配 → T = double
//3. 矛盾!T不能既是int又是double
//4. 推導失敗,編譯錯誤
//swap_T(a, d);
std::cout << "=== 無參數(shù)版本(必須顯式指定)===" << std::endl;
//必須顯式指定類型
//test_T();
test_T<int>();
std::cout << "==============================================================" << std::endl;
}
/// <summary>
/// 選擇排序函數(shù)(模板函數(shù))
/// 不穩(wěn)定排序,可能改變相同元素相對順序,因為會遠距離交換
/// 時間復雜度:始終O(n2)
/// 空間復雜度:O(1)原地排序
/// 比較次數(shù)?:固定:n(n - 1) / 2
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
template<typename T>
void mySelectionSort_T(T arr[],int len) {
// 外層循環(huán):控制放置位置
for (int i = 0; i < len-1; i++) {
int max = i;
// 內層循環(huán):從前往后在未排序部分尋找最大值
for (int j = i + 1; j < len; j++) {
if (arr[max] < arr[j]) {
max = j;
}
}
std::cout << "i = " << i << ", max = " << max << std::endl;
// 內層循環(huán)結束后,交換一次,每次都挑出最大值
if (max != i) {
swap_T(arr[max], arr[i]);
std::cout << "交換:" << std::endl;
printArr(arr, len);
}
}
}
/// <summary>
/// 冒泡排序函數(shù)(模板函數(shù))
/// 穩(wěn)定排序,不改變相同元素相對順序,因為只會相鄰交換
/// 時間復雜度:平均O(n2),最優(yōu)O(n)
/// 空間復雜度:O(1)原地排序
/// 比較次數(shù)?:平均n(n-1)/2,最優(yōu)n-1
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="len"></param>
template<typename T>
void myBubbleSort_T(T arr[], int len) {
// 外層循環(huán):控制冒泡輪數(shù)
for (int i = 0; i < len-1; i++) {
bool swapped = false;//退出邏輯優(yōu)化
// 內層循環(huán):從后往前每輪減少比較范圍執(zhí)行相鄰比較
for (int j = 0; j < len-1-i; j++) {
//每輪內循環(huán):讓一個小氣泡上浮到水面(從大到小排序)
std::cout << "i = " << i << ", j = " << j << std::endl;
if (arr[j] < arr[j+1]) {
//立即交換
swap_T(arr[j], arr[j+1]);
swapped = true;
std::cout << "交換:" << std::endl;
printArr(arr, len);
}
}
if (!swapped) {
break;//如果沒有交換,說明已經(jīng)有序,提前退出
std::cout << "已經(jīng)有序,提前退出" << std::endl;
}
}
}
/// <summary>
/// 測試函數(shù)
/// </summary>
void test2() {
std::cout << "==============================================================" << std::endl;
std::cout << "函數(shù)模板封裝選擇排序函數(shù),char數(shù)組測試:"<< std::endl;
char char_arr[] = "hello world";//雙引號括起的字符串會自動在末尾添加空字符'\0',所以長度12
int num = sizeof(char_arr) / sizeof(char);
std::cout << "輸入數(shù)組元素數(shù)量:" << num << std::endl;
//函數(shù)模板重載測試
printArr(char_arr, num);
printArr(char_arr, num,'-');
mySelectionSort_T(char_arr, num);
printArr(char_arr, num);
std::cout << "==============================================================" << std::endl;
}
/// <summary>
/// 測試函數(shù)
/// </summary>
void test3() {
std::cout << "==============================================================" << std::endl;
std::cout << "函數(shù)模板封裝選擇排序函數(shù),int數(shù)組測試:" << std::endl;
//int int_arr[] = {7,5,3,1,5,9,4,5,6,8,5,2 };
int int_arr[] = { 1,2,3,4 };
int num = sizeof(int_arr) / sizeof(int);
std::cout << "輸入數(shù)組元素數(shù)量:" << num << std::endl;
printArr<>(int_arr, num);//空模板參數(shù)列表來強制調用函數(shù)模板
mySelectionSort_T(int_arr, num);
printArr(int_arr, num);
std::cout << "==============================================================" << std::endl;
}
/// <summary>
/// 測試函數(shù)
/// </summary>
void test4() {
std::cout << "==============================================================" << std::endl;
std::cout << "函數(shù)模板封裝冒泡排序函數(shù),int數(shù)組測試:" << std::endl;
//int int_arr[] = {7,5,3,1,5,9,4,5,6,8,5,2 };
int int_arr[] = { 1,2,3,4 };
int num = sizeof(int_arr) / sizeof(int);
std::cout << "輸入數(shù)組元素數(shù)量:" << num << std::endl;
printArr(int_arr, num);
myBubbleSort_T(int_arr, num);
printArr(int_arr, num);
std::cout << "==============================================================" << std::endl;
}
/// <summary>
/// 測試函數(shù)
/// 函數(shù)調用優(yōu)先級:
/// 1.完全匹配的普通函數(shù)(最高優(yōu)先級)
/// 2.模板特化
/// 3.函數(shù)模板
/// 4.隱式轉換的普通函數(shù)(最低優(yōu)先級)
/// </summary>
void test5() {
std::cout << "==============================================================" << std::endl;
std::cout << "函數(shù)調用優(yōu)先級測試:" << std::endl;
int int_arr[] = { 1,2,3,4 };
int num1 = sizeof(int_arr) / sizeof(int);
std::cout << "輸入數(shù)組元素數(shù)量:" << num1 << std::endl;
printArr(int_arr, num1);
printArr<>(int_arr, num1);//空模板參數(shù)列表來強制調用函數(shù)模板
MyPoint point_arr[] = { MyPoint(1,2), MyPoint(3,4), MyPoint(5,6) };
int num2 = sizeof(point_arr) / sizeof(MyPoint);
std::cout << "輸入數(shù)組元素數(shù)量:" << num2 << std::endl;
printArr(point_arr, num2);
std::cout << "==============================================================" << std::endl;
}
int main()
{
//調用測試函數(shù)
test1();
test2();
test3();
test4();
test5();
//等待用戶輸入
system("pause");
//返回0表示程序正常結束
return 0;
}?? 附2:程序運行輸出
==============================================================
函數(shù)模板封裝交換函數(shù)測試:
交換前:a = 10, b = 20, c = c, d = 0.5
=== 普通函數(shù) ===
交換后:a = 20, b = 10
20+ c= 119
=== 有參數(shù)版本(可以自動推導)===
交換后:a = 10, b = 20
交換后:a = 20, b = 10
相加:a = 20, d = 0.5
20+ 0.5= 20
=== 無參數(shù)版本(必須顯式指定)===
測試:0
測試:0
測試:1
==============================================================
==============================================================
函數(shù)模板封裝選擇排序函數(shù),char數(shù)組測試:
輸入數(shù)組元素數(shù)量:12
h e l l o w o r l d
h-e-l-l-o- -w-o-r-l-d--
i = 0, max = 6
交換:
w e l l o h o r l d
i = 1, max = 8
交換:
w r l l o h o e l d
i = 2, max = 4
交換:
w r o l l h o e l d
i = 3, max = 7
交換:
w r o o l h l e l d
i = 4, max = 4
i = 5, max = 7
交換:
w r o o l l h e l d
i = 6, max = 9
交換:
w r o o l l l e h d
i = 7, max = 9
交換:
w r o o l l l h e d
i = 8, max = 8
i = 9, max = 10
交換:
w r o o l l l h e d
i = 10, max = 10
w r o o l l l h e d
==============================================================
==============================================================
函數(shù)模板封裝選擇排序函數(shù),int數(shù)組測試:
輸入數(shù)組元素數(shù)量:4
1 / 2 / 3 / 4 /
i = 0, max = 3
交換:
4,2,3,1,
i = 1, max = 2
交換:
4,3,2,1,
i = 2, max = 2
4,3,2,1,
==============================================================
==============================================================
函數(shù)模板封裝冒泡排序函數(shù),int數(shù)組測試:
輸入數(shù)組元素數(shù)量:4
1,2,3,4,
i = 0, j = 0
交換:
2,1,3,4,
i = 0, j = 1
交換:
2,3,1,4,
i = 0, j = 2
交換:
2,3,4,1,
i = 1, j = 0
交換:
3,2,4,1,
i = 1, j = 1
交換:
3,4,2,1,
i = 2, j = 0
交換:
4,3,2,1,
4,3,2,1,
==============================================================
==============================================================
函數(shù)調用優(yōu)先級測試:
輸入數(shù)組元素數(shù)量:4
1,2,3,4,
1 / 2 / 3 / 4 /
輸入數(shù)組元素數(shù)量:3
(1,2)(3,4)(5,6)
==============================================================
到此這篇關于C++ 函數(shù)模板完全指南(最新整理)的文章就介紹到這了,更多相關C++ 函數(shù)模板內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C語言文件操作中 fgets與fputs 函數(shù)詳解
這篇文章主要介紹了C語言文件操作中 fgets與fputs 函數(shù)詳解的相關資料,需要的朋友可以參考下2017-06-06
C++11 中的std::function和std::bind詳解
這篇文章主要介紹了C++ 11 std::function和std::bind,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-10-10
C語言實現(xiàn) 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)
這篇文章主要介紹了 C語言 數(shù)據(jù)類型占多少字節(jié)指針占多少字節(jié)的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09

