1.2 函數模板
-
C++另一種編程思想稱為 泛型編程 ,主要利用的技術就是模板
-
C++提供兩種模板機制:函數模板和類模板
1.2.1 函數模板語法
函數模板作用:
建立一個通用函數,其函數返回值類型和形參類型可以不具體制定,用一個虛擬的類型來代表。
語法:
template<typename T>
函數聲明或定義
解釋:
template --- 聲明創建模板
typename --- 表面其后面的符號是一種數據類型,可以用class代替
T --- 通用的數據類型,名稱可以替換,通常為大寫字母
示例:
//交換整型函數
void swapInt(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
//交換浮點型函數
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
//利用模板提供通用的交換函數
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
//swapInt(a, b);
//利用模板實現交換
//1、自動類型推導
mySwap(a, b);
//2、顯示指定類型
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 函數模板利用關鍵字 template
- 使用函數模板有兩種方式:自動類型推導、顯示指定類型
- 模板的目的是為了提高復用性,將類型參數化
1.2.2 函數模板注意事項
注意事項:
-
自動類型推導,必須推導出一致的數據類型T,才可以使用
-
模板必須要確定出T的數據類型,才可以使用
示例:
//利用模板提供通用的交換函數
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
// 1、自動類型推導,必須推導出一致的數據類型T,才可以使用
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
mySwap(a, b); // 正確,可以推導出一致的T
//mySwap(a, c); // 錯誤,推導不出一致的T類型
}
// 2、模板必須要確定出T的數據類型,才可以使用
template<class T>
void func()
{
cout << "func 調用" << endl;
}
void test02()
{
//func(); //錯誤,模板不能獨立使用,必須確定出T的類型
func<int>(); //利用顯示指定類型的方式,給T一個類型,才可以使用該模板
}
int main() {
test01();
test02();
system("pause");
return 0;
}
總結:
- 使用模板時必須確定出通用數據類型T,並且能夠推導出一致的類型
1.2.3 函數模板案例
案例描述:
- 利用函數模板封裝一個排序的函數,可以對不同數據類型數組進行排序
- 排序規則從大到小,排序算法為選擇排序
- 分別利用char數組和int數組進行測試
示例:
//交換的函數模板
template<typename T>
void mySwap(T &a, T&b)
{
T temp = a;
a = b;
b = temp;
}
template<class T> // 也可以替換成typename
//利用選擇排序,進行對數組從大到小的排序
void mySort(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
int max = i; //最大數的下標
for (int j = i + 1; j < len; j++)
{
if (arr[max] < arr[j])
{
max = j;
}
}
if (max != i) //如果最大數的下標不是i,交換兩者
{
mySwap(arr[max], arr[i]);
}
}
}
template<typename T>
void printArray(T arr[], int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01()
{
//測試char數組
char charArr[] = "bdcfeagh";
int num = sizeof(charArr) / sizeof(char);
mySort(charArr, num);
printArray(charArr, num);
}
void test02()
{
//測試int數組
int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
}
int main() {
test01();
test02();
system("pause");
return 0;
}
總結:模板可以提高代碼復用,需要熟練掌握
1.2.4 普通函數與函數模板的區別
普通函數與函數模板區別:
- 普通函數調用時可以發生自動類型轉換(隱式類型轉換)
- 函數模板調用時,如果利用自動類型推導,不會發生隱式類型轉換
- 如果利用顯示指定類型的方式,可以發生隱式類型轉換
示例:
//普通函數
int myAdd01(int a, int b)
{
return a + b;
}
//函數模板
template<class T>
T myAdd02(T a, T b)
{
return a + b;
}
//使用函數模板時,如果用自動類型推導,不會發生自動類型轉換,即隱式類型轉換
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
cout << myAdd01(a, c) << endl; //正確,將char類型的'c'隱式轉換為int類型 'c' 對應 ASCII碼 99
//myAdd02(a, c); // 報錯,使用自動類型推導時,不會發生隱式類型轉換
myAdd02<int>(a, c); //正確,如果用顯示指定類型,可以發生隱式類型轉換
}
int main() {
test01();
system("pause");
return 0;
}
總結:建議使用顯示指定類型的方式,調用函數模板,因為可以自己確定通用類型T
1.2.5 普通函數與函數模板的調用規則
調用規則如下:
- 如果函數模板和普通函數都可以實現,優先調用普通函數
- 可以通過空模板參數列表來強制調用函數模板
- 函數模板也可以發生重載
- 如果函數模板可以產生更好的匹配,優先調用函數模板
示例:
//普通函數與函數模板調用規則
void myPrint(int a, int b)
{
cout << "調用的普通函數" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << "調用的模板" << endl;
}
template<typename T>
void myPrint(T a, T b, T c)
{
cout << "調用重載的模板" << endl;
}
void test01()
{
//1、如果函數模板和普通函數都可以實現,優先調用普通函數
// 注意 如果告訴編譯器 普通函數是有的,但只是聲明沒有實現,或者不在當前文件內實現,就會報錯找不到
int a = 10;
int b = 20;
myPrint(a, b); //調用普通函數
//2、可以通過空模板參數列表來強制調用函數模板
myPrint<>(a, b); //調用函數模板
//3、函數模板也可以發生重載
int c = 30;
myPrint(a, b, c); //調用重載的函數模板
//4、 如果函數模板可以產生更好的匹配,優先調用函數模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2); //調用函數模板
}
int main() {
test01();
system("pause");
return 0;
}
總結:既然提供了函數模板,最好就不要提供普通函數,否則容易出現二義性
1.2.6 模板的局限性
局限性:
- 模板的通用性並不是萬能的
例如:
template<class T>
void f(T a, T b)
{
a = b;
}
在上述代碼中提供的賦值操作,如果傳入的a和b是一個數組,就無法實現了
再例如:
template<class T>
void f(T a, T b)
{
if(a > b) { ... }
}
在上述代碼中,如果T的數據類型傳入的是像Person這樣的自定義數據類型,也無法正常運行
因此C++為了解決這種問題,提供模板的重載,可以為這些特定的類型提供具體化的模板
示例:
#include<iostream>
using namespace std;
#include <string>
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//普通函數模板
template<class T>
bool myCompare(T& a, T& b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
//具體化,顯示具體化的原型和定意思以template<>開頭,並通過名稱來指出類型
//具體化優先於常規模板
template<> bool myCompare(Person &p1, Person &p2)
{
if ( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
{
return true;
}
else
{
return false;
}
}
void test01()
{
int a = 10;
int b = 20;
//內置數據類型可以直接使用通用的函數模板
bool ret = myCompare(a, b);
if (ret)
{
cout << "a == b " << endl;
}
else
{
cout << "a != b " << endl;
}
}
void test02()
{
Person p1("Tom", 10);
Person p2("Tom", 10);
//自定義數據類型,不會調用普通的函數模板
//可以創建具體化的Person數據類型的模板,用於特殊處理這個類型
bool ret = myCompare(p1, p2);
if (ret)
{
cout << "p1 == p2 " << endl;
}
else
{
cout << "p1 != p2 " << endl;
}
}
int main() {
test01();
test02();
system("pause");
return 0;
}
總結:
- 利用具體化的模板,可以解決自定義類型的通用化
- 學習模板並不是為了寫模板,而是在STL能夠運用系統提供的模板